1
0
mirror of https://github.com/postgres/postgres.git synced 2025-08-27 07:42:10 +03:00

Reconsider the handling of procedure OUT parameters.

Commit 2453ea142 redefined pg_proc.proargtypes to include the types of
OUT parameters, for procedures only.  While that had some advantages
for implementing the SQL-spec behavior of DROP PROCEDURE, it was pretty
disastrous from a number of other perspectives.  Notably, since the
primary key of pg_proc is name + proargtypes, this made it possible to
have multiple procedures with identical names + input arguments and
differing output argument types.  That would make it impossible to call
any one of the procedures by writing just NULL (or "?", or any other
data-type-free notation) for the output argument(s).  The change also
seems likely to cause grave confusion for client applications that
examine pg_proc and expect the traditional definition of proargtypes.

Hence, revert the definition of proargtypes to what it was, and
undo a number of complications that had been added to support that.

To support the SQL-spec behavior of DROP PROCEDURE, when there are
no argmode markers in the command's parameter list, we perform the
lookup both ways (that is, matching against both proargtypes and
proallargtypes), succeeding if we get just one unique match.
In principle this could result in ambiguous-function failures
that would not happen when using only one of the two rules.
However, overloading of procedure names is thought to be a pretty
rare usage, so this shouldn't cause many problems in practice.
Postgres-specific code such as pg_dump can defend against any
possibility of such failures by being careful to specify argmodes
for all procedure arguments.

This also fixes a few other bugs in the area of CALL statements
with named parameters, and improves the documentation a little.

catversion bump forced because the representation of procedures
with OUT arguments changes.

Discussion: https://postgr.es/m/3742981.1621533210@sss.pgh.pa.us
This commit is contained in:
Tom Lane
2021-06-10 17:11:36 -04:00
parent 3a09d75b4f
commit e56bce5d43
44 changed files with 1068 additions and 391 deletions

View File

@@ -124,10 +124,13 @@ static Expr *simplify_function(Oid funcid,
Oid result_collid, Oid input_collid, List **args_p,
bool funcvariadic, bool process_args, bool allow_non_const,
eval_const_expressions_context *context);
static List *reorder_function_arguments(List *args, HeapTuple func_tuple);
static List *add_function_defaults(List *args, HeapTuple func_tuple);
static List *reorder_function_arguments(List *args, int pronargs,
HeapTuple func_tuple);
static List *add_function_defaults(List *args, int pronargs,
HeapTuple func_tuple);
static List *fetch_function_defaults(HeapTuple func_tuple);
static void recheck_cast_function_args(List *args, Oid result_type,
Oid *proargtypes, int pronargs,
HeapTuple func_tuple);
static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod,
Oid result_collid, Oid input_collid, List *args,
@@ -2326,7 +2329,8 @@ eval_const_expressions_mutator(Node *node,
if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u", funcid);
args = expand_function_arguments(expr->args, expr->wintype,
args = expand_function_arguments(expr->args,
false, expr->wintype,
func_tuple);
ReleaseSysCache(func_tuple);
@@ -3841,7 +3845,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
*/
if (process_args)
{
args = expand_function_arguments(args, result_type, func_tuple);
args = expand_function_arguments(args, false, result_type, func_tuple);
args = (List *) expression_tree_mutator((Node *) args,
eval_const_expressions_mutator,
(void *) context);
@@ -3905,6 +3909,15 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
* expand_function_arguments: convert named-notation args to positional args
* and/or insert default args, as needed
*
* Returns a possibly-transformed version of the args list.
*
* If include_out_arguments is true, then the args list and the result
* include OUT arguments.
*
* The expected result type of the call must be given, for sanity-checking
* purposes. Also, we ask the caller to provide the function's actual
* pg_proc tuple, not just its OID.
*
* If we need to change anything, the input argument list is copied, not
* modified.
*
@@ -3913,12 +3926,46 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
* will fall through very quickly if there's nothing to do.
*/
List *
expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
expand_function_arguments(List *args, bool include_out_arguments,
Oid result_type, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
Oid *proargtypes = funcform->proargtypes.values;
int pronargs = funcform->pronargs;
bool has_named_args = false;
ListCell *lc;
/*
* If we are asked to match to OUT arguments, then use the proallargtypes
* array (which includes those); otherwise use proargtypes (which
* doesn't). Of course, if proallargtypes is null, we always use
* proargtypes. (Fetching proallargtypes is annoyingly expensive
* considering that we may have nothing to do here, but fortunately the
* common case is include_out_arguments == false.)
*/
if (include_out_arguments)
{
Datum proallargtypes;
bool isNull;
proallargtypes = SysCacheGetAttr(PROCOID, func_tuple,
Anum_pg_proc_proallargtypes,
&isNull);
if (!isNull)
{
ArrayType *arr = DatumGetArrayTypeP(proallargtypes);
pronargs = ARR_DIMS(arr)[0];
if (ARR_NDIM(arr) != 1 ||
pronargs < 0 ||
ARR_HASNULL(arr) ||
ARR_ELEMTYPE(arr) != OIDOID)
elog(ERROR, "proallargtypes is not a 1-D Oid array or it contains nulls");
Assert(pronargs >= funcform->pronargs);
proargtypes = (Oid *) ARR_DATA_PTR(arr);
}
}
/* Do we have any named arguments? */
foreach(lc, args)
{
@@ -3934,16 +3981,20 @@ expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
/* If so, we must apply reorder_function_arguments */
if (has_named_args)
{
args = reorder_function_arguments(args, func_tuple);
args = reorder_function_arguments(args, pronargs, func_tuple);
/* Recheck argument types and add casts if needed */
recheck_cast_function_args(args, result_type, func_tuple);
recheck_cast_function_args(args, result_type,
proargtypes, pronargs,
func_tuple);
}
else if (list_length(args) < funcform->pronargs)
else if (list_length(args) < pronargs)
{
/* No named args, but we seem to be short some defaults */
args = add_function_defaults(args, func_tuple);
args = add_function_defaults(args, pronargs, func_tuple);
/* Recheck argument types and add casts if needed */
recheck_cast_function_args(args, result_type, func_tuple);
recheck_cast_function_args(args, result_type,
proargtypes, pronargs,
func_tuple);
}
return args;
@@ -3956,10 +4007,9 @@ expand_function_arguments(List *args, Oid result_type, HeapTuple func_tuple)
* impossible to form a truly valid positional call without that.
*/
static List *
reorder_function_arguments(List *args, HeapTuple func_tuple)
reorder_function_arguments(List *args, int pronargs, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int pronargs = funcform->pronargs;
int nargsprovided = list_length(args);
Node *argarray[FUNC_MAX_ARGS];
ListCell *lc;
@@ -3986,6 +4036,7 @@ reorder_function_arguments(List *args, HeapTuple func_tuple)
{
NamedArgExpr *na = (NamedArgExpr *) arg;
Assert(na->argnumber >= 0 && na->argnumber < pronargs);
Assert(argarray[na->argnumber] == NULL);
argarray[na->argnumber] = (Node *) na->arg;
}
@@ -4026,9 +4077,8 @@ reorder_function_arguments(List *args, HeapTuple func_tuple)
* and so we know we just need to add defaults at the end.
*/
static List *
add_function_defaults(List *args, HeapTuple func_tuple)
add_function_defaults(List *args, int pronargs, HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int nargsprovided = list_length(args);
List *defaults;
int ndelete;
@@ -4037,7 +4087,7 @@ add_function_defaults(List *args, HeapTuple func_tuple)
defaults = fetch_function_defaults(func_tuple);
/* Delete any unused defaults from the list */
ndelete = nargsprovided + list_length(defaults) - funcform->pronargs;
ndelete = nargsprovided + list_length(defaults) - pronargs;
if (ndelete < 0)
elog(ERROR, "not enough default arguments");
if (ndelete > 0)
@@ -4086,7 +4136,9 @@ fetch_function_defaults(HeapTuple func_tuple)
* caller should have already copied the list structure.
*/
static void
recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
recheck_cast_function_args(List *args, Oid result_type,
Oid *proargtypes, int pronargs,
HeapTuple func_tuple)
{
Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple);
int nargs;
@@ -4102,9 +4154,8 @@ recheck_cast_function_args(List *args, Oid result_type, HeapTuple func_tuple)
{
actual_arg_types[nargs++] = exprType((Node *) lfirst(lc));
}
Assert(nargs == funcform->pronargs);
memcpy(declared_arg_types, funcform->proargtypes.values,
funcform->pronargs * sizeof(Oid));
Assert(nargs == pronargs);
memcpy(declared_arg_types, proargtypes, pronargs * sizeof(Oid));
rettype = enforce_generic_type_consistency(actual_arg_types,
declared_arg_types,
nargs,