Skip to content

Commit

Permalink
feat: add Paths Object for POST requests on RPC endpoints
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
laurenceisla committed Nov 11, 2024
1 parent b6161a0 commit ae7aa61
Show file tree
Hide file tree
Showing 12 changed files with 1,281 additions and 9 deletions.
176 changes: 171 additions & 5 deletions sql/components.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ returns jsonb language sql stable as
$$
select oas_build_component_schemas_from_tables_and_composite_types(schemas) ||
oas_build_component_schemas_from_functions_return_types(schemas) ||
oas_build_component_schemas_from_functions_arguments(schemas) ||
oas_build_component_schemas_headers()
$$;

Expand Down Expand Up @@ -180,6 +181,64 @@ from (
) x;
$$;

create or replace function oas_build_component_schemas_from_functions_arguments(schemas text[])
returns jsonb language sql stable as
$$
with all_functions_with_arguments as (
-- Build Component Schemas for IN/INOUT or VARIADIC function arguments
select *
from postgrest_get_all_functions(schemas)
where argument_input_qty > 0
and (argument_is_in or argument_is_inout or argument_is_variadic)
and argument_name <> ''
),
aggregated_function_arguments as (
select
function_schema,
function_full_name,
function_description,
array_agg(argument_name order by argument_position) filter (where argument_is_required) AS required_arguments,
jsonb_object_agg(
argument_name,
case when argument_item_type_name is null and argument_is_composite then
oas_build_reference_to_schemas(argument_composite_full_name)
else
oas_schema_object(
type := postgrest_pgtype_to_oastype(argument_type_name),
format := argument_type_name::text,
items :=
case
when argument_item_type_name is null then
null
when argument_is_composite then
oas_build_reference_to_schemas(argument_composite_full_name)
else
oas_schema_object(
type := postgrest_pgtype_to_oastype(argument_item_type_name),
format := argument_item_type_name::text
)
end
)
end order by argument_position
) as arguments
from all_functions_with_arguments
group by function_schema, function_full_name, function_description
)
select jsonb_object_agg(x.component_name, x.oas_schema)
from (
select
'rpc.args.' || function_full_name as component_name,
oas_schema_object(
description := function_description,
properties := coalesce(arguments, '{}'),
type := 'object',
required := required_arguments
) as oas_schema
from
aggregated_function_arguments
) x;
$$;

create or replace function oas_build_component_schemas_headers()
returns jsonb language sql stable as
$$
Expand Down Expand Up @@ -304,6 +363,7 @@ returns jsonb language sql stable as
$$
select oas_build_component_parameters_query_params_from_tables(schemas) ||
oas_build_component_parameters_query_params_from_function_args(schemas) ||
oas_build_component_parameters_query_params_from_function_ret(schemas) ||
oas_build_component_parameters_query_params_common() ||
oas_build_component_parameters_headers_common();
$$;
Expand All @@ -322,14 +382,42 @@ from (
)
) as param_schema
from (
select table_full_name, column_name
from postgrest_get_all_tables_and_composite_types()
where table_schema = any(schemas)
and (is_table or is_view)
select table_full_name, column_name
from postgrest_get_all_tables_and_composite_types()
where (
table_schema = any(schemas)
and (is_table or is_view)
)
-- composite type columns can also be used as row filters if a function returns it
or exists (
select 1
from postgrest_get_all_functions(schemas)
where return_type_composite_relid = table_oid
)
) _
) x;
$$;

-- Builds "rowFilter"s for functions returning TABLE or INOUT/OUT types
create or replace function oas_build_component_parameters_query_params_from_function_ret(schemas text[])
returns jsonb language sql stable as
$$
select jsonb_object_agg(x.param_name, x.param_schema)
from (
select format('rowFilter.rpc.%1$s.%2$s', function_full_name, argument_name) as param_name,
oas_parameter_object(
name := argument_name,
"in" := 'query',
schema := oas_schema_object(
type := 'string'
)
) as param_schema
from postgrest_get_all_functions(schemas)
where argument_name <> ''
and (argument_is_inout or argument_is_out or argument_is_table)
) x;
$$;

create or replace function oas_build_component_parameters_query_params_from_function_args(schemas text[])
returns jsonb language sql stable as
$$
Expand Down Expand Up @@ -882,7 +970,8 @@ $$;
create or replace function oas_build_request_bodies(schemas text[])
returns jsonb language sql stable as
$$
select oas_build_request_bodies_from_tables(schemas);
select oas_build_request_bodies_from_tables(schemas) ||
oas_build_request_bodies_from_functions(schemas);
$$;

create or replace function oas_build_request_bodies_from_tables(schemas text[])
Expand Down Expand Up @@ -928,6 +1017,83 @@ from (
) as x;
$$;

create or replace function oas_build_request_bodies_from_functions(schemas text[])
returns jsonb language sql stable as
$$
select jsonb_object_agg('rpc.' || x.function_full_name, x.oas_req_body)
from (
select
function_full_name,
oas_request_body_object(
description := function_full_name,
required := argument_default_qty < argument_input_qty,
content :=
case when argument_input_qty = 1 and (array_agg(argument_name) filter (where argument_is_in or argument_is_inout))[1] = '' then
-- Media types according to the single unnamed parameter type
case function_input_argument_types[0]
when 'bytea'::regtype then
jsonb_build_object(
'application/octet-stream',
oas_media_type_object(
"schema" := oas_schema_object(
type := 'string',
format := 'binary'
)
)
)
when 'text'::regtype then
jsonb_build_object(
'text/plain',
oas_media_type_object(
"schema" := oas_schema_object(
type := 'string'
)
)
)
when 'xml'::regtype then
jsonb_build_object(
'text/xml',
oas_media_type_object(
"schema" := oas_schema_object(
type := 'string',
format := 'xml'
)
)
)
else -- single json or jsonb parameters
jsonb_build_object(
'application/json',
oas_media_type_object(
"schema" := '{}' -- json/jsonb types can be any type
)
)
end
else
jsonb_build_object(
'application/json',
oas_media_type_object(
"schema" := oas_build_reference_to_schemas('rpc.args.' || function_full_name)
),
'application/x-www-form-urlencoded',
oas_media_type_object(
"schema" := oas_build_reference_to_schemas('rpc.args.' || function_full_name)
),
'text/csv',
oas_media_type_object(
"schema" := oas_schema_object(
type := 'string',
format := 'csv'
)
)
)
end
) as oas_req_body
from postgrest_get_all_functions(schemas)
where argument_input_qty > 0
group by function_full_name, argument_input_qty, argument_default_qty, function_input_argument_types
) as x;
$$;

-- Security Schemes

create or replace function oas_build_component_security_schemes ()
Expand Down
66 changes: 63 additions & 3 deletions sql/paths.sql
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,73 @@ from (
'default',
oas_build_reference_to_responses('defaultError', 'Error')
)
),
post := oas_operation_object(
summary := (postgrest_unfold_comment(function_description))[1],
description := (postgrest_unfold_comment(function_description))[2],
tags := array['(rpc) ' || function_name],
requestBody := case when argument_input_qty > 0 then oas_build_reference_to_request_bodies('rpc.' || function_full_name) end,
parameters :=
-- TODO: The row filters for functions returning TABLE, OUT, INOUT and composite types should also work for the GET path.
-- 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).
coalesce(
jsonb_agg(
oas_build_reference_to_parameters(format('rowFilter.rpc.%1$s.%2$s', function_full_name, argument_name))
) filter ( where argument_name <> '' and (argument_is_inout or argument_is_out or argument_is_table)),
'[]'
) ||
return_composite_param_ref ||
case when return_type_is_table or return_type_is_out or return_type_composite_relid <> 0 then
jsonb_build_array(
oas_build_reference_to_parameters('select'),
oas_build_reference_to_parameters('order'),
oas_build_reference_to_parameters('limit'),
oas_build_reference_to_parameters('offset'),
oas_build_reference_to_parameters('or'),
oas_build_reference_to_parameters('and'),
oas_build_reference_to_parameters('not.or'),
oas_build_reference_to_parameters('not.and'),
oas_build_reference_to_parameters('preferPostRpc')
)
else
jsonb_build_array(
oas_build_reference_to_parameters('preferPostRpc')
)
end,
responses :=
case when return_type_is_set then
jsonb_build_object(
'200',
oas_build_reference_to_responses('rpc.' || function_full_name, 'OK'),
'206',
oas_build_reference_to_responses('rpc.' || function_full_name, 'Partial Content')
)
else
jsonb_build_object(
'200',
oas_build_reference_to_responses('rpc.' || function_full_name, 'OK')
)
end ||
jsonb_build_object(
'default',
oas_build_reference_to_responses('defaultError', 'Error')
)
)
) as oas_path_item
from (
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
from postgrest_get_all_functions(schemas)
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,
comp.return_composite_param_ref
from postgrest_get_all_functions(schemas) f
left join lateral (
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
from (
select c.table_full_name, c.column_name
from postgrest_get_all_tables_and_composite_types() c
where f.return_type_composite_relid = c.table_oid
) _
) comp on true
) _
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
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
) x;
$$;

Expand Down
6 changes: 5 additions & 1 deletion sql/postgrest.sql
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ $$;
create or replace function postgrest_get_all_functions(schemas text[])
returns table (
argument_input_qty int,
argument_default_qty int,
argument_name text,
argument_reg_type oid,
argument_type_name text,
Expand All @@ -185,6 +186,7 @@ returns table (
function_name name,
function_full_name text,
function_description text,
function_input_argument_types oidvector,
return_type_name text,
return_type_item_name text,
return_type_is_set bool,
Expand Down Expand Up @@ -225,6 +227,7 @@ $$
all_functions AS (
SELECT
p.pronargs AS argument_input_qty,
p.pronargdefaults AS argument_default_qty,
COALESCE(pa.name, '') AS argument_name,
pa.type AS argument_reg_type,
format_type(ta.oid, NULL::integer) AS argument_type_name,
Expand All @@ -247,6 +250,7 @@ $$
-- The "full name" of the function `<schema>.<name>`. We omit `<schema>.` when it belongs to the `current_schema`
COALESCE(NULLIF(pn.nspname, current_schema) || '.', '') || p.proname AS function_full_name,
d.description AS function_description,
p.proargtypes AS function_input_argument_types,
format_type(t.oid, NULL::integer) AS return_type_name,
format_type(t_arr.oid, NULL::integer) AS return_type_item_name,
p.proretset AS return_type_is_set,
Expand Down Expand Up @@ -290,7 +294,7 @@ $$
WHERE x.argument_input_qty > 0
AND x.argument_name = ''
AND (x.argument_is_in OR x.argument_is_inout OR x.argument_is_variadic)
AND NOT (x.argument_input_qty = 1 AND x.argument_reg_type IN ('bytea'::regtype, 'json'::regtype, 'jsonb'::regtype, 'text'::regtype, 'xml'::regtype))
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))
AND x.function_oid = a.function_oid
);
$$;
Expand Down
Loading

0 comments on commit ae7aa61

Please sign in to comment.