mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +03:00
Switch to 2.1 version of pg_bsd_indent. This formats multiline function declarations "correctly", that is with additional lines of parameter declarations indented to match where the first line's left parenthesis is. Discussion: https://postgr.es/m/CAEepm=0P3FeTXRcU5B2W3jv3PgRVZ-kGUXLGfd42FFhUROO3ug@mail.gmail.com
355 lines
10 KiB
C
355 lines
10 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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-2019, 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;
|
|
|
|
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);
|
|
}
|