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