| /*------------------------------------------------------------------------- |
| * |
| * 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-2021, 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 <limits.h> |
| |
| #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 |
| { |
| 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 |
| parse_fixed_parameters(ParseState *pstate, |
| 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 |
| 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 = (Oid *) repalloc(*parstate->paramTypes, |
| paramno * sizeof(Oid)); |
| else |
| *parstate->paramTypes = (Oid *) palloc(paramno * sizeof(Oid)); |
| /* Zero out the previously-unreferenced slots */ |
| MemSet(*parstate->paramTypes + *parstate->numParams, |
| 0, |
| (paramno - *parstate->numParams) * sizeof(Oid)); |
| *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); |
| } |