diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/utils/fmgr/funcapi.c | 104 | ||||
-rw-r--r-- | src/include/funcapi.h | 6 | ||||
-rw-r--r-- | src/pl/plpgsql/src/gram.y | 60 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_comp.c | 458 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_exec.c | 314 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_funcs.c | 22 | ||||
-rw-r--r-- | src/pl/plpgsql/src/plpgsql.h | 9 | ||||
-rw-r--r-- | src/test/regress/expected/plpgsql.out | 119 | ||||
-rw-r--r-- | src/test/regress/sql/plpgsql.sql | 85 |
9 files changed, 839 insertions, 338 deletions
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 160847de1e0..b16cbf9beac 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -7,7 +7,7 @@ * Copyright (c) 2002-2005, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.19 2005/03/31 22:46:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/fmgr/funcapi.c,v 1.20 2005/04/05 06:22:14 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -484,6 +484,108 @@ resolve_polymorphic_tupdesc(TupleDesc tupdesc, oidvector *declared_args, } /* + * Given the declared argument types and modes for a function, + * replace any polymorphic types (ANYELEMENT/ANYARRAY) with correct data + * types deduced from the input arguments. Returns TRUE if able to deduce + * all types, FALSE if not. This is the same logic as + * resolve_polymorphic_tupdesc, but with a different argument representation. + * + * argmodes may be NULL, in which case all arguments are assumed to be IN mode. + */ +bool +resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, + Node *call_expr) +{ + bool have_anyelement_result = false; + bool have_anyarray_result = false; + Oid anyelement_type = InvalidOid; + Oid anyarray_type = InvalidOid; + int inargno; + int i; + + /* First pass: resolve polymorphic inputs, check for outputs */ + inargno = 0; + for (i = 0; i < numargs; i++) + { + char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; + + switch (argtypes[i]) + { + case ANYELEMENTOID: + if (argmode == PROARGMODE_OUT) + have_anyelement_result = true; + else + { + if (!OidIsValid(anyelement_type)) + { + anyelement_type = get_call_expr_argtype(call_expr, + inargno); + if (!OidIsValid(anyelement_type)) + return false; + } + argtypes[i] = anyelement_type; + } + break; + case ANYARRAYOID: + if (argmode == PROARGMODE_OUT) + have_anyarray_result = true; + else + { + if (!OidIsValid(anyarray_type)) + { + anyarray_type = get_call_expr_argtype(call_expr, + inargno); + if (!OidIsValid(anyarray_type)) + return false; + } + argtypes[i] = anyarray_type; + } + break; + default: + break; + } + if (argmode != PROARGMODE_OUT) + inargno++; + } + + /* Done? */ + if (!have_anyelement_result && !have_anyarray_result) + return true; + + /* If no input polymorphics, parser messed up */ + if (!OidIsValid(anyelement_type) && !OidIsValid(anyarray_type)) + return false; + + /* If needed, deduce one polymorphic type from the other */ + if (have_anyelement_result && !OidIsValid(anyelement_type)) + anyelement_type = resolve_generic_type(ANYELEMENTOID, + anyarray_type, + ANYARRAYOID); + if (have_anyarray_result && !OidIsValid(anyarray_type)) + anyarray_type = resolve_generic_type(ANYARRAYOID, + anyelement_type, + ANYELEMENTOID); + + /* And finally replace the output column types as needed */ + for (i = 0; i < numargs; i++) + { + switch (argtypes[i]) + { + case ANYELEMENTOID: + argtypes[i] = anyelement_type; + break; + case ANYARRAYOID: + argtypes[i] = anyarray_type; + break; + default: + break; + } + } + + return true; +} + +/* * get_type_func_class * Given the type OID, obtain its TYPEFUNC classification. * diff --git a/src/include/funcapi.h b/src/include/funcapi.h index c4d1809948d..2b4bfa28c4d 100644 --- a/src/include/funcapi.h +++ b/src/include/funcapi.h @@ -9,7 +9,7 @@ * * Copyright (c) 2002-2005, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.16 2005/03/31 22:46:24 tgl Exp $ + * $PostgreSQL: pgsql/src/include/funcapi.h,v 1.17 2005/04/05 06:22:15 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -167,6 +167,10 @@ extern TypeFuncClass get_func_result_type(Oid functionId, Oid *resultTypeId, TupleDesc *resultTupleDesc); +extern bool resolve_polymorphic_argtypes(int numargs, Oid *argtypes, + char *argmodes, + Node *call_expr); + extern TupleDesc build_function_result_tupdesc_d(Datum proallargtypes, Datum proargmodes, Datum proargnames); diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y index 210d1442774..e2b5c7aab14 100644 --- a/src/pl/plpgsql/src/gram.y +++ b/src/pl/plpgsql/src/gram.y @@ -4,7 +4,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.66 2005/02/22 07:18:24 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.67 2005/04/05 06:22:16 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -1052,28 +1052,41 @@ stmt_return : K_RETURN lno PLpgSQL_stmt_return *new; new = palloc0(sizeof(PLpgSQL_stmt_return)); + new->cmd_type = PLPGSQL_STMT_RETURN; + new->lineno = $2; new->expr = NULL; - new->retrecno = -1; - new->retrowno = -1; + new->retvarno = -1; if (plpgsql_curr_compile->fn_retset) { if (yylex() != ';') yyerror("RETURN cannot have a parameter in function returning set; use RETURN NEXT"); } + else if (plpgsql_curr_compile->out_param_varno >= 0) + { + if (yylex() != ';') + yyerror("RETURN cannot have a parameter in function with OUT parameters"); + new->retvarno = plpgsql_curr_compile->out_param_varno; + } + else if (plpgsql_curr_compile->fn_rettype == VOIDOID) + { + if (yylex() != ';') + yyerror("function returning void cannot specify RETURN expression"); + } else if (plpgsql_curr_compile->fn_retistuple) { switch (yylex()) { case K_NULL: + /* we allow this to support RETURN NULL in triggers */ break; case T_ROW: - new->retrowno = yylval.row->rowno; + new->retvarno = yylval.row->rowno; break; case T_RECORD: - new->retrecno = yylval.rec->recno; + new->retvarno = yylval.rec->recno; break; default: @@ -1083,11 +1096,6 @@ stmt_return : K_RETURN lno if (yylex() != ';') yyerror("RETURN must specify a record or row variable in function returning tuple"); } - else if (plpgsql_curr_compile->fn_rettype == VOIDOID) - { - if (yylex() != ';') - yyerror("function returning void cannot specify RETURN expression"); - } else { /* @@ -1098,9 +1106,6 @@ stmt_return : K_RETURN lno new->expr = plpgsql_read_expression(';', ";"); } - new->cmd_type = PLPGSQL_STMT_RETURN; - new->lineno = $2; - $$ = (PLpgSQL_stmt *)new; } ; @@ -1115,18 +1120,31 @@ stmt_return_next: K_RETURN_NEXT lno new = palloc0(sizeof(PLpgSQL_stmt_return_next)); new->cmd_type = PLPGSQL_STMT_RETURN_NEXT; new->lineno = $2; + new->expr = NULL; + new->retvarno = -1; - if (plpgsql_curr_compile->fn_retistuple) + if (plpgsql_curr_compile->out_param_varno >= 0) { - int tok = yylex(); + if (yylex() != ';') + yyerror("RETURN NEXT cannot have a parameter in function with OUT parameters"); + new->retvarno = plpgsql_curr_compile->out_param_varno; + } + else if (plpgsql_curr_compile->fn_retistuple) + { + switch (yylex()) + { + case T_ROW: + new->retvarno = yylval.row->rowno; + break; - if (tok == T_RECORD) - new->rec = yylval.rec; - else if (tok == T_ROW) - new->row = yylval.row; - else - yyerror("RETURN NEXT must specify a record or row variable in function returning tuple"); + case T_RECORD: + new->retvarno = yylval.rec->recno; + break; + default: + yyerror("RETURN NEXT must specify a record or row variable in function returning tuple"); + break; + } if (yylex() != ';') yyerror("RETURN NEXT must specify a record or row variable in function returning tuple"); } diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 4d23062f6e3..23109c949f7 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.85 2005/03/29 00:17:23 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.86 2005/04/05 06:22:16 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -49,6 +49,7 @@ #include "catalog/pg_class.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "funcapi.h" #include "nodes/makefuncs.h" #include "parser/gramparse.h" #include "parser/parse_type.h" @@ -122,13 +123,20 @@ static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo, HeapTuple procTup, PLpgSQL_func_hashkey *hashkey, bool forValidator); -static char **fetchArgNames(HeapTuple procTup, int nargs); -static PLpgSQL_row *build_row_var(Oid classOid); +static int fetchArgInfo(HeapTuple procTup, + Oid **p_argtypes, char ***p_argnames, + char **p_argmodes); +static PLpgSQL_row *build_row_from_class(Oid classOid); +static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars); static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod); static void compute_function_hashkey(FunctionCallInfo fcinfo, Form_pg_proc procStruct, PLpgSQL_func_hashkey *hashkey, bool forValidator); +static void plpgsql_resolve_polymorphic_argtypes(int numargs, + Oid *argtypes, char *argmodes, + Node *call_expr, bool forValidator, + const char *proname); static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key); static void plpgsql_HashTableInsert(PLpgSQL_function *function, PLpgSQL_func_hashkey *func_key); @@ -259,11 +267,17 @@ do_compile(FunctionCallInfo fcinfo, PLpgSQL_variable *var; PLpgSQL_rec *rec; int i; - int arg_varnos[FUNC_MAX_ARGS]; ErrorContextCallback plerrcontext; int parse_rc; Oid rettypeid; + int numargs; + int num_in_args; + int num_out_args; + Oid *argtypes; char **argnames; + char *argmodes; + int *in_arg_varnos = NULL; + PLpgSQL_variable **out_arg_variables; MemoryContext func_cxt; /* @@ -330,11 +344,111 @@ do_compile(FunctionCallInfo fcinfo, function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data); function->fn_functype = functype; function->fn_cxt = func_cxt; + function->out_param_varno = -1; /* set up for no OUT param */ switch (functype) { case T_FUNCTION: /* + * Fetch info about the procedure's parameters. Allocations + * aren't needed permanently, so make them in tmp cxt. + * + * We also need to resolve any polymorphic input or output + * argument types. In validation mode we won't be able to, + * so we arbitrarily assume we are dealing with integers. + */ + MemoryContextSwitchTo(compile_tmp_cxt); + + numargs = fetchArgInfo(procTup, &argtypes, &argnames, &argmodes); + + plpgsql_resolve_polymorphic_argtypes(numargs, argtypes, argmodes, + fcinfo->flinfo->fn_expr, + forValidator, + plpgsql_error_funcname); + + in_arg_varnos = (int *) palloc(numargs * sizeof(int)); + out_arg_variables = (PLpgSQL_variable **) palloc(numargs * sizeof(PLpgSQL_variable *)); + + MemoryContextSwitchTo(func_cxt); + + /* + * Create the variables for the procedure's parameters. + */ + num_in_args = num_out_args = 0; + for (i = 0; i < numargs; i++) + { + char buf[32]; + Oid argtypeid = argtypes[i]; + char argmode = argmodes ? argmodes[i] : PROARGMODE_IN; + PLpgSQL_type *argdtype; + PLpgSQL_variable *argvariable; + int argitemtype; + + /* Create $n name for variable */ + snprintf(buf, sizeof(buf), "$%d", i + 1); + + /* Create datatype info */ + argdtype = plpgsql_build_datatype(argtypeid, -1); + + /* Disallow pseudotype argument */ + /* (note we already replaced ANYARRAY/ANYELEMENT) */ + /* (build_variable would do this, but wrong message) */ + if (argdtype->ttype != PLPGSQL_TTYPE_SCALAR && + argdtype->ttype != PLPGSQL_TTYPE_ROW) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("plpgsql functions cannot take type %s", + format_type_be(argtypeid)))); + + /* Build variable and add to datum list */ + argvariable = plpgsql_build_variable(buf, 0, + argdtype, false); + + if (argvariable->dtype == PLPGSQL_DTYPE_VAR) + { + argitemtype = PLPGSQL_NSTYPE_VAR; + /* input argument vars are forced to be CONSTANT */ + if (argmode == PROARGMODE_IN) + ((PLpgSQL_var *) argvariable)->isconst = true; + } + else + { + Assert(argvariable->dtype == PLPGSQL_DTYPE_ROW); + argitemtype = PLPGSQL_NSTYPE_ROW; + } + + /* Remember arguments in appropriate arrays */ + if (argmode == PROARGMODE_IN || argmode == PROARGMODE_INOUT) + in_arg_varnos[num_in_args++] = argvariable->dno; + if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_INOUT) + out_arg_variables[num_out_args++] = argvariable; + + /* Add to namespace under the $n name */ + plpgsql_ns_additem(argitemtype, argvariable->dno, buf); + + /* If there's a name for the argument, make an alias */ + if (argnames && argnames[i][0] != '\0') + plpgsql_ns_additem(argitemtype, argvariable->dno, + argnames[i]); + } + + /* + * If there's just one OUT parameter, out_param_varno points + * directly to it. If there's more than one, build a row + * that holds all of them. + */ + if (num_out_args == 1) + function->out_param_varno = out_arg_variables[0]->dno; + else if (num_out_args > 1) + { + PLpgSQL_row *row = build_row_from_vars(out_arg_variables, + num_out_args); + + plpgsql_adddatum((PLpgSQL_datum *) row); + function->out_param_varno = row->rowno; + } + + /* * Check for a polymorphic returntype. If found, use the * actual returntype type from the caller's FuncExpr node, if * we have one. (In validation mode we arbitrarily assume we @@ -355,13 +469,15 @@ do_compile(FunctionCallInfo fcinfo, rettypeid = INT4OID; } else + { rettypeid = get_fn_expr_rettype(fcinfo->flinfo); - if (!OidIsValid(rettypeid)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("could not determine actual return type " - "for polymorphic function \"%s\"", - plpgsql_error_funcname))); + if (!OidIsValid(rettypeid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual return type " + "for polymorphic function \"%s\"", + plpgsql_error_funcname))); + } } /* @@ -410,10 +526,12 @@ do_compile(FunctionCallInfo fcinfo, /* * install $0 reference, but only for polymorphic return - * types + * types, and not when the return is specified through an + * output parameter. */ - if (procStruct->prorettype == ANYARRAYOID || - procStruct->prorettype == ANYELEMENTOID) + if ((procStruct->prorettype == ANYARRAYOID || + procStruct->prorettype == ANYELEMENTOID) && + num_out_args == 0) { (void) plpgsql_build_variable("$0", 0, build_datatype(typeTup, -1), @@ -421,72 +539,6 @@ do_compile(FunctionCallInfo fcinfo, } } ReleaseSysCache(typeTup); - - /* - * Create the variables for the procedure's - * parameters. Allocations aren't needed permanently, so - * make them in tmp cxt. - */ - MemoryContextSwitchTo(compile_tmp_cxt); - argnames = fetchArgNames(procTup, procStruct->pronargs); - MemoryContextSwitchTo(func_cxt); - - for (i = 0; i < procStruct->pronargs; i++) - { - char buf[32]; - Oid argtypeid; - PLpgSQL_type *argdtype; - PLpgSQL_variable *argvariable; - int argitemtype; - - /* Create $n name for variable */ - snprintf(buf, sizeof(buf), "$%d", i + 1); - - /* - * Since we already did the replacement of polymorphic - * argument types by actual argument types while computing - * the hashkey, we can just use those results. - */ - argtypeid = hashkey->argtypes[i]; - argdtype = plpgsql_build_datatype(argtypeid, -1); - - /* Disallow pseudotype argument */ - /* (note we already replaced ANYARRAY/ANYELEMENT) */ - /* (build_variable would do this, but wrong message) */ - if (argdtype->ttype != PLPGSQL_TTYPE_SCALAR && - argdtype->ttype != PLPGSQL_TTYPE_ROW) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("plpgsql functions cannot take type %s", - format_type_be(argtypeid)))); - - /* Build variable and add to datum list */ - argvariable = plpgsql_build_variable(buf, 0, - argdtype, false); - - if (argvariable->dtype == PLPGSQL_DTYPE_VAR) - { - /* argument vars are forced to be CONSTANT (why?) */ - ((PLpgSQL_var *) argvariable)->isconst = true; - argitemtype = PLPGSQL_NSTYPE_VAR; - } - else - { - Assert(argvariable->dtype == PLPGSQL_DTYPE_ROW); - argitemtype = PLPGSQL_NSTYPE_ROW; - } - - /* Remember datum number */ - arg_varnos[i] = argvariable->dno; - - /* Add to namespace under the $n name */ - plpgsql_ns_additem(argitemtype, argvariable->dno, buf); - - /* If there's a name for the argument, make an alias */ - if (argnames) - plpgsql_ns_additem(argitemtype, argvariable->dno, - argnames[i]); - } break; case T_TRIGGER: @@ -598,7 +650,7 @@ do_compile(FunctionCallInfo fcinfo, */ function->fn_nargs = procStruct->pronargs; for (i = 0; i < function->fn_nargs; i++) - function->fn_argvarnos[i] = arg_varnos[i]; + function->fn_argvarnos[i] = in_arg_varnos[i]; function->ndatums = plpgsql_nDatums; function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums); for (i = 0; i < plpgsql_nDatums; i++) @@ -660,40 +712,96 @@ plpgsql_compile_error_callback(void *arg) /* - * Fetch the argument names, if any, from the proargnames field of the - * pg_proc tuple. Results are palloc'd. + * Fetch info about the argument types, names, and IN/OUT modes from the + * pg_proc tuple. Return value is the number of arguments. + * Other results are palloc'd. */ -static char ** -fetchArgNames(HeapTuple procTup, int nargs) +static int +fetchArgInfo(HeapTuple procTup, Oid **p_argtypes, char ***p_argnames, + char **p_argmodes) { - Datum argnamesDatum; + Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); + Datum proallargtypes; + Datum proargmodes; + Datum proargnames; bool isNull; + ArrayType *arr; + int numargs; Datum *elems; int nelems; - char **result; int i; - if (nargs == 0) - return NULL; + /* First discover the total number of parameters and get their types */ + proallargtypes = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proallargtypes, + &isNull); + if (!isNull) + { + /* + * We expect the arrays to be 1-D arrays of the right types; verify + * that. For the OID and char arrays, we don't need to use + * deconstruct_array() since the array data is just going to look like + * a C array of values. + */ + arr = DatumGetArrayTypeP(proallargtypes); /* ensure not toasted */ + numargs = ARR_DIMS(arr)[0]; + if (ARR_NDIM(arr) != 1 || + numargs < 0 || + ARR_ELEMTYPE(arr) != OIDOID) + elog(ERROR, "proallargtypes is not a 1-D Oid array"); + Assert(numargs >= procStruct->pronargs); + *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid)); + memcpy(*p_argtypes, ARR_DATA_PTR(arr), + numargs * sizeof(Oid)); + } + else + { + /* If no proallargtypes, use proargtypes */ + numargs = procStruct->proargtypes.dim1; + Assert(numargs == procStruct->pronargs); + *p_argtypes = (Oid *) palloc(numargs * sizeof(Oid)); + memcpy(*p_argtypes, procStruct->proargtypes.values, + numargs * sizeof(Oid)); + } - argnamesDatum = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_proargnames, - &isNull); + /* Get argument names, if available */ + proargnames = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proargnames, + &isNull); if (isNull) - return NULL; - - deconstruct_array(DatumGetArrayTypeP(argnamesDatum), - TEXTOID, -1, false, 'i', - &elems, &nelems); - - if (nelems != nargs) /* should not happen */ - elog(ERROR, "proargnames must have the same number of elements as the function has arguments"); - - result = (char **) palloc(sizeof(char *) * nargs); + *p_argnames = NULL; + else + { + deconstruct_array(DatumGetArrayTypeP(proargnames), + TEXTOID, -1, false, 'i', + &elems, &nelems); + if (nelems != numargs) /* should not happen */ + elog(ERROR, "proargnames must have the same number of elements as the function has arguments"); + *p_argnames = (char **) palloc(sizeof(char *) * numargs); + for (i = 0; i < numargs; i++) + (*p_argnames)[i] = DatumGetCString(DirectFunctionCall1(textout, + elems[i])); + } - for (i = 0; i < nargs; i++) - result[i] = DatumGetCString(DirectFunctionCall1(textout, elems[i])); + /* Get argument modes, if available */ + proargmodes = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_proargmodes, + &isNull); + if (isNull) + *p_argmodes = NULL; + else + { + arr = DatumGetArrayTypeP(proargmodes); /* ensure not toasted */ + if (ARR_NDIM(arr) != 1 || + ARR_DIMS(arr)[0] != numargs || + ARR_ELEMTYPE(arr) != CHAROID) + elog(ERROR, "proargmodes is not a 1-D char array"); + *p_argmodes = (char *) palloc(numargs * sizeof(char)); + memcpy(*p_argmodes, ARR_DATA_PTR(arr), + numargs * sizeof(char)); + } - return result; + return numargs; } @@ -1449,7 +1557,7 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype, /* Composite type -- build a row variable */ PLpgSQL_row *row; - row = build_row_var(dtype->typrelid); + row = build_row_from_class(dtype->typrelid); row->dtype = PLPGSQL_DTYPE_ROW; row->refname = pstrdup(refname); @@ -1504,7 +1612,7 @@ plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype, * Build a row-variable data structure given the pg_class OID. */ static PLpgSQL_row * -build_row_var(Oid classOid) +build_row_from_class(Oid classOid) { PLpgSQL_row *row; Relation rel; @@ -1589,6 +1697,62 @@ build_row_var(Oid classOid) return row; } +/* + * Build a row-variable data structure given the component variables. + */ +static PLpgSQL_row * +build_row_from_vars(PLpgSQL_variable **vars, int numvars) +{ + PLpgSQL_row *row; + int i; + + row = palloc0(sizeof(PLpgSQL_row)); + row->dtype = PLPGSQL_DTYPE_ROW; + row->rowtupdesc = CreateTemplateTupleDesc(numvars, false); + row->nfields = numvars; + row->fieldnames = palloc(numvars * sizeof(char *)); + row->varnos = palloc(numvars * sizeof(int)); + + for (i = 0; i < numvars; i++) + { + PLpgSQL_variable *var = vars[i]; + Oid typoid = RECORDOID; + int32 typmod = -1; + + switch (var->dtype) + { + case PLPGSQL_DTYPE_VAR: + typoid = ((PLpgSQL_var *) var)->datatype->typoid; + typmod = ((PLpgSQL_var *) var)->datatype->atttypmod; + break; + + case PLPGSQL_DTYPE_REC: + break; + + case PLPGSQL_DTYPE_ROW: + if (((PLpgSQL_row *) var)->rowtupdesc) + { + typoid = ((PLpgSQL_row *) var)->rowtupdesc->tdtypeid; + typmod = ((PLpgSQL_row *) var)->rowtupdesc->tdtypmod; + } + break; + + default: + elog(ERROR, "unrecognized dtype: %d", var->dtype); + } + + row->fieldnames[i] = var->refname; + row->varnos[i] = var->dno; + + TupleDescInitEntry(row->rowtupdesc, i+1, + var->refname, + typoid, typmod, + 0); + } + + return row; +} + /* ---------- * plpgsql_parse_datatype Scanner found something that should @@ -1820,8 +1984,6 @@ compute_function_hashkey(FunctionCallInfo fcinfo, PLpgSQL_func_hashkey *hashkey, bool forValidator) { - int i; - /* Make sure any unused bytes of the struct are zero */ MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey)); @@ -1840,42 +2002,64 @@ compute_function_hashkey(FunctionCallInfo fcinfo, hashkey->trigrelOid = RelationGetRelid(trigdata->tg_relation); } - /* get the argument types */ - for (i = 0; i < procStruct->pronargs; i++) + if (procStruct->pronargs > 0) { - Oid argtypeid = procStruct->proargtypes.values[i]; + /* get the argument types */ + memcpy(hashkey->argtypes, procStruct->proargtypes.values, + procStruct->pronargs * sizeof(Oid)); + + /* resolve any polymorphic argument types */ + plpgsql_resolve_polymorphic_argtypes(procStruct->pronargs, + hashkey->argtypes, + NULL, + fcinfo->flinfo->fn_expr, + forValidator, + NameStr(procStruct->proname)); + } +} - /* - * Check for polymorphic arguments. If found, use the actual - * parameter type from the caller's FuncExpr node, if we have one. - * (In validation mode we arbitrarily assume we are dealing with - * integers. This lets us build a valid, if possibly useless, - * function hashtable entry.) - * - * We can support arguments of type ANY the same way as normal - * polymorphic arguments. - */ - if (argtypeid == ANYARRAYOID || argtypeid == ANYELEMENTOID || - argtypeid == ANYOID) +/* + * This is the same as the standard resolve_polymorphic_argtypes() function, + * but with a special case for validation: assume that polymorphic arguments + * are integer or integer-array. Also, we go ahead and report the error + * if we can't resolve the types. + */ +static void +plpgsql_resolve_polymorphic_argtypes(int numargs, + Oid *argtypes, char *argmodes, + Node *call_expr, bool forValidator, + const char *proname) +{ + int i; + + if (!forValidator) + { + /* normal case, pass to standard routine */ + if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes, + call_expr)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("could not determine actual argument " + "type for polymorphic function \"%s\"", + proname))); + } + else + { + /* special validation case */ + for (i = 0; i < numargs; i++) { - if (forValidator) + switch (argtypes[i]) { - if (argtypeid == ANYARRAYOID) - argtypeid = INT4ARRAYOID; - else - argtypeid = INT4OID; + case ANYELEMENTOID: + argtypes[i] = INT4OID; + break; + case ANYARRAYOID: + argtypes[i] = INT4ARRAYOID; + break; + default: + break; } - else - argtypeid = get_fn_expr_argtype(fcinfo->flinfo, i); - if (!OidIsValid(argtypeid)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("could not determine actual argument " - "type for polymorphic function \"%s\"", - NameStr(procStruct->proname)))); } - - hashkey->argtypes[i] = argtypeid; } } diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 9fbe2897210..4454f2834a1 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.133 2005/03/25 01:45:42 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.134 2005/04/05 06:22:16 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -75,8 +75,6 @@ static PLpgSQL_expr *active_simple_exprs = NULL; ************************************************************/ static void plpgsql_exec_error_callback(void *arg); static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum); -static PLpgSQL_var *copy_var(PLpgSQL_var *var); -static PLpgSQL_rec *copy_rec(PLpgSQL_rec *rec); static int exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block); @@ -212,11 +210,11 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo) * Make local execution copies of all the datums */ estate.err_text = gettext_noop("during initialization of execution state"); - for (i = 0; i < func->ndatums; i++) + for (i = 0; i < estate.ndatums; i++) estate.datums[i] = copy_plpgsql_datum(func->datums[i]); /* - * Store the actual call argument values into the variables + * Store the actual call argument values into the appropriate variables */ estate.err_text = gettext_noop("while storing call arguments into local variables"); for (i = 0; i < func->fn_nargs; i++) @@ -273,36 +271,6 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo) } /* - * Initialize the other variables to NULL values for now. The default - * values are set when the blocks are entered. - */ - estate.err_text = gettext_noop("while initializing local variables to NULL"); - for (i = estate.found_varno; i < estate.ndatums; i++) - { - switch (estate.datums[i]->dtype) - { - case PLPGSQL_DTYPE_VAR: - { - PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i]; - - var->value = 0; - var->isnull = true; - var->freeval = false; - } - break; - - case PLPGSQL_DTYPE_ROW: - case PLPGSQL_DTYPE_REC: - case PLPGSQL_DTYPE_RECFIELD: - case PLPGSQL_DTYPE_ARRAYELEM: - break; - - default: - elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype); - } - } - - /* * Set the magic variable FOUND to false */ exec_set_found(&estate, false); @@ -445,7 +413,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func, * Make local execution copies of all the datums */ estate.err_text = gettext_noop("during initialization of execution state"); - for (i = 0; i < func->ndatums; i++) + for (i = 0; i < estate.ndatums; i++) estate.datums[i] = copy_plpgsql_datum(func->datums[i]); /* @@ -551,7 +519,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func, var->freeval = false; /* - * Store the actual call argument values into the special execution + * Store the trigger argument values into the special execution * state variables */ estate.err_text = gettext_noop("while storing call arguments into local variables"); @@ -567,37 +535,6 @@ plpgsql_exec_trigger(PLpgSQL_function *func, } /* - * Initialize the other variables to NULL values for now. The default - * values are set when the blocks are entered. - */ - estate.err_text = gettext_noop("while initializing local variables to NULL"); - for (i = estate.found_varno; i < estate.ndatums; i++) - { - switch (estate.datums[i]->dtype) - { - case PLPGSQL_DTYPE_VAR: - { - PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[i]; - - var->value = 0; - var->isnull = true; - var->freeval = false; - } - break; - - case PLPGSQL_DTYPE_ROW: - case PLPGSQL_DTYPE_REC: - case PLPGSQL_DTYPE_RECFIELD: - case PLPGSQL_DTYPE_ARRAYELEM: - case PLPGSQL_DTYPE_TRIGARG: - break; - - default: - elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype); - } - } - - /* * Set the magic variable FOUND to false */ exec_set_found(&estate, false); @@ -710,67 +647,65 @@ plpgsql_exec_error_callback(void *arg) /* ---------- - * Support functions for copying local execution variables - * - * NB: this is not a generic copy operation because it assumes that any - * pass-by-ref original values will live as long as the copy is needed. + * Support function for initializing local execution variables * ---------- */ static PLpgSQL_datum * copy_plpgsql_datum(PLpgSQL_datum *datum) { - PLpgSQL_datum *result = NULL; + PLpgSQL_datum *result; switch (datum->dtype) { case PLPGSQL_DTYPE_VAR: - result = (PLpgSQL_datum *) copy_var((PLpgSQL_var *) datum); - break; + { + PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var)); + + memcpy(new, datum, sizeof(PLpgSQL_var)); + /* Ensure the value is null (possibly not needed?) */ + new->value = 0; + new->isnull = true; + new->freeval = false; + + result = (PLpgSQL_datum *) new; + } + break; case PLPGSQL_DTYPE_REC: - result = (PLpgSQL_datum *) copy_rec((PLpgSQL_rec *) datum); - break; + { + PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec)); + + memcpy(new, datum, sizeof(PLpgSQL_rec)); + /* Ensure the value is null (possibly not needed?) */ + new->tup = NULL; + new->tupdesc = NULL; + new->freetup = false; + new->freetupdesc = false; + + result = (PLpgSQL_datum *) new; + } + break; case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_RECFIELD: case PLPGSQL_DTYPE_ARRAYELEM: case PLPGSQL_DTYPE_TRIGARG: + /* + * These datum records are read-only at runtime, so no need + * to copy them + */ result = datum; break; default: elog(ERROR, "unrecognized dtype: %d", datum->dtype); + result = NULL; /* keep compiler quiet */ + break; } return result; } -static PLpgSQL_var * -copy_var(PLpgSQL_var *var) -{ - PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var)); - - memcpy(new, var, sizeof(PLpgSQL_var)); - new->freeval = false; - - return new; -} - - -static PLpgSQL_rec * -copy_rec(PLpgSQL_rec *rec) -{ - PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec)); - - memcpy(new, rec, sizeof(PLpgSQL_rec)); - new->tup = NULL; - new->tupdesc = NULL; - new->freetup = false; - new->freetupdesc = false; - - return new; -} - static bool exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond) @@ -1682,43 +1617,64 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) if (estate->retisset) return PLPGSQL_RC_RETURN; - if (estate->retistuple) + /* initialize for null result (possibly a tuple) */ + estate->retval = (Datum) 0; + estate->rettupdesc = NULL; + estate->retisnull = true; + + if (stmt->retvarno >= 0) { - /* initialize for null result tuple */ - estate->retval = (Datum) 0; - estate->rettupdesc = NULL; - estate->retisnull = true; + PLpgSQL_datum *retvar = estate->datums[stmt->retvarno]; - if (stmt->retrecno >= 0) + switch (retvar->dtype) { - PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->retrecno]); - - if (HeapTupleIsValid(rec->tup)) + case PLPGSQL_DTYPE_VAR: { - estate->retval = (Datum) rec->tup; - estate->rettupdesc = rec->tupdesc; - estate->retisnull = false; + PLpgSQL_var *var = (PLpgSQL_var *) retvar; + + estate->retval = var->value; + estate->retisnull = var->isnull; + estate->rettype = var->datatype->typoid; } - return PLPGSQL_RC_RETURN; - } + break; - if (stmt->retrowno >= 0) - { - PLpgSQL_row *row = (PLpgSQL_row *) (estate->datums[stmt->retrowno]); + case PLPGSQL_DTYPE_REC: + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar; - if (row->rowtupdesc) /* should always be true here */ + if (HeapTupleIsValid(rec->tup)) + { + estate->retval = (Datum) rec->tup; + estate->rettupdesc = rec->tupdesc; + estate->retisnull = false; + } + } + break; + + case PLPGSQL_DTYPE_ROW: { + PLpgSQL_row *row = (PLpgSQL_row *) retvar; + + Assert(row->rowtupdesc); estate->retval = (Datum) make_tuple_from_row(estate, row, - row->rowtupdesc); - if (estate->retval == (Datum) NULL) /* should not happen */ + row->rowtupdesc); + if (estate->retval == (Datum) NULL) /* should not happen */ elog(ERROR, "row not compatible with its own tupdesc"); estate->rettupdesc = row->rowtupdesc; estate->retisnull = false; } - return PLPGSQL_RC_RETURN; + break; + + default: + elog(ERROR, "unrecognized dtype: %d", retvar->dtype); } - if (stmt->expr != NULL) + return PLPGSQL_RC_RETURN; + } + + if (stmt->expr != NULL) + { + if (estate->retistuple) { exec_run_select(estate, stmt->expr, 1, NULL); if (estate->eval_processed > 0) @@ -1728,24 +1684,23 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt) estate->retisnull = false; } } - return PLPGSQL_RC_RETURN; + else + { + /* Normal case for scalar results */ + estate->retval = exec_eval_expr(estate, stmt->expr, + &(estate->retisnull), + &(estate->rettype)); + } } if (estate->fn_rettype == VOIDOID) { /* Special hack for function returning VOID */ - Assert(stmt->expr == NULL); + Assert(stmt->retvarno < 0 && stmt->expr == NULL); estate->retval = (Datum) 0; estate->retisnull = false; estate->rettype = VOIDOID; } - else - { - /* Normal case for scalar results */ - estate->retval = exec_eval_expr(estate, stmt->expr, - &(estate->retisnull), - &(estate->rettype)); - } return PLPGSQL_RC_RETURN; } @@ -1777,37 +1732,78 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, tupdesc = estate->rettupdesc; natts = tupdesc->natts; - if (stmt->rec) + if (stmt->retvarno >= 0) { - PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]); + PLpgSQL_datum *retvar = estate->datums[stmt->retvarno]; - if (!HeapTupleIsValid(rec->tup)) - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("record \"%s\" is not assigned yet", - rec->refname), - errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); - if (!compatible_tupdesc(tupdesc, rec->tupdesc)) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("wrong record type supplied in RETURN NEXT"))); - tuple = rec->tup; - } - else if (stmt->row) - { - tuple = make_tuple_from_row(estate, stmt->row, tupdesc); - if (tuple == NULL) - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("wrong record type supplied in RETURN NEXT"))); - free_tuple = true; + switch (retvar->dtype) + { + case PLPGSQL_DTYPE_VAR: + { + PLpgSQL_var *var = (PLpgSQL_var *) retvar; + Datum retval = var->value; + bool isNull = var->isnull; + + if (natts != 1) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("wrong result type supplied in RETURN NEXT"))); + + /* coerce type if needed */ + retval = exec_simple_cast_value(retval, + var->datatype->typoid, + tupdesc->attrs[0]->atttypid, + tupdesc->attrs[0]->atttypmod, + &isNull); + + tuple = heap_form_tuple(tupdesc, &retval, &isNull); + + free_tuple = true; + } + break; + + case PLPGSQL_DTYPE_REC: + { + PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar; + + if (!HeapTupleIsValid(rec->tup)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("record \"%s\" is not assigned yet", + rec->refname), + errdetail("The tuple structure of a not-yet-assigned record is indeterminate."))); + if (!compatible_tupdesc(tupdesc, rec->tupdesc)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("wrong record type supplied in RETURN NEXT"))); + tuple = rec->tup; + } + break; + + case PLPGSQL_DTYPE_ROW: + { + PLpgSQL_row *row = (PLpgSQL_row *) retvar; + + tuple = make_tuple_from_row(estate, row, tupdesc); + if (tuple == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("wrong record type supplied in RETURN NEXT"))); + free_tuple = true; + } + break; + + default: + elog(ERROR, "unrecognized dtype: %d", retvar->dtype); + tuple = NULL; /* keep compiler quiet */ + break; + } } else if (stmt->expr) { Datum retval; bool isNull; Oid rettype; - char nullflag; if (natts != 1) ereport(ERROR, @@ -1826,9 +1822,7 @@ exec_stmt_return_next(PLpgSQL_execstate *estate, tupdesc->attrs[0]->atttypmod, &isNull); - nullflag = isNull ? 'n' : ' '; - - tuple = heap_formtuple(tupdesc, &retval, &nullflag); + tuple = heap_form_tuple(tupdesc, &retval, &isNull); free_tuple = true; diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 91d7e1558e3..475d2224bff 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.39 2005/02/22 07:18:24 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.40 2005/04/05 06:22:16 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -857,14 +857,12 @@ dump_return(PLpgSQL_stmt_return *stmt) { dump_ind(); printf("RETURN "); - if (stmt->retrecno >= 0) - printf("record %d", stmt->retrecno); - else if (stmt->retrowno >= 0) - printf("row %d", stmt->retrowno); - else if (stmt->expr == NULL) - printf("NULL"); - else + if (stmt->retvarno >= 0) + printf("variable %d", stmt->retvarno); + else if (stmt->expr != NULL) dump_expr(stmt->expr); + else + printf("NULL"); printf("\n"); } @@ -873,12 +871,12 @@ dump_return_next(PLpgSQL_stmt_return_next *stmt) { dump_ind(); printf("RETURN NEXT "); - if (stmt->rec != NULL) - printf("target = %d %s\n", stmt->rec->recno, stmt->rec->refname); - else if (stmt->row != NULL) - printf("target = %d %s\n", stmt->row->rowno, stmt->row->refname); + if (stmt->retvarno >= 0) + printf("variable %d", stmt->retvarno); else if (stmt->expr != NULL) dump_expr(stmt->expr); + else + printf("NULL"); printf("\n"); } diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index df38351a23e..80bcd9e8fa6 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -3,7 +3,7 @@ * procedural language * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.57 2005/02/22 07:18:24 neilc Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.58 2005/04/05 06:22:16 tgl Exp $ * * This software is copyrighted by Jan Wieck - Hamburg. * @@ -491,17 +491,15 @@ typedef struct int cmd_type; int lineno; PLpgSQL_expr *expr; - int retrecno; - int retrowno; + int retvarno; } PLpgSQL_stmt_return; typedef struct { /* RETURN NEXT statement */ int cmd_type; int lineno; - PLpgSQL_rec *rec; - PLpgSQL_row *row; PLpgSQL_expr *expr; + int retvarno; } PLpgSQL_stmt_return_next; typedef struct @@ -572,6 +570,7 @@ typedef struct PLpgSQL_function int fn_nargs; int fn_argvarnos[FUNC_MAX_ARGS]; + int out_param_varno; int found_varno; int new_varno; int old_varno; diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index 7fec95a2794..ee1c52dfa46 100644 --- a/src/test/regress/expected/plpgsql.out +++ b/src/test/regress/expected/plpgsql.out @@ -1739,6 +1739,125 @@ SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text); (1 row) -- +-- Test handling of OUT parameters, including polymorphic cases +-- +-- wrong way to do it: +create function f1(in i int, out j int) returns int as $$ +begin + return i+1; +end$$ language plpgsql; +ERROR: RETURN cannot have a parameter in function with OUT parameters at or near "i" at character 74 +LINE 3: return i+1; + ^ +create function f1(in i int, out j int) as $$ +begin + j := i+1; + return; +end$$ language plpgsql; +select f1(42); + f1 +---- + 43 +(1 row) + +select * from f1(42); + f1 +---- + 43 +(1 row) + +create or replace function f1(inout i int) as $$ +begin + i := i+1; + return; +end$$ language plpgsql; +select f1(42); + f1 +---- + 43 +(1 row) + +select * from f1(42); + f1 +---- + 43 +(1 row) + +drop function f1(int); +create function f1(in i int, out j int) returns setof int as $$ +begin + j := i+1; + return next; + j := i+2; + return next; + return; +end$$ language plpgsql; +select * from f1(42); + f1 +---- + 43 + 44 +(2 rows) + +drop function f1(int); +create function f1(in i int, out j int, out k text) as $$ +begin + j := i; + j := j+1; + k := 'foo'; + return; +end$$ language plpgsql; +select f1(42); + f1 +---------- + (43,foo) +(1 row) + +select * from f1(42); + j | k +----+----- + 43 | foo +(1 row) + +drop function f1(int); +create function f1(in i int, out j int, out k text) returns setof record as $$ +begin + j := i+1; + k := 'foo'; + return next; + j := j+1; + k := 'foot'; + return next; + return; +end$$ language plpgsql; +select * from f1(42); + j | k +----+------ + 43 | foo + 44 | foot +(2 rows) + +drop function f1(int); +create function dup(in i anyelement, out j anyelement, out k anyarray) as $$ +begin + j := i; + k := array[j,j]; + return; +end$$ language plpgsql; +select * from dup(42); + j | k +----+--------- + 42 | {42,42} +(1 row) + +select * from dup('foo'::text); + j | k +-----+----------- + foo | {foo,foo} +(1 row) + +drop function dup(anyelement); +-- -- test PERFORM -- create table perform_test ( diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 607b7f28860..e8079615f1e 100644 --- a/src/test/regress/sql/plpgsql.sql +++ b/src/test/regress/sql/plpgsql.sql @@ -1561,6 +1561,89 @@ SELECT * FROM test_ret_rec_dyn(1500) AS (a int, b int, c int); SELECT * FROM test_ret_rec_dyn(5) AS (a int, b numeric, c text); -- +-- Test handling of OUT parameters, including polymorphic cases +-- + +-- wrong way to do it: +create function f1(in i int, out j int) returns int as $$ +begin + return i+1; +end$$ language plpgsql; + +create function f1(in i int, out j int) as $$ +begin + j := i+1; + return; +end$$ language plpgsql; + +select f1(42); +select * from f1(42); + +create or replace function f1(inout i int) as $$ +begin + i := i+1; + return; +end$$ language plpgsql; + +select f1(42); +select * from f1(42); + +drop function f1(int); + +create function f1(in i int, out j int) returns setof int as $$ +begin + j := i+1; + return next; + j := i+2; + return next; + return; +end$$ language plpgsql; + +select * from f1(42); + +drop function f1(int); + +create function f1(in i int, out j int, out k text) as $$ +begin + j := i; + j := j+1; + k := 'foo'; + return; +end$$ language plpgsql; + +select f1(42); +select * from f1(42); + +drop function f1(int); + +create function f1(in i int, out j int, out k text) returns setof record as $$ +begin + j := i+1; + k := 'foo'; + return next; + j := j+1; + k := 'foot'; + return next; + return; +end$$ language plpgsql; + +select * from f1(42); + +drop function f1(int); + +create function dup(in i anyelement, out j anyelement, out k anyarray) as $$ +begin + j := i; + k := array[j,j]; + return; +end$$ language plpgsql; + +select * from dup(42); +select * from dup('foo'::text); + +drop function dup(anyelement); + +-- -- test PERFORM -- @@ -1917,4 +2000,4 @@ end;$$ language plpgsql; create function void_return_expr() returns void as $$ begin return 5; -end;$$ language plpgsql;
\ No newline at end of file +end;$$ language plpgsql; |