@@ -37,9 +37,22 @@ export const apply = async ({
37
37
} ) : Promise < string > => {
38
38
schemas . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
39
39
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 ] >
42
49
)
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
+ }
43
56
for ( const column of columns ) {
44
57
if ( column . table_id in columnsByTableId ) {
45
58
columnsByTableId [ column . table_id ] . push ( column )
@@ -134,6 +147,30 @@ export const apply = async ({
134
147
} )
135
148
}
136
149
}
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
+
137
174
for ( const func of functions ) {
138
175
if ( func . schema in introspectionBySchema ) {
139
176
func . args . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
@@ -159,24 +196,16 @@ export const apply = async ({
159
196
inArgs [ 0 ] . name === '' &&
160
197
( VALID_UNNAMED_FUNCTION_ARG_TYPES . has ( inArgs [ 0 ] . type_id ) ||
161
198
// 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 ) ) ||
163
201
// 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 ) ) ) )
165
204
) {
166
205
introspectionBySchema [ func . schema ] . functions . push ( { fn : func , inArgs } )
167
206
}
168
207
}
169
208
}
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
- }
180
209
for ( const schema in introspectionBySchema ) {
181
210
introspectionBySchema [ schema ] . tables . sort ( ( a , b ) => a . table . name . localeCompare ( b . table . name ) )
182
211
introspectionBySchema [ schema ] . views . sort ( ( a , b ) => a . view . name . localeCompare ( b . view . name ) )
@@ -185,35 +214,33 @@ export const apply = async ({
185
214
introspectionBySchema [ schema ] . compositeTypes . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
186
215
}
187
216
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
-
197
217
const getFunctionTsReturnType = ( fn : PostgresFunction , returnType : string ) => {
198
218
// Determine if this function should have SetofOptions
199
219
let setofOptionsInfo = ''
200
220
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
+
201
228
// Only add SetofOptions for functions with table arguments (embedded functions)
202
229
// or specific functions that RETURNS table-name
203
230
if ( fn . args . length === 1 && fn . args [ 0 ] . table_name ) {
204
231
// Case 1: Standard embedded function with proper setof detection
205
- if ( fn . returns_set_of_table && fn . return_table_name ) {
232
+ if ( returnsSetOfTable && returnTableName ) {
206
233
setofOptionsInfo = `SetofOptions: {
207
234
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 ) }
210
237
isSetofReturn: true
211
238
}`
212
239
}
213
240
// 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 ) {
215
242
const sourceTable = typesById [ fn . args [ 0 ] . type_id ] . format
216
- const targetTable = fn . return_table_name
243
+ const targetTable = returnTableName
217
244
setofOptionsInfo = `SetofOptions: {
218
245
from: ${ JSON . stringify ( sourceTable ) }
219
246
to: ${ JSON . stringify ( targetTable ) }
@@ -224,16 +251,16 @@ export const apply = async ({
224
251
}
225
252
// Case 3: Special case for functions without table arguments still returning a table
226
253
// Those can be used in rpc to select sub fields of a table
227
- else if ( fn . return_table_name ) {
254
+ else if ( returnTableName ) {
228
255
setofOptionsInfo = `SetofOptions: {
229
256
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 ) }
232
259
isSetofReturn: ${ fn . is_set_returning_function }
233
260
}`
234
261
}
235
262
236
- return `${ returnType } ${ fn . is_set_returning_function && fn . returns_multiple_rows ? '[]' : '' }
263
+ return `${ returnType } ${ fn . is_set_returning_function && returnsMultipleRows ? '[]' : '' }
237
264
${ setofOptionsInfo ? `${ setofOptionsInfo } ` : '' } `
238
265
}
239
266
@@ -311,7 +338,7 @@ export const apply = async ({
311
338
inArgs . length === 1 &&
312
339
inArgs [ 0 ] . name === '' &&
313
340
inArgs [ 0 ] . table_name &&
314
- ! fn . return_table_name
341
+ ! getTableNameFromRelationId ( fn . return_type_relation_id , fn . return_type_id )
315
342
) {
316
343
return true
317
344
}
0 commit comments