/src/postgres/src/backend/parser/parse_param.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * parse_param.c |
4 | | * handle parameters in parser |
5 | | * |
6 | | * This code covers two cases that are used within the core backend: |
7 | | * * a fixed list of parameters with known types |
8 | | * * an expandable list of parameters whose types can optionally |
9 | | * be determined from context |
10 | | * In both cases, only explicit $n references (ParamRef nodes) are supported. |
11 | | * |
12 | | * Note that other approaches to parameters are possible using the parser |
13 | | * hooks defined in ParseState. |
14 | | * |
15 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
16 | | * Portions Copyright (c) 1994, Regents of the University of California |
17 | | * |
18 | | * |
19 | | * IDENTIFICATION |
20 | | * src/backend/parser/parse_param.c |
21 | | * |
22 | | *------------------------------------------------------------------------- |
23 | | */ |
24 | | |
25 | | #include "postgres.h" |
26 | | |
27 | | #include <limits.h> |
28 | | |
29 | | #include "catalog/pg_type.h" |
30 | | #include "nodes/nodeFuncs.h" |
31 | | #include "parser/parse_param.h" |
32 | | #include "utils/builtins.h" |
33 | | #include "utils/lsyscache.h" |
34 | | #include "utils/memutils.h" |
35 | | |
36 | | |
37 | | typedef struct FixedParamState |
38 | | { |
39 | | const Oid *paramTypes; /* array of parameter type OIDs */ |
40 | | int numParams; /* number of array entries */ |
41 | | } FixedParamState; |
42 | | |
43 | | /* |
44 | | * In the varparams case, the caller-supplied OID array (if any) can be |
45 | | * re-palloc'd larger at need. A zero array entry means that parameter number |
46 | | * hasn't been seen, while UNKNOWNOID means the parameter has been used but |
47 | | * its type is not yet known. |
48 | | */ |
49 | | typedef struct VarParamState |
50 | | { |
51 | | Oid **paramTypes; /* array of parameter type OIDs */ |
52 | | int *numParams; /* number of array entries */ |
53 | | } VarParamState; |
54 | | |
55 | | static Node *fixed_paramref_hook(ParseState *pstate, ParamRef *pref); |
56 | | static Node *variable_paramref_hook(ParseState *pstate, ParamRef *pref); |
57 | | static Node *variable_coerce_param_hook(ParseState *pstate, Param *param, |
58 | | Oid targetTypeId, int32 targetTypeMod, |
59 | | int location); |
60 | | static bool check_parameter_resolution_walker(Node *node, ParseState *pstate); |
61 | | static bool query_contains_extern_params_walker(Node *node, void *context); |
62 | | |
63 | | |
64 | | /* |
65 | | * Set up to process a query containing references to fixed parameters. |
66 | | */ |
67 | | void |
68 | | setup_parse_fixed_parameters(ParseState *pstate, |
69 | | const Oid *paramTypes, int numParams) |
70 | 0 | { |
71 | 0 | FixedParamState *parstate = palloc_object(FixedParamState); |
72 | |
|
73 | 0 | parstate->paramTypes = paramTypes; |
74 | 0 | parstate->numParams = numParams; |
75 | 0 | pstate->p_ref_hook_state = parstate; |
76 | 0 | pstate->p_paramref_hook = fixed_paramref_hook; |
77 | | /* no need to use p_coerce_param_hook */ |
78 | 0 | } |
79 | | |
80 | | /* |
81 | | * Set up to process a query containing references to variable parameters. |
82 | | */ |
83 | | void |
84 | | setup_parse_variable_parameters(ParseState *pstate, |
85 | | Oid **paramTypes, int *numParams) |
86 | 0 | { |
87 | 0 | VarParamState *parstate = palloc_object(VarParamState); |
88 | |
|
89 | 0 | parstate->paramTypes = paramTypes; |
90 | 0 | parstate->numParams = numParams; |
91 | 0 | pstate->p_ref_hook_state = parstate; |
92 | 0 | pstate->p_paramref_hook = variable_paramref_hook; |
93 | 0 | pstate->p_coerce_param_hook = variable_coerce_param_hook; |
94 | 0 | } |
95 | | |
96 | | /* |
97 | | * Transform a ParamRef using fixed parameter types. |
98 | | */ |
99 | | static Node * |
100 | | fixed_paramref_hook(ParseState *pstate, ParamRef *pref) |
101 | 0 | { |
102 | 0 | FixedParamState *parstate = (FixedParamState *) pstate->p_ref_hook_state; |
103 | 0 | int paramno = pref->number; |
104 | 0 | Param *param; |
105 | | |
106 | | /* Check parameter number is valid */ |
107 | 0 | if (paramno <= 0 || paramno > parstate->numParams || |
108 | 0 | !OidIsValid(parstate->paramTypes[paramno - 1])) |
109 | 0 | ereport(ERROR, |
110 | 0 | (errcode(ERRCODE_UNDEFINED_PARAMETER), |
111 | 0 | errmsg("there is no parameter $%d", paramno), |
112 | 0 | parser_errposition(pstate, pref->location))); |
113 | | |
114 | 0 | param = makeNode(Param); |
115 | 0 | param->paramkind = PARAM_EXTERN; |
116 | 0 | param->paramid = paramno; |
117 | 0 | param->paramtype = parstate->paramTypes[paramno - 1]; |
118 | 0 | param->paramtypmod = -1; |
119 | 0 | param->paramcollid = get_typcollation(param->paramtype); |
120 | 0 | param->location = pref->location; |
121 | |
|
122 | 0 | return (Node *) param; |
123 | 0 | } |
124 | | |
125 | | /* |
126 | | * Transform a ParamRef using variable parameter types. |
127 | | * |
128 | | * The only difference here is we must enlarge the parameter type array |
129 | | * as needed. |
130 | | */ |
131 | | static Node * |
132 | | variable_paramref_hook(ParseState *pstate, ParamRef *pref) |
133 | 0 | { |
134 | 0 | VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; |
135 | 0 | int paramno = pref->number; |
136 | 0 | Oid *pptype; |
137 | 0 | Param *param; |
138 | | |
139 | | /* Check parameter number is in range */ |
140 | 0 | if (paramno <= 0 || paramno > MaxAllocSize / sizeof(Oid)) |
141 | 0 | ereport(ERROR, |
142 | 0 | (errcode(ERRCODE_UNDEFINED_PARAMETER), |
143 | 0 | errmsg("there is no parameter $%d", paramno), |
144 | 0 | parser_errposition(pstate, pref->location))); |
145 | 0 | if (paramno > *parstate->numParams) |
146 | 0 | { |
147 | | /* Need to enlarge param array */ |
148 | 0 | if (*parstate->paramTypes) |
149 | 0 | *parstate->paramTypes = repalloc0_array(*parstate->paramTypes, Oid, |
150 | 0 | *parstate->numParams, paramno); |
151 | 0 | else |
152 | 0 | *parstate->paramTypes = palloc0_array(Oid, paramno); |
153 | 0 | *parstate->numParams = paramno; |
154 | 0 | } |
155 | | |
156 | | /* Locate param's slot in array */ |
157 | 0 | pptype = &(*parstate->paramTypes)[paramno - 1]; |
158 | | |
159 | | /* If not seen before, initialize to UNKNOWN type */ |
160 | 0 | if (*pptype == InvalidOid) |
161 | 0 | *pptype = UNKNOWNOID; |
162 | | |
163 | | /* |
164 | | * If the argument is of type void and it's procedure call, interpret it |
165 | | * as unknown. This allows the JDBC driver to not have to distinguish |
166 | | * function and procedure calls. See also another component of this hack |
167 | | * in ParseFuncOrColumn(). |
168 | | */ |
169 | 0 | if (*pptype == VOIDOID && pstate->p_expr_kind == EXPR_KIND_CALL_ARGUMENT) |
170 | 0 | *pptype = UNKNOWNOID; |
171 | |
|
172 | 0 | param = makeNode(Param); |
173 | 0 | param->paramkind = PARAM_EXTERN; |
174 | 0 | param->paramid = paramno; |
175 | 0 | param->paramtype = *pptype; |
176 | 0 | param->paramtypmod = -1; |
177 | 0 | param->paramcollid = get_typcollation(param->paramtype); |
178 | 0 | param->location = pref->location; |
179 | |
|
180 | 0 | return (Node *) param; |
181 | 0 | } |
182 | | |
183 | | /* |
184 | | * Coerce a Param to a query-requested datatype, in the varparams case. |
185 | | */ |
186 | | static Node * |
187 | | variable_coerce_param_hook(ParseState *pstate, Param *param, |
188 | | Oid targetTypeId, int32 targetTypeMod, |
189 | | int location) |
190 | 0 | { |
191 | 0 | if (param->paramkind == PARAM_EXTERN && param->paramtype == UNKNOWNOID) |
192 | 0 | { |
193 | | /* |
194 | | * Input is a Param of previously undetermined type, and we want to |
195 | | * update our knowledge of the Param's type. |
196 | | */ |
197 | 0 | VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; |
198 | 0 | Oid *paramTypes = *parstate->paramTypes; |
199 | 0 | int paramno = param->paramid; |
200 | |
|
201 | 0 | if (paramno <= 0 || /* shouldn't happen, but... */ |
202 | 0 | paramno > *parstate->numParams) |
203 | 0 | ereport(ERROR, |
204 | 0 | (errcode(ERRCODE_UNDEFINED_PARAMETER), |
205 | 0 | errmsg("there is no parameter $%d", paramno), |
206 | 0 | parser_errposition(pstate, param->location))); |
207 | | |
208 | 0 | if (paramTypes[paramno - 1] == UNKNOWNOID) |
209 | 0 | { |
210 | | /* We've successfully resolved the type */ |
211 | 0 | paramTypes[paramno - 1] = targetTypeId; |
212 | 0 | } |
213 | 0 | else if (paramTypes[paramno - 1] == targetTypeId) |
214 | 0 | { |
215 | | /* We previously resolved the type, and it matches */ |
216 | 0 | } |
217 | 0 | else |
218 | 0 | { |
219 | | /* Oops */ |
220 | 0 | ereport(ERROR, |
221 | 0 | (errcode(ERRCODE_AMBIGUOUS_PARAMETER), |
222 | 0 | errmsg("inconsistent types deduced for parameter $%d", |
223 | 0 | paramno), |
224 | 0 | errdetail("%s versus %s", |
225 | 0 | format_type_be(paramTypes[paramno - 1]), |
226 | 0 | format_type_be(targetTypeId)), |
227 | 0 | parser_errposition(pstate, param->location))); |
228 | 0 | } |
229 | | |
230 | 0 | param->paramtype = targetTypeId; |
231 | | |
232 | | /* |
233 | | * Note: it is tempting here to set the Param's paramtypmod to |
234 | | * targetTypeMod, but that is probably unwise because we have no |
235 | | * infrastructure that enforces that the value delivered for a Param |
236 | | * will match any particular typmod. Leaving it -1 ensures that a |
237 | | * run-time length check/coercion will occur if needed. |
238 | | */ |
239 | 0 | param->paramtypmod = -1; |
240 | | |
241 | | /* |
242 | | * This module always sets a Param's collation to be the default for |
243 | | * its datatype. If that's not what you want, you should be using the |
244 | | * more general parser substitution hooks. |
245 | | */ |
246 | 0 | param->paramcollid = get_typcollation(param->paramtype); |
247 | | |
248 | | /* Use the leftmost of the param's and coercion's locations */ |
249 | 0 | if (location >= 0 && |
250 | 0 | (param->location < 0 || location < param->location)) |
251 | 0 | param->location = location; |
252 | |
|
253 | 0 | return (Node *) param; |
254 | 0 | } |
255 | | |
256 | | /* Else signal to proceed with normal coercion */ |
257 | 0 | return NULL; |
258 | 0 | } |
259 | | |
260 | | /* |
261 | | * Check for consistent assignment of variable parameters after completion |
262 | | * of parsing with parse_variable_parameters. |
263 | | * |
264 | | * Note: this code intentionally does not check that all parameter positions |
265 | | * were used, nor that all got non-UNKNOWN types assigned. Caller of parser |
266 | | * should enforce that if it's important. |
267 | | */ |
268 | | void |
269 | | check_variable_parameters(ParseState *pstate, Query *query) |
270 | 0 | { |
271 | 0 | VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; |
272 | | |
273 | | /* If numParams is zero then no Params were generated, so no work */ |
274 | 0 | if (*parstate->numParams > 0) |
275 | 0 | (void) query_tree_walker(query, |
276 | 0 | check_parameter_resolution_walker, |
277 | 0 | pstate, 0); |
278 | 0 | } |
279 | | |
280 | | /* |
281 | | * Traverse a fully-analyzed tree to verify that parameter symbols |
282 | | * match their types. We need this because some Params might still |
283 | | * be UNKNOWN, if there wasn't anything to force their coercion, |
284 | | * and yet other instances seen later might have gotten coerced. |
285 | | */ |
286 | | static bool |
287 | | check_parameter_resolution_walker(Node *node, ParseState *pstate) |
288 | 0 | { |
289 | 0 | if (node == NULL) |
290 | 0 | return false; |
291 | 0 | if (IsA(node, Param)) |
292 | 0 | { |
293 | 0 | Param *param = (Param *) node; |
294 | |
|
295 | 0 | if (param->paramkind == PARAM_EXTERN) |
296 | 0 | { |
297 | 0 | VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; |
298 | 0 | int paramno = param->paramid; |
299 | |
|
300 | 0 | if (paramno <= 0 || /* shouldn't happen, but... */ |
301 | 0 | paramno > *parstate->numParams) |
302 | 0 | ereport(ERROR, |
303 | 0 | (errcode(ERRCODE_UNDEFINED_PARAMETER), |
304 | 0 | errmsg("there is no parameter $%d", paramno), |
305 | 0 | parser_errposition(pstate, param->location))); |
306 | | |
307 | 0 | if (param->paramtype != (*parstate->paramTypes)[paramno - 1]) |
308 | 0 | ereport(ERROR, |
309 | 0 | (errcode(ERRCODE_AMBIGUOUS_PARAMETER), |
310 | 0 | errmsg("could not determine data type of parameter $%d", |
311 | 0 | paramno), |
312 | 0 | parser_errposition(pstate, param->location))); |
313 | 0 | } |
314 | 0 | return false; |
315 | 0 | } |
316 | 0 | if (IsA(node, Query)) |
317 | 0 | { |
318 | | /* Recurse into RTE subquery or not-yet-planned sublink subquery */ |
319 | 0 | return query_tree_walker((Query *) node, |
320 | 0 | check_parameter_resolution_walker, |
321 | 0 | pstate, 0); |
322 | 0 | } |
323 | 0 | return expression_tree_walker(node, check_parameter_resolution_walker, |
324 | 0 | pstate); |
325 | 0 | } |
326 | | |
327 | | /* |
328 | | * Check to see if a fully-parsed query tree contains any PARAM_EXTERN Params. |
329 | | */ |
330 | | bool |
331 | | query_contains_extern_params(Query *query) |
332 | 0 | { |
333 | 0 | return query_tree_walker(query, |
334 | 0 | query_contains_extern_params_walker, |
335 | 0 | NULL, 0); |
336 | 0 | } |
337 | | |
338 | | static bool |
339 | | query_contains_extern_params_walker(Node *node, void *context) |
340 | 0 | { |
341 | 0 | if (node == NULL) |
342 | 0 | return false; |
343 | 0 | if (IsA(node, Param)) |
344 | 0 | { |
345 | 0 | Param *param = (Param *) node; |
346 | |
|
347 | 0 | if (param->paramkind == PARAM_EXTERN) |
348 | 0 | return true; |
349 | 0 | return false; |
350 | 0 | } |
351 | 0 | if (IsA(node, Query)) |
352 | 0 | { |
353 | | /* Recurse into RTE subquery or not-yet-planned sublink subquery */ |
354 | 0 | return query_tree_walker((Query *) node, |
355 | 0 | query_contains_extern_params_walker, |
356 | 0 | context, 0); |
357 | 0 | } |
358 | 0 | return expression_tree_walker(node, query_contains_extern_params_walker, |
359 | 0 | context); |
360 | 0 | } |