@@ -37,9 +37,22 @@ export const apply = async ({
3737} ) : Promise < string > => {
3838 schemas . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
3939
40- const columnsByTableId = Object . fromEntries < PostgresColumn [ ] > (
41- [ ...tables , ...foreignTables , ...views , ...materializedViews ] . map ( ( t ) => [ t . id , [ ] ] )
40+ const columnsByTableId : Record < number , PostgresColumn [ ] > = { }
41+ const tablesNamesByTableId : Record < number , string > = { }
42+ // group types by id for quicker lookup
43+ const typesById = types . reduce (
44+ ( acc , type ) => {
45+ acc [ type . id ] = type
46+ return acc
47+ } ,
48+ { } as Record < number , ( typeof types ) [ number ] >
4249 )
50+ const tablesLike = [ ...tables , ...foreignTables , ...views , ...materializedViews ]
51+
52+ for ( const tableLike of tablesLike ) {
53+ columnsByTableId [ tableLike . id ] = [ ]
54+ tablesNamesByTableId [ tableLike . id ] = tableLike . name
55+ }
4356 for ( const column of columns ) {
4457 if ( column . table_id in columnsByTableId ) {
4558 columnsByTableId [ column . table_id ] . push ( column )
@@ -134,6 +147,30 @@ export const apply = async ({
134147 } )
135148 }
136149 }
150+ for ( const type of types ) {
151+ if ( type . schema in introspectionBySchema ) {
152+ if ( type . enums . length > 0 ) {
153+ introspectionBySchema [ type . schema ] . enums . push ( type )
154+ }
155+ if ( type . attributes . length > 0 ) {
156+ introspectionBySchema [ type . schema ] . compositeTypes . push ( type )
157+ }
158+ }
159+ }
160+ // Helper function to get table/view name from relation id
161+ const getTableNameFromRelationId = (
162+ relationId : number | null ,
163+ returnTypeId : number | null
164+ ) : string | null => {
165+ if ( ! relationId ) return null
166+
167+ if ( tablesNamesByTableId [ relationId ] ) return tablesNamesByTableId [ relationId ]
168+ // if it's a composite type we use the type name as relation name to allow sub-selecting fields of the composite type
169+ if ( returnTypeId && typesById [ returnTypeId ] && typesById [ returnTypeId ] . attributes . length > 0 )
170+ return typesById [ returnTypeId ] . name
171+ return null
172+ }
173+
137174 for ( const func of functions ) {
138175 if ( func . schema in introspectionBySchema ) {
139176 func . args . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
@@ -159,24 +196,16 @@ export const apply = async ({
159196 inArgs [ 0 ] . name === '' &&
160197 ( VALID_UNNAMED_FUNCTION_ARG_TYPES . has ( inArgs [ 0 ] . type_id ) ||
161198 // OR if the function have a single unnamed args which is another table (embeded function)
162- ( inArgs [ 0 ] . table_name && func . return_table_name ) ||
199+ ( inArgs [ 0 ] . table_name &&
200+ getTableNameFromRelationId ( func . return_type_relation_id , func . return_type_id ) ) ||
163201 // OR if the function takes a table row but doesn't qualify as embedded (for error reporting)
164- ( inArgs [ 0 ] . table_name && ! func . return_table_name ) ) )
202+ ( inArgs [ 0 ] . table_name &&
203+ ! getTableNameFromRelationId ( func . return_type_relation_id , func . return_type_id ) ) ) )
165204 ) {
166205 introspectionBySchema [ func . schema ] . functions . push ( { fn : func , inArgs } )
167206 }
168207 }
169208 }
170- for ( const type of types ) {
171- if ( type . schema in introspectionBySchema ) {
172- if ( type . enums . length > 0 ) {
173- introspectionBySchema [ type . schema ] . enums . push ( type )
174- }
175- if ( type . attributes . length > 0 ) {
176- introspectionBySchema [ type . schema ] . compositeTypes . push ( type )
177- }
178- }
179- }
180209 for ( const schema in introspectionBySchema ) {
181210 introspectionBySchema [ schema ] . tables . sort ( ( a , b ) => a . table . name . localeCompare ( b . table . name ) )
182211 introspectionBySchema [ schema ] . views . sort ( ( a , b ) => a . view . name . localeCompare ( b . view . name ) )
@@ -185,35 +214,33 @@ export const apply = async ({
185214 introspectionBySchema [ schema ] . compositeTypes . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
186215 }
187216
188- // group types by id for quicker lookup
189- const typesById = types . reduce (
190- ( acc , type ) => {
191- acc [ type . id ] = type
192- return acc
193- } ,
194- { } as Record < number , ( typeof types ) [ number ] >
195- )
196-
197217 const getFunctionTsReturnType = ( fn : PostgresFunction , returnType : string ) => {
198218 // Determine if this function should have SetofOptions
199219 let setofOptionsInfo = ''
200220
221+ const returnTableName = getTableNameFromRelationId (
222+ fn . return_type_relation_id ,
223+ fn . return_type_id
224+ )
225+ const returnsSetOfTable = fn . is_set_returning_function && fn . return_type_relation_id !== null
226+ const returnsMultipleRows = fn . prorows !== null && fn . prorows > 1
227+
201228 // Only add SetofOptions for functions with table arguments (embedded functions)
202229 // or specific functions that RETURNS table-name
203230 if ( fn . args . length === 1 && fn . args [ 0 ] . table_name ) {
204231 // Case 1: Standard embedded function with proper setof detection
205- if ( fn . returns_set_of_table && fn . return_table_name ) {
232+ if ( returnsSetOfTable && returnTableName ) {
206233 setofOptionsInfo = `SetofOptions: {
207234 from: ${ JSON . stringify ( typesById [ fn . args [ 0 ] . type_id ] . format ) }
208- to: ${ JSON . stringify ( fn . return_table_name ) }
209- isOneToOne: ${ Boolean ( ! fn . returns_multiple_rows ) }
235+ to: ${ JSON . stringify ( returnTableName ) }
236+ isOneToOne: ${ Boolean ( ! returnsMultipleRows ) }
210237 isSetofReturn: true
211238 }`
212239 }
213240 // Case 2: Handle RETURNS table-name those are always a one to one relationship
214- else if ( fn . return_table_name && ! fn . returns_set_of_table ) {
241+ else if ( returnTableName && ! returnsSetOfTable ) {
215242 const sourceTable = typesById [ fn . args [ 0 ] . type_id ] . format
216- const targetTable = fn . return_table_name
243+ const targetTable = returnTableName
217244 setofOptionsInfo = `SetofOptions: {
218245 from: ${ JSON . stringify ( sourceTable ) }
219246 to: ${ JSON . stringify ( targetTable ) }
@@ -224,16 +251,16 @@ export const apply = async ({
224251 }
225252 // Case 3: Special case for functions without table arguments still returning a table
226253 // Those can be used in rpc to select sub fields of a table
227- else if ( fn . return_table_name ) {
254+ else if ( returnTableName ) {
228255 setofOptionsInfo = `SetofOptions: {
229256 from: "*"
230- to: ${ JSON . stringify ( fn . return_table_name ) }
231- isOneToOne: ${ Boolean ( ! fn . returns_multiple_rows ) }
257+ to: ${ JSON . stringify ( returnTableName ) }
258+ isOneToOne: ${ Boolean ( ! returnsMultipleRows ) }
232259 isSetofReturn: ${ fn . is_set_returning_function }
233260 }`
234261 }
235262
236- return `${ returnType } ${ fn . is_set_returning_function && fn . returns_multiple_rows ? '[]' : '' }
263+ return `${ returnType } ${ fn . is_set_returning_function && returnsMultipleRows ? '[]' : '' }
237264 ${ setofOptionsInfo ? `${ setofOptionsInfo } ` : '' } `
238265 }
239266
@@ -311,7 +338,7 @@ export const apply = async ({
311338 inArgs . length === 1 &&
312339 inArgs [ 0 ] . name === '' &&
313340 inArgs [ 0 ] . table_name &&
314- ! fn . return_table_name
341+ ! getTableNameFromRelationId ( fn . return_type_relation_id , fn . return_type_id )
315342 ) {
316343 return true
317344 }
0 commit comments