/*------------------------------------------------------------------------- * * parse_param.c * handle parameters in parser * * This code covers two cases that are used within the core backend: * * a fixed list of parameters with known types * * an expandable list of parameters whose types can optionally * be determined from context * In both cases, only explicit $n references (ParamRef nodes) are supported. * * Note that other approaches to parameters are possible using the parser * hooks defined in ParseState. * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/parser/parse_param.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include #include "catalog/pg_type.h" #include "nodes/nodeFuncs.h" #include "parser/parse_param.h" #include "utils/builtins.h" #include "utils/lsyscache.h" typedef struct FixedParamState { const Oid *paramTypes; /* array of parameter type OIDs */ int numParams; /* number of array entries */ } FixedParamState; /* * In the varparams case, the caller-supplied OID array (if any) can be * re-palloc'd larger at need. A zero array entry means that parameter number * hasn't been seen, while UNKNOWNOID means the parameter has been used but * its type is not yet known. */ typedef struct VarParamState { Oid **paramTypes; /* array of parameter type OIDs */ int *numParams; /* number of array entries */ } VarParamState; static Node *fixed_paramref_hook(ParseState *pstate, ParamRef *pref); static Node *variable_paramref_hook(ParseState *pstate, ParamRef *pref); static Node *variable_coerce_param_hook(ParseState *pstate, Param *param, Oid targetTypeId, int32 targetTypeMod, int location); static bool check_parameter_resolution_walker(Node *node, ParseState *pstate); static bool query_contains_extern_params_walker(Node *node, void *context); /* * Set up to process a query containing references to fixed parameters. */ void setup_parse_fixed_parameters(ParseState *pstate, const Oid *paramTypes, int numParams) { FixedParamState *parstate = palloc(sizeof(FixedParamState)); parstate->paramTypes = paramTypes; parstate->numParams = numParams; pstate->p_ref_hook_state = (void *) parstate; pstate->p_paramref_hook = fixed_paramref_hook; /* no need to use p_coerce_param_hook */ } /* * Set up to process a query containing references to variable parameters. */ void setup_parse_variable_parameters(ParseState *pstate, Oid **paramTypes, int *numParams) { VarParamState *parstate = palloc(sizeof(VarParamState)); parstate->paramTypes = paramTypes; parstate->numParams = numParams; pstate->p_ref_hook_state = (void *) parstate; pstate->p_paramref_hook = variable_paramref_hook; pstate->p_coerce_param_hook = variable_coerce_param_hook; } /* * Transform a ParamRef using fixed parameter types. */ static Node * fixed_paramref_hook(ParseState *pstate, ParamRef *pref) { FixedParamState *parstate = (FixedParamState *) pstate->p_ref_hook_state; int paramno = pref->number; Param *param; /* Check parameter number is valid */ if (paramno <= 0 || paramno > parstate->numParams || !OidIsValid(parstate->paramTypes[paramno - 1])) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("there is no parameter $%d", paramno), parser_errposition(pstate, pref->location))); param = makeNode(Param); param->paramkind = PARAM_EXTERN; param->paramid = paramno; param->paramtype = parstate->paramTypes[paramno - 1]; param->paramtypmod = -1; param->paramcollid = get_typcollation(param->paramtype); param->location = pref->location; return (Node *) param; } /* * Transform a ParamRef using variable parameter types. * * The only difference here is we must enlarge the parameter type array * as needed. */ static Node * variable_paramref_hook(ParseState *pstate, ParamRef *pref) { VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; int paramno = pref->number; Oid *pptype; Param *param; /* Check parameter number is in range */ if (paramno <= 0 || paramno > INT_MAX / sizeof(Oid)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("there is no parameter $%d", paramno), parser_errposition(pstate, pref->location))); if (paramno > *parstate->numParams) { /* Need to enlarge param array */ if (*parstate->paramTypes) *parstate->paramTypes = repalloc0_array(*parstate->paramTypes, Oid, *parstate->numParams, paramno); else *parstate->paramTypes = palloc0_array(Oid, paramno); *parstate->numParams = paramno; } /* Locate param's slot in array */ pptype = &(*parstate->paramTypes)[paramno - 1]; /* If not seen before, initialize to UNKNOWN type */ if (*pptype == InvalidOid) *pptype = UNKNOWNOID; /* * If the argument is of type void and it's procedure call, interpret it * as unknown. This allows the JDBC driver to not have to distinguish * function and procedure calls. See also another component of this hack * in ParseFuncOrColumn(). */ if (*pptype == VOIDOID && pstate->p_expr_kind == EXPR_KIND_CALL_ARGUMENT) *pptype = UNKNOWNOID; param = makeNode(Param); param->paramkind = PARAM_EXTERN; param->paramid = paramno; param->paramtype = *pptype; param->paramtypmod = -1; param->paramcollid = get_typcollation(param->paramtype); param->location = pref->location; return (Node *) param; } /* * Coerce a Param to a query-requested datatype, in the varparams case. */ static Node * variable_coerce_param_hook(ParseState *pstate, Param *param, Oid targetTypeId, int32 targetTypeMod, int location) { if (param->paramkind == PARAM_EXTERN && param->paramtype == UNKNOWNOID) { /* * Input is a Param of previously undetermined type, and we want to * update our knowledge of the Param's type. */ VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; Oid *paramTypes = *parstate->paramTypes; int paramno = param->paramid; if (paramno <= 0 || /* shouldn't happen, but... */ paramno > *parstate->numParams) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("there is no parameter $%d", paramno), parser_errposition(pstate, param->location))); if (paramTypes[paramno - 1] == UNKNOWNOID) { /* We've successfully resolved the type */ paramTypes[paramno - 1] = targetTypeId; } else if (paramTypes[paramno - 1] == targetTypeId) { /* We previously resolved the type, and it matches */ } else { /* Oops */ ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_PARAMETER), errmsg("inconsistent types deduced for parameter $%d", paramno), errdetail("%s versus %s", format_type_be(paramTypes[paramno - 1]), format_type_be(targetTypeId)), parser_errposition(pstate, param->location))); } param->paramtype = targetTypeId; /* * Note: it is tempting here to set the Param's paramtypmod to * targetTypeMod, but that is probably unwise because we have no * infrastructure that enforces that the value delivered for a Param * will match any particular typmod. Leaving it -1 ensures that a * run-time length check/coercion will occur if needed. */ param->paramtypmod = -1; /* * This module always sets a Param's collation to be the default for * its datatype. If that's not what you want, you should be using the * more general parser substitution hooks. */ param->paramcollid = get_typcollation(param->paramtype); /* Use the leftmost of the param's and coercion's locations */ if (location >= 0 && (param->location < 0 || location < param->location)) param->location = location; return (Node *) param; } /* Else signal to proceed with normal coercion */ return NULL; } /* * Check for consistent assignment of variable parameters after completion * of parsing with parse_variable_parameters. * * Note: this code intentionally does not check that all parameter positions * were used, nor that all got non-UNKNOWN types assigned. Caller of parser * should enforce that if it's important. */ void check_variable_parameters(ParseState *pstate, Query *query) { VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; /* If numParams is zero then no Params were generated, so no work */ if (*parstate->numParams > 0) (void) query_tree_walker(query, check_parameter_resolution_walker, (void *) pstate, 0); } /* * Traverse a fully-analyzed tree to verify that parameter symbols * match their types. We need this because some Params might still * be UNKNOWN, if there wasn't anything to force their coercion, * and yet other instances seen later might have gotten coerced. */ static bool check_parameter_resolution_walker(Node *node, ParseState *pstate) { if (node == NULL) return false; if (IsA(node, Param)) { Param *param = (Param *) node; if (param->paramkind == PARAM_EXTERN) { VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; int paramno = param->paramid; if (paramno <= 0 || /* shouldn't happen, but... */ paramno > *parstate->numParams) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("there is no parameter $%d", paramno), parser_errposition(pstate, param->location))); if (param->paramtype != (*parstate->paramTypes)[paramno - 1]) ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_PARAMETER), errmsg("could not determine data type of parameter $%d", paramno), parser_errposition(pstate, param->location))); } return false; } if (IsA(node, Query)) { /* Recurse into RTE subquery or not-yet-planned sublink subquery */ return query_tree_walker((Query *) node, check_parameter_resolution_walker, (void *) pstate, 0); } return expression_tree_walker(node, check_parameter_resolution_walker, (void *) pstate); } /* * Check to see if a fully-parsed query tree contains any PARAM_EXTERN Params. */ bool query_contains_extern_params(Query *query) { return query_tree_walker(query, query_contains_extern_params_walker, NULL, 0); } static bool query_contains_extern_params_walker(Node *node, void *context) { if (node == NULL) return false; if (IsA(node, Param)) { Param *param = (Param *) node; if (param->paramkind == PARAM_EXTERN) return true; return false; } if (IsA(node, Query)) { /* Recurse into RTE subquery or not-yet-planned sublink subquery */ return query_tree_walker((Query *) node, query_contains_extern_params_walker, context, 0); } return expression_tree_walker(node, query_contains_extern_params_walker, context); }