-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: generate "queries" by stored pg function #25
Comments
Thank you for the outstanding report! I think what you're asking is:
The problem is that Here's the full information from OID for record type [
{
"oid": 2249,
"typname": "record",
"typnamespace": 11,
"typowner": 10,
"typlen": -1,
"typbyval": false,
"typtype": "p",
"typcategory": "P",
"typispreferred": false,
"typisdefined": true,
"typdelim": ",",
"typrelid": 0,
"typelem": 0,
"typarray": 2287,
"typinput": "record_in",
"typoutput": "record_out",
"typreceive": "record_recv",
"typsend": "record_send",
"typmodin": "-",
"typmodout": "-",
"typanalyze": "-",
"typalign": "d",
"typstorage": "x",
"typnotnull": false,
"typbasetype": 0,
"typtypmod": -1,
"typndims": 0,
"typcollation": 0,
"typdefaultbin": null,
"typdefault": null,
"typacl": null
}
] pg_proc details for func - Note the return type of 2249, the record type. [
{
"oid": 16519,
"proname": "composite_func_2",
"pronamespace": 16385,
"proowner": 10,
"prolang": 13385,
"procost": 100,
"prorows": 0,
"provariadic": 0,
"prosupport": "-",
"prokind": "f",
"prosecdef": false,
"proleakproof": false,
"proisstrict": false,
"proretset": false,
"provolatile": "i",
"proparallel": "s",
"pronargs": 3,
"pronargdefaults": 2,
"prorettype": 2249,
"proargtypes": "20 23 23",
"proallargtypes": [20, 23, 23, 16514, 16518],
"proargmodes": ["i", "i", "i", "o", "o"],
"proargnames": ["param1", "page", "item_count", "res_items", "res_stats"],
"proargdefaults": "({CONST :consttype 23 :consttypmod -1 :constcollid 0 :constlen 4 :constbyval true :constisnull false :location 238 :constvalue 4 [ 1 0 0 0 0 0 0 0 ]} {CONST :consttype 23 :consttypmod -1 :constcollid 0 :constlen 4 :constbyval true :constisnull false :location 263 :constvalue 4 [ 10 0 0 0 0 0 0 0 ]})",
"protrftypes": null,
"prosrc": "\nBEGIN\n res_items := ARRAY [('foo', 'bar')::list_item];\n res_stats := ('a=>1,a=>2'::hstore, ARRAY [param1, page, item_count])::list_stats;\nEND\n",
"probin": null,
"proconfig": null,
"proacl": null
}
] Attempt 1: Use
|
An idea were if it's not possible to reflect the return types in certain cases the following: define the function names as pggen argument. pggen gen go \
--schema-glob "db/schema/**/*.sql" \
--pg-func "fn1" \
--pg-func "fn1_name_in_go=fn1" \ # or Fn1NameInGo ... explicit naming should be always possible
--pg-func "poly_fn2_x=polyfn2(int, text)" \ # or PolyFn2X ... but is required for ambiguous function names
--pg-func "poly_fn2_y=polyfn2(int, int)" # or PolyFn2Y based on function info from Schema could be defined by passing function with the schema "public.fn1", default is "public"
Argument data types contains the signature like:
Type should be "normal" // other were among others "trigger", "agg" What is interesting here is:
The generated go source code, calls the postgres function, and passes optional args only if they are defined. Otherwise the arg should be omitted so that pg can use the default value (it only use it if the arg not present in the call at all). Just for completeness: arguments with a default functions must be always after all other mandatory args in pg. Also nice: If the function changes in the future my go code fails on build time, which is similar to static type checking - or just calling a function which not exists. When the full function signature is defined in "pg-func", of course not - then it fails even earlier by pggen while source code generation. I try to investigate more about the "record" type issue, or when this happens. Note: |
Question 1: For this query, what's the signature for generated Go function? -- name: Fn1 :one
select fn1(
pggen.arg('x')
); I agree that this proposal is possible, at least for simple cases, but I have 2 concerns:
CREATE TYPE my_custom_type ( res_items list_item[], res_stats lists_stats );
CREATE FUNCTION fn1(
param1 bigint,
page int = 1,
item_count int = 10
) RETURNS my_custom_type AS ''; I haven't tried, so if it's not possible, let me know. |
My idea is to skip the whole query definition part for a defined function. It not solves the reflection problem. Your last example is exactly how I use it today. The downsides are:
But that's totally fine, because pggen try it's best to figure out the types. Querying functions or a queries which returns I try to create a full example of my proposal: schema CREATE FUNCTION fn1(
param1 bigint,
page int = 1,
item_count int = 10,
OUT res_items list_items[],
OUT res_stats lists_stats
) AS ''; query
generation
go source ...
type Fn1OptParams struct {
Page *int
ItemCount *int
}
func (Querier) Fn1(param1 int, Fn1OptParams) (resItems ListItem[], resStats ListsStats, err error) {
// ...
}
... how the function parameters in return value is built up is not defined. Ideas were:
effectively called sql select fn1(param) -- when you call in go -> querier.Fn1(999)
select fn1(param, page => 1) -- when you call in go -> querier.Fn1(999, Fn1OptParams{Page: &1})
select fn1(param, page => 1, item_count => 10) -- when you call in go -> querier.Fn1(999, Fn1OptParams{Page: &1, ItemCount: &10})
select fn1(param, item_count => 10) -- when you call in go -> querier.Fn1(999, Fn1OptParams{ItemCount: &10}) Because internally the query is built up by a defined function, pggen can find out the exact result signature by check for the |
create or replace function format_types(oid[])
returns text[] as $$
select array(select format_type(unnest($1), null))
$$ language sql immutable;
select
l.lanname lang,
n.nspname "schema",
p.oid,
p.prokind kind, -- ! char // f = normal fn, p = procedure, a = aggregate fn, or w = window fn
p.proname "name", -- ! name // name of the function (ex: get_user_info)
format_types(p.proargtypes[:array_length(p.proargtypes,1)-p.pronargdefaults-1]) in_required_args, -- ! oidvector (references pg_type.oid) // An array of the data types of the function arguments. This includes only input arguments (including INOUT and VARIADIC arguments), and thus represents the call signature of the function.
format_types(p.proargtypes[:p.pronargdefaults-1]) as in_optional_args,
p.pronargdefaults default_arg_count, -- ! int2 // Number of arguments that have defaults
p.proisstrict no_null_args, -- ! bool // Function returns null if any call argument is null. In that case the function won't actually be called at all. Functions that are not “strict” must be prepared to handle null inputs.
format_type(p.provariadic, null) variadic_arg_type, -- ! oid (references pg_type.oid) // Data type of the variadic array parameter's elements, or zero if the function does not have a variadic parameter
format_type(p.prorettype, null) "return_type", -- ! oid (references pg_type.oid) // Data type of the return value
p.proretset returns_many, -- ! bool // Function returns a set (i.e., multiple values of the specified data type)
format_types(p.proallargtypes[array_length(p.proargtypes,1)+1:]) out_arg_types,
p.proargmodes arg_types, -- ! array of: 'i' for IN, 'o' for OUT, 'b' for INOUT, 'v' for VARIADIC, 't' for TABLE -- if ALL arguments are 'i' this field will be NULL
p.proargnames arg_names -- ! text[] // An array of the names of the function arguments. Arguments without a name are set to empty strings in the array. If none of the arguments have a name, this field will be null. Note that subscripts correspond to positions of proallargtypes not proargtypes.
from pg_catalog.pg_proc p
left join pg_catalog.pg_namespace n on n.oid = p.pronamespace
left join pg_catalog.pg_language l ON l.oid = p.prolang
where p.proname like 'my_%';
-- where p.oid='myfn()'::regprocedure::oid; Here's another sql snippet to create many kind of functions: drop type if exists user_data cascade;
create type user_data as (
firstname text,
age int
);
drop type if exists user_stat cascade;
create type user_stat as (
stat1 int,
stat2 int
);
-- returns: MANY(user_data[], user_stat)
create or replace function my_fn_1(
page int = 1,
item_count int = 10,
out user_info_list user_data[],
out user_stat user_stat
) returns setof record language plpgsql as $$begin
end$$;
-- 2x OUT, polymorph function #1
-- returns: ONE(user_data[], user_stat)
create or replace function my_fn_2(
page int = 1,
item_count int = 10,
out user_info_list user_data[],
out user_stat user_stat
) returns record language plpgsql as $$begin
end$$;
-- 2x OUT, STRICT, polymorph function #2
-- returns: ONE(user_data[], user_stat)
create or replace function my_fn_2(
page text,
out user_info_list user_data[],
out user_stat user_stat
) returns setof record strict language plpgsql as $$begin
end$$;
-- VARIADIC arg
create or replace function my_fn_4(
user_id int,
digits variadic int[]
) returns void language plpgsql as $$begin
end$$; |
I came up with another idea I want to mention (should be self-described) advantages:
disadvantages:
query-- name: WhateverFn1 :pg-func fn_name_in_pg()
-- name: WhateverFn2 :pg-func fn_name_in_pg(text)
-- name: WhateverFn3 :pg-func another_pg_fn() the function is created in the schema file. |
My preferred approach so far is to use a normal query file -- name: WhateverFn
SELECT fn_name_in_pg(); If the function return type is a record type and the function has output parameters we can infer what the results should be. I think we can get this from the explain output without too much trouble. For parsing, I think we limit it to top-level columns that match a regex like It's not clear how it should interact with other parameters: SELECT fn_name_in_pg(), 2 as two I think I'd want all the parameters at the top level instead of nesting the function output parameters. |
I'm afrait a little when the parsing is done manually, it can end in a patchwork quilt of supported statements what pggen can intrepet correctly - and what ends in hard to understand parsing errors. It would be hoped that the regex does not generate false positives. Open questions:
But wouldn't the inline function anaysing build on my proposal anyway? So this proposal could be the first step (query Function informations), and the inlining mechanism in a second step (parsing statements for function calls and apply it). However this proposal still have a following big advantages:
|
The main trick is to SELECT FROM the function which converts the record type into proper postgres columns. Fixes #25.
The main trick is to SELECT FROM the function which converts the record type into proper postgres columns. Fixes #25.
The main trick is to SELECT FROM the function which converts the record type into proper postgres columns. Fixes #25.
Stumbled upon a much simpler solution. The out columns of SELECT * FROM fn1; |
Motivation
Generally allow any complex nested queries. Currently it's unclear what is possible and when pggen cannot handle it
(as a result this issue is in contract to the promising project description)
In contrast the limitation caused on pg side ("optional arguments" because no reliable argument & result type reflection is possible) are clear.
example schema
keep in mind the same return values could be also created by a single statement used directly as pggen query.
this example just demonstrate the way trough stored functions since this was my initial situation I was confronted with this idea.
example query
My intention is to create plpgsql stored functions which takes all needed arguments once and has at least two different datasets as return values.
There could be an arbitrary number of return values. They can be arrays, single values and of course nested.
advantages
disadvantages
challenges
questions
FindDescendantOIDs
. When some OID's must be preprocessed in pgx, maybe a preparation function (likePrepareAll
in How to enable statement caching? #26 ) could be a solution (since the pg binary msg format don't support more info on query time, a preparation of pgx is anyway indispensable)workaround
unclear
call plpgsql function
vsrun batched prepared statements
in generalThe text was updated successfully, but these errors were encountered: