Skip to content

Commit a7089ac

Browse files
authored
feat: add Paths Object for POST requests on RPC endpoints (#51)
* Adds POST paths to the existing "/rpc/<function_name>" endpoints for valid functions * Adds Schema Objects for function parameters to be used in the request body * Adds common and specific Media Types to the Request Body Object (includes single unnamed argument functions) * Adds "rowFilter"s for functions that return <table>, <composite type> or have TABLE or INOUT/OUT arguments
1 parent b6161a0 commit a7089ac

13 files changed

+1295
-15
lines changed

README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ SQL functions to build the OpenAPI output of a PostgREST instance.
77
- The first step in the roadmap is to migrate the OpenAPI spec from the PostgREST core repository (version 2.0 to 3.1):
88
- [x] Info object
99
- [x] Server object (replaces host, basePath and schemes from OAS 2.0)
10-
- [ ] Components object
10+
- [x] Components object
1111
- [x] Schemas (definitions in OAS 2.0)
1212
- [x] Security scheme (security definitions in OAS 2.0)
13-
- [ ] Parameters
14-
- [ ] Paths object
13+
- [x] Parameters
14+
- [x] Paths object
1515
- [x] Tables and Views
1616
- [x] GET
1717
- [x] POST
1818
- [x] PATCH
1919
- [x] DELETE
20-
- [ ] Functions
21-
- [ ] GET
22-
- [ ] POST
20+
- [x] Functions
21+
- [x] GET
22+
- [x] POST
2323
- [ ] External Documentation Object
2424
- [ ] Handle relevant OpenAPI elements according to user permissions
2525
- The next step is to fix the issues tagged with `OpenAPI` in the core repo.

sql/components.sql

+171-5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ returns jsonb language sql stable as
1919
$$
2020
select oas_build_component_schemas_from_tables_and_composite_types(schemas) ||
2121
oas_build_component_schemas_from_functions_return_types(schemas) ||
22+
oas_build_component_schemas_from_functions_arguments(schemas) ||
2223
oas_build_component_schemas_headers()
2324
$$;
2425

@@ -180,6 +181,64 @@ from (
180181
) x;
181182
$$;
182183

184+
create or replace function oas_build_component_schemas_from_functions_arguments(schemas text[])
185+
returns jsonb language sql stable as
186+
$$
187+
with all_functions_with_arguments as (
188+
-- Build Component Schemas for IN/INOUT or VARIADIC function arguments
189+
select *
190+
from postgrest_get_all_functions(schemas)
191+
where argument_input_qty > 0
192+
and (argument_is_in or argument_is_inout or argument_is_variadic)
193+
and argument_name <> ''
194+
),
195+
aggregated_function_arguments as (
196+
select
197+
function_schema,
198+
function_full_name,
199+
function_description,
200+
array_agg(argument_name order by argument_position) filter (where argument_is_required) AS required_arguments,
201+
jsonb_object_agg(
202+
argument_name,
203+
case when argument_item_type_name is null and argument_is_composite then
204+
oas_build_reference_to_schemas(argument_composite_full_name)
205+
else
206+
oas_schema_object(
207+
type := postgrest_pgtype_to_oastype(argument_type_name),
208+
format := argument_type_name::text,
209+
items :=
210+
case
211+
when argument_item_type_name is null then
212+
null
213+
when argument_is_composite then
214+
oas_build_reference_to_schemas(argument_composite_full_name)
215+
else
216+
oas_schema_object(
217+
type := postgrest_pgtype_to_oastype(argument_item_type_name),
218+
format := argument_item_type_name::text
219+
)
220+
end
221+
)
222+
end order by argument_position
223+
) as arguments
224+
from all_functions_with_arguments
225+
group by function_schema, function_full_name, function_description
226+
)
227+
select jsonb_object_agg(x.component_name, x.oas_schema)
228+
from (
229+
select
230+
'rpc.args.' || function_full_name as component_name,
231+
oas_schema_object(
232+
description := function_description,
233+
properties := coalesce(arguments, '{}'),
234+
type := 'object',
235+
required := required_arguments
236+
) as oas_schema
237+
from
238+
aggregated_function_arguments
239+
) x;
240+
$$;
241+
183242
create or replace function oas_build_component_schemas_headers()
184243
returns jsonb language sql stable as
185244
$$
@@ -304,6 +363,7 @@ returns jsonb language sql stable as
304363
$$
305364
select oas_build_component_parameters_query_params_from_tables(schemas) ||
306365
oas_build_component_parameters_query_params_from_function_args(schemas) ||
366+
oas_build_component_parameters_query_params_from_function_ret(schemas) ||
307367
oas_build_component_parameters_query_params_common() ||
308368
oas_build_component_parameters_headers_common();
309369
$$;
@@ -322,14 +382,42 @@ from (
322382
)
323383
) as param_schema
324384
from (
325-
select table_full_name, column_name
326-
from postgrest_get_all_tables_and_composite_types()
327-
where table_schema = any(schemas)
328-
and (is_table or is_view)
385+
select table_full_name, column_name
386+
from postgrest_get_all_tables_and_composite_types()
387+
where (
388+
table_schema = any(schemas)
389+
and (is_table or is_view)
390+
)
391+
-- composite type columns can also be used as row filters if a function returns it
392+
or exists (
393+
select 1
394+
from postgrest_get_all_functions(schemas)
395+
where return_type_composite_relid = table_oid
396+
)
329397
) _
330398
) x;
331399
$$;
332400

401+
-- Builds "rowFilter"s for functions returning TABLE or INOUT/OUT types
402+
create or replace function oas_build_component_parameters_query_params_from_function_ret(schemas text[])
403+
returns jsonb language sql stable as
404+
$$
405+
select jsonb_object_agg(x.param_name, x.param_schema)
406+
from (
407+
select format('rowFilter.rpc.%1$s.%2$s', function_full_name, argument_name) as param_name,
408+
oas_parameter_object(
409+
name := argument_name,
410+
"in" := 'query',
411+
schema := oas_schema_object(
412+
type := 'string'
413+
)
414+
) as param_schema
415+
from postgrest_get_all_functions(schemas)
416+
where argument_name <> ''
417+
and (argument_is_inout or argument_is_out or argument_is_table)
418+
) x;
419+
$$;
420+
333421
create or replace function oas_build_component_parameters_query_params_from_function_args(schemas text[])
334422
returns jsonb language sql stable as
335423
$$
@@ -882,7 +970,8 @@ $$;
882970
create or replace function oas_build_request_bodies(schemas text[])
883971
returns jsonb language sql stable as
884972
$$
885-
select oas_build_request_bodies_from_tables(schemas);
973+
select oas_build_request_bodies_from_tables(schemas) ||
974+
oas_build_request_bodies_from_functions(schemas);
886975
$$;
887976

888977
create or replace function oas_build_request_bodies_from_tables(schemas text[])
@@ -928,6 +1017,83 @@ from (
9281017
) as x;
9291018
$$;
9301019

1020+
create or replace function oas_build_request_bodies_from_functions(schemas text[])
1021+
returns jsonb language sql stable as
1022+
$$
1023+
select jsonb_object_agg('rpc.' || x.function_full_name, x.oas_req_body)
1024+
from (
1025+
select
1026+
function_full_name,
1027+
oas_request_body_object(
1028+
description := function_full_name,
1029+
required := argument_default_qty < argument_input_qty,
1030+
content :=
1031+
case when argument_input_qty = 1 and (array_agg(argument_name) filter (where argument_is_in or argument_is_inout))[1] = '' then
1032+
-- Media types according to the single unnamed parameter type
1033+
case function_input_argument_types[0]
1034+
when 'bytea'::regtype then
1035+
jsonb_build_object(
1036+
'application/octet-stream',
1037+
oas_media_type_object(
1038+
"schema" := oas_schema_object(
1039+
type := 'string',
1040+
format := 'binary'
1041+
)
1042+
)
1043+
)
1044+
when 'text'::regtype then
1045+
jsonb_build_object(
1046+
'text/plain',
1047+
oas_media_type_object(
1048+
"schema" := oas_schema_object(
1049+
type := 'string'
1050+
)
1051+
)
1052+
)
1053+
when 'xml'::regtype then
1054+
jsonb_build_object(
1055+
'text/xml',
1056+
oas_media_type_object(
1057+
"schema" := oas_schema_object(
1058+
type := 'string',
1059+
format := 'xml'
1060+
)
1061+
)
1062+
)
1063+
else -- single json or jsonb parameters
1064+
jsonb_build_object(
1065+
'application/json',
1066+
oas_media_type_object(
1067+
"schema" := '{}' -- json/jsonb types can be any type
1068+
)
1069+
)
1070+
end
1071+
else
1072+
jsonb_build_object(
1073+
'application/json',
1074+
oas_media_type_object(
1075+
"schema" := oas_build_reference_to_schemas('rpc.args.' || function_full_name)
1076+
),
1077+
'application/x-www-form-urlencoded',
1078+
oas_media_type_object(
1079+
"schema" := oas_build_reference_to_schemas('rpc.args.' || function_full_name)
1080+
),
1081+
'text/csv',
1082+
oas_media_type_object(
1083+
"schema" := oas_schema_object(
1084+
type := 'string',
1085+
format := 'csv'
1086+
)
1087+
)
1088+
)
1089+
end
1090+
) as oas_req_body
1091+
from postgrest_get_all_functions(schemas)
1092+
where argument_input_qty > 0
1093+
group by function_full_name, argument_input_qty, argument_default_qty, function_input_argument_types
1094+
) as x;
1095+
$$;
1096+
9311097
-- Security Schemes
9321098

9331099
create or replace function oas_build_component_security_schemes ()

sql/paths.sql

+63-3
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,73 @@ from (
189189
'default',
190190
oas_build_reference_to_responses('defaultError', 'Error')
191191
)
192+
),
193+
post := oas_operation_object(
194+
summary := (postgrest_unfold_comment(function_description))[1],
195+
description := (postgrest_unfold_comment(function_description))[2],
196+
tags := array['(rpc) ' || function_name],
197+
requestBody := case when argument_input_qty > 0 then oas_build_reference_to_request_bodies('rpc.' || function_full_name) end,
198+
parameters :=
199+
-- TODO: The row filters for functions returning TABLE, OUT, INOUT and composite types should also work for the GET path.
200+
-- Right now they're not included in GET, because the argument names (in rpcParams) could clash with the name of the return type columns (in rowFilter).
201+
coalesce(
202+
jsonb_agg(
203+
oas_build_reference_to_parameters(format('rowFilter.rpc.%1$s.%2$s', function_full_name, argument_name))
204+
) filter ( where argument_name <> '' and (argument_is_inout or argument_is_out or argument_is_table)),
205+
'[]'
206+
) ||
207+
return_composite_param_ref ||
208+
case when return_type_is_table or return_type_is_out or return_type_composite_relid <> 0 then
209+
jsonb_build_array(
210+
oas_build_reference_to_parameters('select'),
211+
oas_build_reference_to_parameters('order'),
212+
oas_build_reference_to_parameters('limit'),
213+
oas_build_reference_to_parameters('offset'),
214+
oas_build_reference_to_parameters('or'),
215+
oas_build_reference_to_parameters('and'),
216+
oas_build_reference_to_parameters('not.or'),
217+
oas_build_reference_to_parameters('not.and'),
218+
oas_build_reference_to_parameters('preferPostRpc')
219+
)
220+
else
221+
jsonb_build_array(
222+
oas_build_reference_to_parameters('preferPostRpc')
223+
)
224+
end,
225+
responses :=
226+
case when return_type_is_set then
227+
jsonb_build_object(
228+
'200',
229+
oas_build_reference_to_responses('rpc.' || function_full_name, 'OK'),
230+
'206',
231+
oas_build_reference_to_responses('rpc.' || function_full_name, 'Partial Content')
232+
)
233+
else
234+
jsonb_build_object(
235+
'200',
236+
oas_build_reference_to_responses('rpc.' || function_full_name, 'OK')
237+
)
238+
end ||
239+
jsonb_build_object(
240+
'default',
241+
oas_build_reference_to_responses('defaultError', 'Error')
242+
)
192243
)
193244
) as oas_path_item
194245
from (
195-
select function_name, function_full_name, function_description, return_type_name, return_type_is_set, return_type_is_table, return_type_is_out, return_type_composite_relid, argument_name, argument_is_in, argument_is_inout, argument_is_variadic
196-
from postgrest_get_all_functions(schemas)
246+
select function_name, function_full_name, function_description, return_type_name, return_type_is_set, return_type_is_table, return_type_is_out, return_type_composite_relid, argument_name, argument_is_in, argument_is_inout, argument_is_out, argument_is_table, argument_is_variadic, argument_input_qty,
247+
comp.return_composite_param_ref
248+
from postgrest_get_all_functions(schemas) f
249+
left join lateral (
250+
select coalesce(jsonb_agg(oas_build_reference_to_parameters(format('rowFilter.%1$s.%2$s', table_full_name, column_name))),'[]') as return_composite_param_ref
251+
from (
252+
select c.table_full_name, c.column_name
253+
from postgrest_get_all_tables_and_composite_types() c
254+
where f.return_type_composite_relid = c.table_oid
255+
) _
256+
) comp on true
197257
) _
198-
group by function_name, function_full_name, function_description, return_type_name, return_type_is_set, return_type_is_table, return_type_is_out, return_type_composite_relid
258+
group by function_name, function_full_name, function_description, return_type_name, return_type_is_set, return_type_is_table, return_type_is_out, return_type_composite_relid, argument_input_qty, return_composite_param_ref
199259
) x;
200260
$$;
201261

sql/postgrest.sql

+5-1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ $$;
166166
create or replace function postgrest_get_all_functions(schemas text[])
167167
returns table (
168168
argument_input_qty int,
169+
argument_default_qty int,
169170
argument_name text,
170171
argument_reg_type oid,
171172
argument_type_name text,
@@ -185,6 +186,7 @@ returns table (
185186
function_name name,
186187
function_full_name text,
187188
function_description text,
189+
function_input_argument_types oidvector,
188190
return_type_name text,
189191
return_type_item_name text,
190192
return_type_is_set bool,
@@ -225,6 +227,7 @@ $$
225227
all_functions AS (
226228
SELECT
227229
p.pronargs AS argument_input_qty,
230+
p.pronargdefaults AS argument_default_qty,
228231
COALESCE(pa.name, '') AS argument_name,
229232
pa.type AS argument_reg_type,
230233
format_type(ta.oid, NULL::integer) AS argument_type_name,
@@ -247,6 +250,7 @@ $$
247250
-- The "full name" of the function `<schema>.<name>`. We omit `<schema>.` when it belongs to the `current_schema`
248251
COALESCE(NULLIF(pn.nspname, current_schema) || '.', '') || p.proname AS function_full_name,
249252
d.description AS function_description,
253+
p.proargtypes AS function_input_argument_types,
250254
format_type(t.oid, NULL::integer) AS return_type_name,
251255
format_type(t_arr.oid, NULL::integer) AS return_type_item_name,
252256
p.proretset AS return_type_is_set,
@@ -290,7 +294,7 @@ $$
290294
WHERE x.argument_input_qty > 0
291295
AND x.argument_name = ''
292296
AND (x.argument_is_in OR x.argument_is_inout OR x.argument_is_variadic)
293-
AND NOT (x.argument_input_qty = 1 AND x.argument_reg_type IN ('bytea'::regtype, 'json'::regtype, 'jsonb'::regtype, 'text'::regtype, 'xml'::regtype))
297+
AND NOT (x.argument_input_qty = 1 AND x.function_input_argument_types[0] IN ('bytea'::regtype, 'json'::regtype, 'jsonb'::regtype, 'text'::regtype, 'xml'::regtype))
294298
AND x.function_oid = a.function_oid
295299
);
296300
$$;

0 commit comments

Comments
 (0)