Skip to content

Commit 6af5d63

Browse files
committed
feat(functions): add support for functions embeding introspection
1 parent 91407ee commit 6af5d63

File tree

5 files changed

+228
-0
lines changed

5 files changed

+228
-0
lines changed

Diff for: src/lib/sql/functions.sql

+14
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ select
4444
pg_get_function_result(f.oid) as return_type,
4545
nullif(rt.typrelid::int8, 0) as return_type_relation_id,
4646
f.proretset as is_set_returning_function,
47+
case
48+
when f.proretset and rt.typrelid != 0 then true
49+
else false
50+
end as returns_set_of_table,
51+
case
52+
when f.proretset and rt.typrelid != 0 then
53+
(select relname from pg_class where oid = rt.typrelid)
54+
else null
55+
end as return_table_name,
56+
case
57+
when f.proretset then
58+
coalesce(f.prorows, 0) > 1
59+
else false
60+
end as returns_multiple_rows,
4761
case
4862
when f.provolatile = 'i' then 'IMMUTABLE'
4963
when f.provolatile = 's' then 'STABLE'

Diff for: src/lib/types.ts

+3
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ const postgresFunctionSchema = Type.Object({
156156
return_type: Type.String(),
157157
return_type_relation_id: Type.Union([Type.Integer(), Type.Null()]),
158158
is_set_returning_function: Type.Boolean(),
159+
returns_set_of_table: Type.Boolean(),
160+
return_table_name: Type.Union([Type.String(), Type.Null()]),
161+
returns_multiple_rows: Type.Boolean(),
159162
behavior: Type.Union([
160163
Type.Literal('IMMUTABLE'),
161164
Type.Literal('STABLE'),

Diff for: test/db/00-init.sql

+29
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,32 @@ LANGUAGE SQL STABLE
181181
AS $$
182182
SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id";
183183
$$;
184+
185+
-- SETOF composite_type - Returns multiple rows of a custom composite type
186+
CREATE OR REPLACE FUNCTION public.get_composite_type_data()
187+
RETURNS SETOF composite_type_with_array_attribute
188+
LANGUAGE SQL STABLE
189+
AS $$
190+
SELECT ROW(ARRAY['hello', 'world']::text[])::composite_type_with_array_attribute
191+
UNION ALL
192+
SELECT ROW(ARRAY['foo', 'bar']::text[])::composite_type_with_array_attribute;
193+
$$;
194+
195+
-- SETOF record - Returns multiple rows with structure defined in the function
196+
CREATE OR REPLACE FUNCTION public.get_user_summary()
197+
RETURNS SETOF record
198+
LANGUAGE SQL STABLE
199+
AS $$
200+
SELECT u.id, name, count(t.id) as todo_count
201+
FROM public.users u
202+
LEFT JOIN public.todos t ON t."user-id" = u.id
203+
GROUP BY u.id, u.name;
204+
$$;
205+
206+
-- SETOF scalar_type - Returns multiple values of a basic type
207+
CREATE OR REPLACE FUNCTION public.get_user_ids()
208+
RETURNS SETOF bigint
209+
LANGUAGE SQL STABLE
210+
AS $$
211+
SELECT id FROM public.users;
212+
$$;

Diff for: test/lib/functions.ts

+146
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,147 @@ test('list', async () => {
3636
"is_set_returning_function": false,
3737
"language": "sql",
3838
"name": "add",
39+
"return_table_name": null,
3940
"return_type": "integer",
4041
"return_type_id": 23,
4142
"return_type_relation_id": null,
43+
"returns_multiple_rows": false,
44+
"returns_set_of_table": false,
4245
"schema": "public",
4346
"security_definer": false,
4447
}
4548
`
4649
)
4750
})
4851

52+
test('list set-returning function with single object limit', async () => {
53+
const res = await pgMeta.functions.list()
54+
expect(res.data?.filter(({ name }) => name === 'get_user_audit_setof_single_row'))
55+
.toMatchInlineSnapshot(`
56+
[
57+
{
58+
"args": [
59+
{
60+
"has_default": false,
61+
"mode": "in",
62+
"name": "user_row",
63+
"type_id": 16395,
64+
},
65+
],
66+
"argument_types": "user_row users",
67+
"behavior": "STABLE",
68+
"complete_statement": "CREATE OR REPLACE FUNCTION public.get_user_audit_setof_single_row(user_row users)
69+
RETURNS SETOF users_audit
70+
LANGUAGE sql
71+
STABLE ROWS 1
72+
AS $function$
73+
SELECT * FROM public.users_audit WHERE user_id = user_row.id;
74+
$function$
75+
",
76+
"config_params": null,
77+
"definition": "
78+
SELECT * FROM public.users_audit WHERE user_id = user_row.id;
79+
",
80+
"id": 16498,
81+
"identity_argument_types": "user_row users",
82+
"is_set_returning_function": true,
83+
"language": "sql",
84+
"name": "get_user_audit_setof_single_row",
85+
"return_table_name": "users_audit",
86+
"return_type": "SETOF users_audit",
87+
"return_type_id": 16418,
88+
"return_type_relation_id": 16416,
89+
"returns_multiple_rows": false,
90+
"returns_set_of_table": true,
91+
"schema": "public",
92+
"security_definer": false,
93+
},
94+
]
95+
`)
96+
})
97+
98+
test('list set-returning function with multiples definitions', async () => {
99+
const res = await pgMeta.functions.list()
100+
expect(res.data?.filter(({ name }) => name === 'get_todos_setof_rows')).toMatchInlineSnapshot(`
101+
[
102+
{
103+
"args": [
104+
{
105+
"has_default": false,
106+
"mode": "in",
107+
"name": "user_row",
108+
"type_id": 16395,
109+
},
110+
],
111+
"argument_types": "user_row users",
112+
"behavior": "STABLE",
113+
"complete_statement": "CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(user_row users)
114+
RETURNS SETOF todos
115+
LANGUAGE sql
116+
STABLE
117+
AS $function$
118+
SELECT * FROM public.todos WHERE "user-id" = user_row.id;
119+
$function$
120+
",
121+
"config_params": null,
122+
"definition": "
123+
SELECT * FROM public.todos WHERE "user-id" = user_row.id;
124+
",
125+
"id": 16499,
126+
"identity_argument_types": "user_row users",
127+
"is_set_returning_function": true,
128+
"language": "sql",
129+
"name": "get_todos_setof_rows",
130+
"return_table_name": "todos",
131+
"return_type": "SETOF todos",
132+
"return_type_id": 16404,
133+
"return_type_relation_id": 16402,
134+
"returns_multiple_rows": true,
135+
"returns_set_of_table": true,
136+
"schema": "public",
137+
"security_definer": false,
138+
},
139+
{
140+
"args": [
141+
{
142+
"has_default": false,
143+
"mode": "in",
144+
"name": "todo_row",
145+
"type_id": 16404,
146+
},
147+
],
148+
"argument_types": "todo_row todos",
149+
"behavior": "STABLE",
150+
"complete_statement": "CREATE OR REPLACE FUNCTION public.get_todos_setof_rows(todo_row todos)
151+
RETURNS SETOF todos
152+
LANGUAGE sql
153+
STABLE
154+
AS $function$
155+
SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id";
156+
$function$
157+
",
158+
"config_params": null,
159+
"definition": "
160+
SELECT * FROM public.todos WHERE "user-id" = todo_row."user-id";
161+
",
162+
"id": 16500,
163+
"identity_argument_types": "todo_row todos",
164+
"is_set_returning_function": true,
165+
"language": "sql",
166+
"name": "get_todos_setof_rows",
167+
"return_table_name": "todos",
168+
"return_type": "SETOF todos",
169+
"return_type_id": 16404,
170+
"return_type_relation_id": 16402,
171+
"returns_multiple_rows": true,
172+
"returns_set_of_table": true,
173+
"schema": "public",
174+
"security_definer": false,
175+
},
176+
]
177+
`)
178+
})
179+
49180
test('list functions with included schemas', async () => {
50181
let res = await pgMeta.functions.list({
51182
includedSchemas: ['public'],
@@ -136,9 +267,12 @@ test('retrieve, create, update, delete', async () => {
136267
"is_set_returning_function": false,
137268
"language": "sql",
138269
"name": "test_func",
270+
"return_table_name": null,
139271
"return_type": "integer",
140272
"return_type_id": 23,
141273
"return_type_relation_id": null,
274+
"returns_multiple_rows": false,
275+
"returns_set_of_table": false,
142276
"schema": "public",
143277
"security_definer": true,
144278
},
@@ -186,9 +320,12 @@ test('retrieve, create, update, delete', async () => {
186320
"is_set_returning_function": false,
187321
"language": "sql",
188322
"name": "test_func",
323+
"return_table_name": null,
189324
"return_type": "integer",
190325
"return_type_id": 23,
191326
"return_type_relation_id": null,
327+
"returns_multiple_rows": false,
328+
"returns_set_of_table": false,
192329
"schema": "public",
193330
"security_definer": true,
194331
},
@@ -240,9 +377,12 @@ test('retrieve, create, update, delete', async () => {
240377
"is_set_returning_function": false,
241378
"language": "sql",
242379
"name": "test_func_renamed",
380+
"return_table_name": null,
243381
"return_type": "integer",
244382
"return_type_id": 23,
245383
"return_type_relation_id": null,
384+
"returns_multiple_rows": false,
385+
"returns_set_of_table": false,
246386
"schema": "test_schema",
247387
"security_definer": true,
248388
},
@@ -290,9 +430,12 @@ test('retrieve, create, update, delete', async () => {
290430
"is_set_returning_function": false,
291431
"language": "sql",
292432
"name": "test_func_renamed",
433+
"return_table_name": null,
293434
"return_type": "integer",
294435
"return_type_id": 23,
295436
"return_type_relation_id": null,
437+
"returns_multiple_rows": false,
438+
"returns_set_of_table": false,
296439
"schema": "test_schema",
297440
"security_definer": true,
298441
},
@@ -345,9 +488,12 @@ test('retrieve set-returning function', async () => {
345488
"is_set_returning_function": true,
346489
"language": "sql",
347490
"name": "function_returning_set_of_rows",
491+
"return_table_name": "users",
348492
"return_type": "SETOF users",
349493
"return_type_id": Any<Number>,
350494
"return_type_relation_id": Any<Number>,
495+
"returns_multiple_rows": true,
496+
"returns_set_of_table": true,
351497
"schema": "public",
352498
"security_definer": false,
353499
}

Diff for: test/server/typegen.ts

+36
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,10 @@ test('typegen: typescript', async () => {
432432
name: string
433433
}[]
434434
}
435+
get_composite_type_data: {
436+
Args: Record<PropertyKey, never>
437+
Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][]
438+
}
435439
get_todos_setof_rows: {
436440
Args:
437441
| { user_row: Database["public"]["Tables"]["users"]["Row"] }
@@ -451,6 +455,14 @@ test('typegen: typescript', async () => {
451455
user_id: number | null
452456
}[]
453457
}
458+
get_user_ids: {
459+
Args: Record<PropertyKey, never>
460+
Returns: number[]
461+
}
462+
get_user_summary: {
463+
Args: Record<PropertyKey, never>
464+
Returns: Record<string, unknown>[]
465+
}
454466
polymorphic_function: {
455467
Args: { "": string } | { "": boolean }
456468
Returns: undefined
@@ -1065,6 +1077,10 @@ test('typegen w/ one-to-one relationships', async () => {
10651077
name: string
10661078
}[]
10671079
}
1080+
get_composite_type_data: {
1081+
Args: Record<PropertyKey, never>
1082+
Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][]
1083+
}
10681084
get_todos_setof_rows: {
10691085
Args:
10701086
| { user_row: Database["public"]["Tables"]["users"]["Row"] }
@@ -1084,6 +1100,14 @@ test('typegen w/ one-to-one relationships', async () => {
10841100
user_id: number | null
10851101
}[]
10861102
}
1103+
get_user_ids: {
1104+
Args: Record<PropertyKey, never>
1105+
Returns: number[]
1106+
}
1107+
get_user_summary: {
1108+
Args: Record<PropertyKey, never>
1109+
Returns: Record<string, unknown>[]
1110+
}
10871111
polymorphic_function: {
10881112
Args: { "": string } | { "": boolean }
10891113
Returns: undefined
@@ -1698,6 +1722,10 @@ test('typegen: typescript w/ one-to-one relationships', async () => {
16981722
name: string
16991723
}[]
17001724
}
1725+
get_composite_type_data: {
1726+
Args: Record<PropertyKey, never>
1727+
Returns: Database["public"]["CompositeTypes"]["composite_type_with_array_attribute"][]
1728+
}
17011729
get_todos_setof_rows: {
17021730
Args:
17031731
| { user_row: Database["public"]["Tables"]["users"]["Row"] }
@@ -1717,6 +1745,14 @@ test('typegen: typescript w/ one-to-one relationships', async () => {
17171745
user_id: number | null
17181746
}[]
17191747
}
1748+
get_user_ids: {
1749+
Args: Record<PropertyKey, never>
1750+
Returns: number[]
1751+
}
1752+
get_user_summary: {
1753+
Args: Record<PropertyKey, never>
1754+
Returns: Record<string, unknown>[]
1755+
}
17201756
polymorphic_function: {
17211757
Args: { "": string } | { "": boolean }
17221758
Returns: undefined

0 commit comments

Comments
 (0)