@@ -8,7 +8,11 @@ import type {
88 PostgresView ,
99} from '../../lib/index.js'
1010import type { GeneratorMetadata } from '../../lib/generators.js'
11- import { GENERATE_TYPES_DEFAULT_SCHEMA , VALID_FUNCTION_ARGS_MODE } from '../constants.js'
11+ import {
12+ GENERATE_TYPES_DEFAULT_SCHEMA ,
13+ VALID_FUNCTION_ARGS_MODE ,
14+ VALID_UNNAMED_FUNCTION_ARG_TYPES ,
15+ } from '../constants.js'
1216
1317export const apply = async ( {
1418 schemas,
@@ -43,16 +47,45 @@ export const apply = async ({
4347 )
4448
4549 const getFunctionTsReturnType = ( fn : PostgresFunction , returnType : string ) => {
50+ // Determine if this function should have SetofOptions
51+ let setofOptionsInfo = ''
52+
53+ // Only add SetofOptions for functions with table arguments (embedded functions)
54+ // or specific functions that need RETURNS table-name introspection fixes
55+ if ( fn . args . length === 1 && fn . args [ 0 ] . table_name ) {
56+ // Case 1: Standard embedded function with proper setof detection
57+ if ( fn . returns_set_of_table && fn . return_table_name ) {
58+ setofOptionsInfo = `SetofOptions: {
59+ from: ${ JSON . stringify ( typesById [ fn . args [ 0 ] . type_id ] . format ) }
60+ to: ${ JSON . stringify ( fn . return_table_name ) }
61+ isOneToOne: ${ fn . returns_multiple_rows ? false : true }
62+ isSetofReturn: true
63+ }`
64+ }
65+ // Case 2: Handle RETURNS table-name those are always a one to one relationship
66+ else if ( fn . return_table_name && ! fn . returns_set_of_table ) {
67+ const sourceTable = typesById [ fn . args [ 0 ] . type_id ] . format
68+ let targetTable = fn . return_table_name
69+ setofOptionsInfo = `SetofOptions: {
70+ from: ${ JSON . stringify ( sourceTable ) }
71+ to: ${ JSON . stringify ( targetTable ) }
72+ isOneToOne: true
73+ isSetofReturn: false
74+ }`
75+ }
76+ }
77+ // Case 3: Special case for functions without table arguments but specific names
78+ else if ( fn . return_table_name ) {
79+ setofOptionsInfo = `SetofOptions: {
80+ from: "*"
81+ to: ${ JSON . stringify ( fn . return_table_name ) }
82+ isOneToOne: ${ fn . returns_multiple_rows ? false : true }
83+ isSetofReturn: ${ fn . is_set_returning_function }
84+ }`
85+ }
86+
4687 return `${ returnType } ${ fn . is_set_returning_function && fn . returns_multiple_rows ? '[]' : '' }
47- ${
48- fn . returns_set_of_table && fn . args . length === 1 && fn . args [ 0 ] . table_name
49- ? `SetofOptions: {
50- from: ${ JSON . stringify ( typesById [ fn . args [ 0 ] . type_id ] . format ) }
51- to: ${ JSON . stringify ( fn . return_table_name ) }
52- isOneToOne: ${ fn . returns_multiple_rows ? false : true }
53- }`
54- : ''
55- } `
88+ ${ setofOptionsInfo ? `${ setofOptionsInfo } ` : '' } `
5689 }
5790
5891 const getFunctionReturnType = ( schema : PostgresSchema , fn : PostgresFunction ) : string => {
@@ -370,42 +403,53 @@ export type Database = {
370403 return '[_ in never]: never'
371404 }
372405 const schemaFunctionsGroupedByName = schemaFunctions
373- // .filter((func) => {
374- // // Get all input args (in, inout, variadic modes)
375- // const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode))
376- // // Case 1: Function has no parameters
377- // if (inArgs.length === 0) {
378- // return true
379- // }
380-
381- // // Case 2: All input args are named
382- // if (!inArgs.some(({ name }) => name === '')) {
383- // return true
384- // }
385-
386- // // Case 3: All unnamed args have default values
387- // if (inArgs.every((arg) => (arg.name === '' ? arg.has_default : true))) {
388- // return true
389- // }
390-
391- // // Case 4: Single unnamed parameter of valid type (json, jsonb, text)
392- // // Exclude all functions definitions that have only one single argument unnamed argument that isn't
393- // // a json/jsonb/text as it won't be considered by PostgREST
394- // if (
395- // (inArgs.length === 1 &&
396- // inArgs[0].name === '' &&
397- // VALID_UNNAMED_FUNCTION_ARG_TYPES.has(inArgs[0].type_id)) ||
398- // // OR if the function have a single unnamed args which is another table (embeded function)
399- // (inArgs.length === 1 &&
400- // inArgs[0].name === '' &&
401- // inArgs[0].table_name &&
402- // func.return_table_name)
403- // ) {
404- // return true
405- // }
406-
407- // return false
408- // })
406+ . filter ( ( func ) => {
407+ // Get all input args (in, inout, variadic modes)
408+ const inArgs = func . args
409+ . toSorted ( ( a , b ) => a . name . localeCompare ( b . name ) )
410+ . filter ( ( { mode } ) => VALID_FUNCTION_ARGS_MODE . has ( mode ) )
411+ // Case 1: Function has no parameters
412+ if ( inArgs . length === 0 ) {
413+ return true
414+ }
415+
416+ // Case 2: All input args are named
417+ if ( ! inArgs . some ( ( { name } ) => name === '' ) ) {
418+ return true
419+ }
420+
421+ // Case 3: All unnamed args have default values AND are valid types
422+ if (
423+ inArgs . every ( ( arg ) => {
424+ if ( arg . name === '' ) {
425+ return arg . has_default && VALID_UNNAMED_FUNCTION_ARG_TYPES . has ( arg . type_id )
426+ }
427+ return true
428+ } )
429+ ) {
430+ return true
431+ }
432+
433+ // Case 4: Single unnamed parameter of valid type (json, jsonb, text)
434+ // Exclude all functions definitions that have only one single argument unnamed argument that isn't
435+ // a json/jsonb/text as it won't be considered by PostgREST
436+ if (
437+ inArgs . length === 1 &&
438+ inArgs [ 0 ] . name === '' &&
439+ ( VALID_UNNAMED_FUNCTION_ARG_TYPES . has ( inArgs [ 0 ] . type_id ) ||
440+ // OR if the function have a single unnamed args which is another table (embeded function)
441+ ( inArgs [ 0 ] . table_name && func . return_table_name ) ||
442+ // OR if the function takes a table row but doesn't qualify as embedded (for error reporting)
443+ ( inArgs [ 0 ] . table_name && ! func . return_table_name ) )
444+ ) {
445+ return true
446+ }
447+
448+ // NOTE: Functions with named table arguments are generally excluded
449+ // as they're not supported by PostgREST in the expected way
450+
451+ return false
452+ } )
409453 . reduce (
410454 ( acc , curr ) => {
411455 acc [ curr . name ] ??= [ ]
@@ -415,12 +459,140 @@ export type Database = {
415459 { } as Record < string , PostgresFunction [ ] >
416460 )
417461
418- return Object . entries ( schemaFunctionsGroupedByName ) . map ( ( [ fnName , fns ] ) => {
462+ return Object . entries ( schemaFunctionsGroupedByName ) . map ( ( [ fnName , _fns ] ) => {
463+ // Check for function overload conflicts
464+ const fns = _fns . toSorted ( ( a , b ) => b . definition . localeCompare ( a . definition ) )
465+
419466 const functionSignatures = fns . map ( ( fn ) => {
420467 const inArgs = fn . args . filter ( ( { mode } ) => VALID_FUNCTION_ARGS_MODE . has ( mode ) )
421468
422- let argsType = 'Record<PropertyKey, never>'
423- if ( inArgs . length > 0 ) {
469+ // Special error case for functions that take table row but don't qualify as embedded functions
470+ const hasTableRowError = ( fn : PostgresFunction ) => {
471+ if (
472+ inArgs . length === 1 &&
473+ inArgs [ 0 ] . name === '' &&
474+ inArgs [ 0 ] . table_name &&
475+ ! fn . return_table_name
476+ ) {
477+ return true
478+ }
479+ return false
480+ }
481+
482+ // Check for generic conflict cases that need error reporting
483+ const getConflictError = ( fn : PostgresFunction ) => {
484+ const sameFunctions = fns . filter ( ( f ) => f . name === fn . name )
485+ if ( sameFunctions . length <= 1 ) return null
486+
487+ // Generic conflict detection patterns
488+
489+ // Pattern 1: No-args vs default-args conflicts
490+ if ( inArgs . length === 0 ) {
491+ const conflictingFns = sameFunctions . filter ( ( otherFn ) => {
492+ if ( otherFn === fn ) return false
493+ const otherInArgs = otherFn . args . filter ( ( { mode } ) =>
494+ VALID_FUNCTION_ARGS_MODE . has ( mode )
495+ )
496+ return (
497+ otherInArgs . length === 1 &&
498+ otherInArgs [ 0 ] . name === '' &&
499+ otherInArgs [ 0 ] . has_default
500+ )
501+ } )
502+
503+ if ( conflictingFns . length > 0 ) {
504+ const conflictingFn = conflictingFns [ 0 ]
505+ const returnTypeName =
506+ types . find ( ( t ) => t . id === conflictingFn . return_type_id ) ?. name ||
507+ 'unknown'
508+ return `Could not choose the best candidate function between: ${ schema . name } .${ fn . name } (), ${ schema . name } .${ fn . name } ( => ${ returnTypeName } ). Try renaming the parameters or the function itself in the database so function overloading can be resolved`
509+ }
510+ }
511+
512+ // Pattern 2: Same parameter name but different types (unresolvable overloads)
513+ if ( inArgs . length === 1 && inArgs [ 0 ] . name !== '' ) {
514+ const conflictingFns = sameFunctions . filter ( ( otherFn ) => {
515+ if ( otherFn === fn ) return false
516+ const otherInArgs = otherFn . args . filter ( ( { mode } ) =>
517+ VALID_FUNCTION_ARGS_MODE . has ( mode )
518+ )
519+ return (
520+ otherInArgs . length === 1 &&
521+ otherInArgs [ 0 ] . name === inArgs [ 0 ] . name &&
522+ otherInArgs [ 0 ] . type_id !== inArgs [ 0 ] . type_id
523+ )
524+ } )
525+
526+ if ( conflictingFns . length > 0 ) {
527+ const allConflictingFunctions = [ fn , ...conflictingFns ]
528+ const conflictList = allConflictingFunctions
529+ . sort ( ( a , b ) => {
530+ const aArgs = a . args . filter ( ( { mode } ) =>
531+ VALID_FUNCTION_ARGS_MODE . has ( mode )
532+ )
533+ const bArgs = b . args . filter ( ( { mode } ) =>
534+ VALID_FUNCTION_ARGS_MODE . has ( mode )
535+ )
536+ return ( aArgs [ 0 ] ?. type_id || 0 ) - ( bArgs [ 0 ] ?. type_id || 0 )
537+ } )
538+ . map ( ( f ) => {
539+ const args = f . args . filter ( ( { mode } ) =>
540+ VALID_FUNCTION_ARGS_MODE . has ( mode )
541+ )
542+ return `${ schema . name } .${ fn . name } (${ args . map ( ( a ) => `${ a . name || '' } => ${ types . find ( ( t ) => t . id === a . type_id ) ?. name || 'unknown' } ` ) . join ( ', ' ) } )`
543+ } )
544+ . join ( ', ' )
545+
546+ return `Could not choose the best candidate function between: ${ conflictList } . Try renaming the parameters or the function itself in the database so function overloading can be resolved`
547+ }
548+ }
549+
550+ return null
551+ }
552+
553+ let argsType = 'never'
554+ let returnType = getFunctionReturnType ( schema , fn )
555+
556+ // Check for specific error cases
557+ const conflictError = getConflictError ( fn )
558+ if ( conflictError ) {
559+ if ( inArgs . length > 0 ) {
560+ const argsNameAndType = inArgs . map ( ( { name, type_id, has_default } ) => {
561+ const type = types . find ( ( { id } ) => id === type_id )
562+ let tsType = 'unknown'
563+ if ( type ) {
564+ tsType = pgTypeToTsType ( schema , type . name , {
565+ types,
566+ schemas,
567+ tables,
568+ views,
569+ } )
570+ }
571+ return { name, type : tsType , has_default }
572+ } )
573+ argsType = `{ ${ argsNameAndType . map ( ( { name, type, has_default } ) => `${ JSON . stringify ( name ) } ${ has_default ? '?' : '' } : ${ type } ` ) } }`
574+ }
575+ returnType = `{ error: true } & ${ JSON . stringify ( conflictError ) } `
576+ } else if ( hasTableRowError ( fn ) ) {
577+ // Special case for computed fields returning scalars functions
578+ if ( inArgs . length > 0 ) {
579+ const argsNameAndType = inArgs . map ( ( { name, type_id, has_default } ) => {
580+ const type = types . find ( ( { id } ) => id === type_id )
581+ let tsType = 'unknown'
582+ if ( type ) {
583+ tsType = pgTypeToTsType ( schema , type . name , {
584+ types,
585+ schemas,
586+ tables,
587+ views,
588+ } )
589+ }
590+ return { name, type : tsType , has_default }
591+ } )
592+ argsType = `{ ${ argsNameAndType . map ( ( { name, type, has_default } ) => `${ JSON . stringify ( name ) } ${ has_default ? '?' : '' } : ${ type } ` ) } }`
593+ }
594+ returnType = `{ error: true } & ${ JSON . stringify ( `the function ${ schema . name } .${ fn . name } with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache` ) } `
595+ } else if ( inArgs . length > 0 ) {
424596 const argsNameAndType = inArgs . map ( ( { name, type_id, has_default } ) => {
425597 const type = types . find ( ( { id } ) => id === type_id )
426598 let tsType = 'unknown'
@@ -437,7 +609,7 @@ export type Database = {
437609 argsType = `{ ${ argsNameAndType . map ( ( { name, type, has_default } ) => `${ JSON . stringify ( name ) } ${ has_default ? '?' : '' } : ${ type } ` ) } }`
438610 }
439611
440- return `{ Args: ${ argsType } ; Returns: ${ getFunctionTsReturnType ( fn , getFunctionReturnType ( schema , fn ) ) } }`
612+ return `{ Args: ${ argsType } ; Returns: ${ getFunctionTsReturnType ( fn , returnType ) } }`
441613 } )
442614
443615 return `${ JSON . stringify ( fnName ) } :\n${ functionSignatures . map ( ( sig ) => `| ${ sig } ` ) . join ( '\n' ) } `
0 commit comments