/src/postgres/src/backend/commands/prepare.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * prepare.c |
4 | | * Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE |
5 | | * |
6 | | * This module also implements storage of prepared statements that are |
7 | | * accessed via the extended FE/BE query protocol. |
8 | | * |
9 | | * |
10 | | * Copyright (c) 2002-2025, PostgreSQL Global Development Group |
11 | | * |
12 | | * IDENTIFICATION |
13 | | * src/backend/commands/prepare.c |
14 | | * |
15 | | *------------------------------------------------------------------------- |
16 | | */ |
17 | | #include "postgres.h" |
18 | | |
19 | | #include <limits.h> |
20 | | |
21 | | #include "access/xact.h" |
22 | | #include "catalog/pg_type.h" |
23 | | #include "commands/createas.h" |
24 | | #include "commands/explain.h" |
25 | | #include "commands/explain_format.h" |
26 | | #include "commands/explain_state.h" |
27 | | #include "commands/prepare.h" |
28 | | #include "funcapi.h" |
29 | | #include "nodes/nodeFuncs.h" |
30 | | #include "parser/parse_coerce.h" |
31 | | #include "parser/parse_collate.h" |
32 | | #include "parser/parse_expr.h" |
33 | | #include "parser/parse_type.h" |
34 | | #include "tcop/pquery.h" |
35 | | #include "tcop/utility.h" |
36 | | #include "utils/builtins.h" |
37 | | #include "utils/snapmgr.h" |
38 | | #include "utils/timestamp.h" |
39 | | |
40 | | |
41 | | /* |
42 | | * The hash table in which prepared queries are stored. This is |
43 | | * per-backend: query plans are not shared between backends. |
44 | | * The keys for this hash table are the arguments to PREPARE and EXECUTE |
45 | | * (statement names); the entries are PreparedStatement structs. |
46 | | */ |
47 | | static HTAB *prepared_queries = NULL; |
48 | | |
49 | | static void InitQueryHashTable(void); |
50 | | static ParamListInfo EvaluateParams(ParseState *pstate, |
51 | | PreparedStatement *pstmt, List *params, |
52 | | EState *estate); |
53 | | static Datum build_regtype_array(Oid *param_types, int num_params); |
54 | | |
55 | | /* |
56 | | * Implements the 'PREPARE' utility statement. |
57 | | */ |
58 | | void |
59 | | PrepareQuery(ParseState *pstate, PrepareStmt *stmt, |
60 | | int stmt_location, int stmt_len) |
61 | 0 | { |
62 | 0 | RawStmt *rawstmt; |
63 | 0 | CachedPlanSource *plansource; |
64 | 0 | Oid *argtypes = NULL; |
65 | 0 | int nargs; |
66 | 0 | List *query_list; |
67 | | |
68 | | /* |
69 | | * Disallow empty-string statement name (conflicts with protocol-level |
70 | | * unnamed statement). |
71 | | */ |
72 | 0 | if (!stmt->name || stmt->name[0] == '\0') |
73 | 0 | ereport(ERROR, |
74 | 0 | (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), |
75 | 0 | errmsg("invalid statement name: must not be empty"))); |
76 | | |
77 | | /* |
78 | | * Need to wrap the contained statement in a RawStmt node to pass it to |
79 | | * parse analysis. |
80 | | */ |
81 | 0 | rawstmt = makeNode(RawStmt); |
82 | 0 | rawstmt->stmt = stmt->query; |
83 | 0 | rawstmt->stmt_location = stmt_location; |
84 | 0 | rawstmt->stmt_len = stmt_len; |
85 | | |
86 | | /* |
87 | | * Create the CachedPlanSource before we do parse analysis, since it needs |
88 | | * to see the unmodified raw parse tree. |
89 | | */ |
90 | 0 | plansource = CreateCachedPlan(rawstmt, pstate->p_sourcetext, |
91 | 0 | CreateCommandTag(stmt->query)); |
92 | | |
93 | | /* Transform list of TypeNames to array of type OIDs */ |
94 | 0 | nargs = list_length(stmt->argtypes); |
95 | |
|
96 | 0 | if (nargs) |
97 | 0 | { |
98 | 0 | int i; |
99 | 0 | ListCell *l; |
100 | |
|
101 | 0 | argtypes = palloc_array(Oid, nargs); |
102 | 0 | i = 0; |
103 | |
|
104 | 0 | foreach(l, stmt->argtypes) |
105 | 0 | { |
106 | 0 | TypeName *tn = lfirst(l); |
107 | 0 | Oid toid = typenameTypeId(pstate, tn); |
108 | |
|
109 | 0 | argtypes[i++] = toid; |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | | /* |
114 | | * Analyze the statement using these parameter types (any parameters |
115 | | * passed in from above us will not be visible to it), allowing |
116 | | * information about unknown parameters to be deduced from context. |
117 | | * Rewrite the query. The result could be 0, 1, or many queries. |
118 | | */ |
119 | 0 | query_list = pg_analyze_and_rewrite_varparams(rawstmt, pstate->p_sourcetext, |
120 | 0 | &argtypes, &nargs, NULL); |
121 | | |
122 | | /* Finish filling in the CachedPlanSource */ |
123 | 0 | CompleteCachedPlan(plansource, |
124 | 0 | query_list, |
125 | 0 | NULL, |
126 | 0 | argtypes, |
127 | 0 | nargs, |
128 | 0 | NULL, |
129 | 0 | NULL, |
130 | 0 | CURSOR_OPT_PARALLEL_OK, /* allow parallel mode */ |
131 | 0 | true); /* fixed result */ |
132 | | |
133 | | /* |
134 | | * Save the results. |
135 | | */ |
136 | 0 | StorePreparedStatement(stmt->name, |
137 | 0 | plansource, |
138 | 0 | true); |
139 | 0 | } |
140 | | |
141 | | /* |
142 | | * ExecuteQuery --- implement the 'EXECUTE' utility statement. |
143 | | * |
144 | | * This code also supports CREATE TABLE ... AS EXECUTE. That case is |
145 | | * indicated by passing a non-null intoClause. The DestReceiver is already |
146 | | * set up correctly for CREATE TABLE AS, but we still have to make a few |
147 | | * other adjustments here. |
148 | | */ |
149 | | void |
150 | | ExecuteQuery(ParseState *pstate, |
151 | | ExecuteStmt *stmt, IntoClause *intoClause, |
152 | | ParamListInfo params, |
153 | | DestReceiver *dest, QueryCompletion *qc) |
154 | 0 | { |
155 | 0 | PreparedStatement *entry; |
156 | 0 | CachedPlan *cplan; |
157 | 0 | List *plan_list; |
158 | 0 | ParamListInfo paramLI = NULL; |
159 | 0 | EState *estate = NULL; |
160 | 0 | Portal portal; |
161 | 0 | char *query_string; |
162 | 0 | int eflags; |
163 | 0 | long count; |
164 | | |
165 | | /* Look it up in the hash table */ |
166 | 0 | entry = FetchPreparedStatement(stmt->name, true); |
167 | | |
168 | | /* Shouldn't find a non-fixed-result cached plan */ |
169 | 0 | if (!entry->plansource->fixed_result) |
170 | 0 | elog(ERROR, "EXECUTE does not support variable-result cached plans"); |
171 | | |
172 | | /* Evaluate parameters, if any */ |
173 | 0 | if (entry->plansource->num_params > 0) |
174 | 0 | { |
175 | | /* |
176 | | * Need an EState to evaluate parameters; must not delete it till end |
177 | | * of query, in case parameters are pass-by-reference. Note that the |
178 | | * passed-in "params" could possibly be referenced in the parameter |
179 | | * expressions. |
180 | | */ |
181 | 0 | estate = CreateExecutorState(); |
182 | 0 | estate->es_param_list_info = params; |
183 | 0 | paramLI = EvaluateParams(pstate, entry, stmt->params, estate); |
184 | 0 | } |
185 | | |
186 | | /* Create a new portal to run the query in */ |
187 | 0 | portal = CreateNewPortal(); |
188 | | /* Don't display the portal in pg_cursors, it is for internal use only */ |
189 | 0 | portal->visible = false; |
190 | | |
191 | | /* Copy the plan's saved query string into the portal's memory */ |
192 | 0 | query_string = MemoryContextStrdup(portal->portalContext, |
193 | 0 | entry->plansource->query_string); |
194 | | |
195 | | /* Replan if needed, and increment plan refcount for portal */ |
196 | 0 | cplan = GetCachedPlan(entry->plansource, paramLI, NULL, NULL); |
197 | 0 | plan_list = cplan->stmt_list; |
198 | | |
199 | | /* |
200 | | * DO NOT add any logic that could possibly throw an error between |
201 | | * GetCachedPlan and PortalDefineQuery, or you'll leak the plan refcount. |
202 | | */ |
203 | 0 | PortalDefineQuery(portal, |
204 | 0 | NULL, |
205 | 0 | query_string, |
206 | 0 | entry->plansource->commandTag, |
207 | 0 | plan_list, |
208 | 0 | cplan); |
209 | | |
210 | | /* |
211 | | * For CREATE TABLE ... AS EXECUTE, we must verify that the prepared |
212 | | * statement is one that produces tuples. Currently we insist that it be |
213 | | * a plain old SELECT. In future we might consider supporting other |
214 | | * things such as INSERT ... RETURNING, but there are a couple of issues |
215 | | * to be settled first, notably how WITH NO DATA should be handled in such |
216 | | * a case (do we really want to suppress execution?) and how to pass down |
217 | | * the OID-determining eflags (PortalStart won't handle them in such a |
218 | | * case, and for that matter it's not clear the executor will either). |
219 | | * |
220 | | * For CREATE TABLE ... AS EXECUTE, we also have to ensure that the proper |
221 | | * eflags and fetch count are passed to PortalStart/PortalRun. |
222 | | */ |
223 | 0 | if (intoClause) |
224 | 0 | { |
225 | 0 | PlannedStmt *pstmt; |
226 | |
|
227 | 0 | if (list_length(plan_list) != 1) |
228 | 0 | ereport(ERROR, |
229 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
230 | 0 | errmsg("prepared statement is not a SELECT"))); |
231 | 0 | pstmt = linitial_node(PlannedStmt, plan_list); |
232 | 0 | if (pstmt->commandType != CMD_SELECT) |
233 | 0 | ereport(ERROR, |
234 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
235 | 0 | errmsg("prepared statement is not a SELECT"))); |
236 | | |
237 | | /* Set appropriate eflags */ |
238 | 0 | eflags = GetIntoRelEFlags(intoClause); |
239 | | |
240 | | /* And tell PortalRun whether to run to completion or not */ |
241 | 0 | if (intoClause->skipData) |
242 | 0 | count = 0; |
243 | 0 | else |
244 | 0 | count = FETCH_ALL; |
245 | 0 | } |
246 | 0 | else |
247 | 0 | { |
248 | | /* Plain old EXECUTE */ |
249 | 0 | eflags = 0; |
250 | 0 | count = FETCH_ALL; |
251 | 0 | } |
252 | | |
253 | | /* |
254 | | * Run the portal as appropriate. |
255 | | */ |
256 | 0 | PortalStart(portal, paramLI, eflags, GetActiveSnapshot()); |
257 | |
|
258 | 0 | (void) PortalRun(portal, count, false, dest, dest, qc); |
259 | |
|
260 | 0 | PortalDrop(portal, false); |
261 | |
|
262 | 0 | if (estate) |
263 | 0 | FreeExecutorState(estate); |
264 | | |
265 | | /* No need to pfree other memory, MemoryContext will be reset */ |
266 | 0 | } |
267 | | |
268 | | /* |
269 | | * EvaluateParams: evaluate a list of parameters. |
270 | | * |
271 | | * pstate: parse state |
272 | | * pstmt: statement we are getting parameters for. |
273 | | * params: list of given parameter expressions (raw parser output!) |
274 | | * estate: executor state to use. |
275 | | * |
276 | | * Returns a filled-in ParamListInfo -- this can later be passed to |
277 | | * CreateQueryDesc(), which allows the executor to make use of the parameters |
278 | | * during query execution. |
279 | | */ |
280 | | static ParamListInfo |
281 | | EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params, |
282 | | EState *estate) |
283 | 0 | { |
284 | 0 | Oid *param_types = pstmt->plansource->param_types; |
285 | 0 | int num_params = pstmt->plansource->num_params; |
286 | 0 | int nparams = list_length(params); |
287 | 0 | ParamListInfo paramLI; |
288 | 0 | List *exprstates; |
289 | 0 | ListCell *l; |
290 | 0 | int i; |
291 | |
|
292 | 0 | if (nparams != num_params) |
293 | 0 | ereport(ERROR, |
294 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
295 | 0 | errmsg("wrong number of parameters for prepared statement \"%s\"", |
296 | 0 | pstmt->stmt_name), |
297 | 0 | errdetail("Expected %d parameters but got %d.", |
298 | 0 | num_params, nparams))); |
299 | | |
300 | | /* Quick exit if no parameters */ |
301 | 0 | if (num_params == 0) |
302 | 0 | return NULL; |
303 | | |
304 | | /* |
305 | | * We have to run parse analysis for the expressions. Since the parser is |
306 | | * not cool about scribbling on its input, copy first. |
307 | | */ |
308 | 0 | params = copyObject(params); |
309 | |
|
310 | 0 | i = 0; |
311 | 0 | foreach(l, params) |
312 | 0 | { |
313 | 0 | Node *expr = lfirst(l); |
314 | 0 | Oid expected_type_id = param_types[i]; |
315 | 0 | Oid given_type_id; |
316 | |
|
317 | 0 | expr = transformExpr(pstate, expr, EXPR_KIND_EXECUTE_PARAMETER); |
318 | |
|
319 | 0 | given_type_id = exprType(expr); |
320 | |
|
321 | 0 | expr = coerce_to_target_type(pstate, expr, given_type_id, |
322 | 0 | expected_type_id, -1, |
323 | 0 | COERCION_ASSIGNMENT, |
324 | 0 | COERCE_IMPLICIT_CAST, |
325 | 0 | -1); |
326 | |
|
327 | 0 | if (expr == NULL) |
328 | 0 | ereport(ERROR, |
329 | 0 | (errcode(ERRCODE_DATATYPE_MISMATCH), |
330 | 0 | errmsg("parameter $%d of type %s cannot be coerced to the expected type %s", |
331 | 0 | i + 1, |
332 | 0 | format_type_be(given_type_id), |
333 | 0 | format_type_be(expected_type_id)), |
334 | 0 | errhint("You will need to rewrite or cast the expression."), |
335 | 0 | parser_errposition(pstate, exprLocation(lfirst(l))))); |
336 | | |
337 | | /* Take care of collations in the finished expression. */ |
338 | 0 | assign_expr_collations(pstate, expr); |
339 | |
|
340 | 0 | lfirst(l) = expr; |
341 | 0 | i++; |
342 | 0 | } |
343 | | |
344 | | /* Prepare the expressions for execution */ |
345 | 0 | exprstates = ExecPrepareExprList(params, estate); |
346 | |
|
347 | 0 | paramLI = makeParamList(num_params); |
348 | |
|
349 | 0 | i = 0; |
350 | 0 | foreach(l, exprstates) |
351 | 0 | { |
352 | 0 | ExprState *n = (ExprState *) lfirst(l); |
353 | 0 | ParamExternData *prm = ¶mLI->params[i]; |
354 | |
|
355 | 0 | prm->ptype = param_types[i]; |
356 | 0 | prm->pflags = PARAM_FLAG_CONST; |
357 | 0 | prm->value = ExecEvalExprSwitchContext(n, |
358 | 0 | GetPerTupleExprContext(estate), |
359 | 0 | &prm->isnull); |
360 | |
|
361 | 0 | i++; |
362 | 0 | } |
363 | |
|
364 | 0 | return paramLI; |
365 | 0 | } |
366 | | |
367 | | |
368 | | /* |
369 | | * Initialize query hash table upon first use. |
370 | | */ |
371 | | static void |
372 | | InitQueryHashTable(void) |
373 | 0 | { |
374 | 0 | HASHCTL hash_ctl; |
375 | |
|
376 | 0 | hash_ctl.keysize = NAMEDATALEN; |
377 | 0 | hash_ctl.entrysize = sizeof(PreparedStatement); |
378 | |
|
379 | 0 | prepared_queries = hash_create("Prepared Queries", |
380 | 0 | 32, |
381 | 0 | &hash_ctl, |
382 | 0 | HASH_ELEM | HASH_STRINGS); |
383 | 0 | } |
384 | | |
385 | | /* |
386 | | * Store all the data pertaining to a query in the hash table using |
387 | | * the specified key. The passed CachedPlanSource should be "unsaved" |
388 | | * in case we get an error here; we'll save it once we've created the hash |
389 | | * table entry. |
390 | | */ |
391 | | void |
392 | | StorePreparedStatement(const char *stmt_name, |
393 | | CachedPlanSource *plansource, |
394 | | bool from_sql) |
395 | 0 | { |
396 | 0 | PreparedStatement *entry; |
397 | 0 | TimestampTz cur_ts = GetCurrentStatementStartTimestamp(); |
398 | 0 | bool found; |
399 | | |
400 | | /* Initialize the hash table, if necessary */ |
401 | 0 | if (!prepared_queries) |
402 | 0 | InitQueryHashTable(); |
403 | | |
404 | | /* Add entry to hash table */ |
405 | 0 | entry = (PreparedStatement *) hash_search(prepared_queries, |
406 | 0 | stmt_name, |
407 | 0 | HASH_ENTER, |
408 | 0 | &found); |
409 | | |
410 | | /* Shouldn't get a duplicate entry */ |
411 | 0 | if (found) |
412 | 0 | ereport(ERROR, |
413 | 0 | (errcode(ERRCODE_DUPLICATE_PSTATEMENT), |
414 | 0 | errmsg("prepared statement \"%s\" already exists", |
415 | 0 | stmt_name))); |
416 | | |
417 | | /* Fill in the hash table entry */ |
418 | 0 | entry->plansource = plansource; |
419 | 0 | entry->from_sql = from_sql; |
420 | 0 | entry->prepare_time = cur_ts; |
421 | | |
422 | | /* Now it's safe to move the CachedPlanSource to permanent memory */ |
423 | 0 | SaveCachedPlan(plansource); |
424 | 0 | } |
425 | | |
426 | | /* |
427 | | * Lookup an existing query in the hash table. If the query does not |
428 | | * actually exist, throw ereport(ERROR) or return NULL per second parameter. |
429 | | * |
430 | | * Note: this does not force the referenced plancache entry to be valid, |
431 | | * since not all callers care. |
432 | | */ |
433 | | PreparedStatement * |
434 | | FetchPreparedStatement(const char *stmt_name, bool throwError) |
435 | 0 | { |
436 | 0 | PreparedStatement *entry; |
437 | | |
438 | | /* |
439 | | * If the hash table hasn't been initialized, it can't be storing |
440 | | * anything, therefore it couldn't possibly store our plan. |
441 | | */ |
442 | 0 | if (prepared_queries) |
443 | 0 | entry = (PreparedStatement *) hash_search(prepared_queries, |
444 | 0 | stmt_name, |
445 | 0 | HASH_FIND, |
446 | 0 | NULL); |
447 | 0 | else |
448 | 0 | entry = NULL; |
449 | |
|
450 | 0 | if (!entry && throwError) |
451 | 0 | ereport(ERROR, |
452 | 0 | (errcode(ERRCODE_UNDEFINED_PSTATEMENT), |
453 | 0 | errmsg("prepared statement \"%s\" does not exist", |
454 | 0 | stmt_name))); |
455 | | |
456 | 0 | return entry; |
457 | 0 | } |
458 | | |
459 | | /* |
460 | | * Given a prepared statement, determine the result tupledesc it will |
461 | | * produce. Returns NULL if the execution will not return tuples. |
462 | | * |
463 | | * Note: the result is created or copied into current memory context. |
464 | | */ |
465 | | TupleDesc |
466 | | FetchPreparedStatementResultDesc(PreparedStatement *stmt) |
467 | 0 | { |
468 | | /* |
469 | | * Since we don't allow prepared statements' result tupdescs to change, |
470 | | * there's no need to worry about revalidating the cached plan here. |
471 | | */ |
472 | 0 | Assert(stmt->plansource->fixed_result); |
473 | 0 | if (stmt->plansource->resultDesc) |
474 | 0 | return CreateTupleDescCopy(stmt->plansource->resultDesc); |
475 | 0 | else |
476 | 0 | return NULL; |
477 | 0 | } |
478 | | |
479 | | /* |
480 | | * Given a prepared statement that returns tuples, extract the query |
481 | | * targetlist. Returns NIL if the statement doesn't have a determinable |
482 | | * targetlist. |
483 | | * |
484 | | * Note: this is pretty ugly, but since it's only used in corner cases like |
485 | | * Describe Statement on an EXECUTE command, we don't worry too much about |
486 | | * efficiency. |
487 | | */ |
488 | | List * |
489 | | FetchPreparedStatementTargetList(PreparedStatement *stmt) |
490 | 0 | { |
491 | 0 | List *tlist; |
492 | | |
493 | | /* Get the plan's primary targetlist */ |
494 | 0 | tlist = CachedPlanGetTargetList(stmt->plansource, NULL); |
495 | | |
496 | | /* Copy into caller's context in case plan gets invalidated */ |
497 | 0 | return copyObject(tlist); |
498 | 0 | } |
499 | | |
500 | | /* |
501 | | * Implements the 'DEALLOCATE' utility statement: deletes the |
502 | | * specified plan from storage. |
503 | | */ |
504 | | void |
505 | | DeallocateQuery(DeallocateStmt *stmt) |
506 | 0 | { |
507 | 0 | if (stmt->name) |
508 | 0 | DropPreparedStatement(stmt->name, true); |
509 | 0 | else |
510 | 0 | DropAllPreparedStatements(); |
511 | 0 | } |
512 | | |
513 | | /* |
514 | | * Internal version of DEALLOCATE |
515 | | * |
516 | | * If showError is false, dropping a nonexistent statement is a no-op. |
517 | | */ |
518 | | void |
519 | | DropPreparedStatement(const char *stmt_name, bool showError) |
520 | 0 | { |
521 | 0 | PreparedStatement *entry; |
522 | | |
523 | | /* Find the query's hash table entry; raise error if wanted */ |
524 | 0 | entry = FetchPreparedStatement(stmt_name, showError); |
525 | |
|
526 | 0 | if (entry) |
527 | 0 | { |
528 | | /* Release the plancache entry */ |
529 | 0 | DropCachedPlan(entry->plansource); |
530 | | |
531 | | /* Now we can remove the hash table entry */ |
532 | 0 | hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL); |
533 | 0 | } |
534 | 0 | } |
535 | | |
536 | | /* |
537 | | * Drop all cached statements. |
538 | | */ |
539 | | void |
540 | | DropAllPreparedStatements(void) |
541 | 0 | { |
542 | 0 | HASH_SEQ_STATUS seq; |
543 | 0 | PreparedStatement *entry; |
544 | | |
545 | | /* nothing cached */ |
546 | 0 | if (!prepared_queries) |
547 | 0 | return; |
548 | | |
549 | | /* walk over cache */ |
550 | 0 | hash_seq_init(&seq, prepared_queries); |
551 | 0 | while ((entry = hash_seq_search(&seq)) != NULL) |
552 | 0 | { |
553 | | /* Release the plancache entry */ |
554 | 0 | DropCachedPlan(entry->plansource); |
555 | | |
556 | | /* Now we can remove the hash table entry */ |
557 | 0 | hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL); |
558 | 0 | } |
559 | 0 | } |
560 | | |
561 | | /* |
562 | | * Implements the 'EXPLAIN EXECUTE' utility statement. |
563 | | * |
564 | | * "into" is NULL unless we are doing EXPLAIN CREATE TABLE AS EXECUTE, |
565 | | * in which case executing the query should result in creating that table. |
566 | | * |
567 | | * Note: the passed-in pstate's queryString is that of the EXPLAIN EXECUTE, |
568 | | * not the original PREPARE; we get the latter string from the plancache. |
569 | | */ |
570 | | void |
571 | | ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, |
572 | | ParseState *pstate, ParamListInfo params) |
573 | 0 | { |
574 | 0 | PreparedStatement *entry; |
575 | 0 | const char *query_string; |
576 | 0 | CachedPlan *cplan; |
577 | 0 | List *plan_list; |
578 | 0 | ListCell *p; |
579 | 0 | ParamListInfo paramLI = NULL; |
580 | 0 | EState *estate = NULL; |
581 | 0 | instr_time planstart; |
582 | 0 | instr_time planduration; |
583 | 0 | BufferUsage bufusage_start, |
584 | 0 | bufusage; |
585 | 0 | MemoryContextCounters mem_counters; |
586 | 0 | MemoryContext planner_ctx = NULL; |
587 | 0 | MemoryContext saved_ctx = NULL; |
588 | |
|
589 | 0 | if (es->memory) |
590 | 0 | { |
591 | | /* See ExplainOneQuery about this */ |
592 | 0 | Assert(IsA(CurrentMemoryContext, AllocSetContext)); |
593 | 0 | planner_ctx = AllocSetContextCreate(CurrentMemoryContext, |
594 | 0 | "explain analyze planner context", |
595 | 0 | ALLOCSET_DEFAULT_SIZES); |
596 | 0 | saved_ctx = MemoryContextSwitchTo(planner_ctx); |
597 | 0 | } |
598 | |
|
599 | 0 | if (es->buffers) |
600 | 0 | bufusage_start = pgBufferUsage; |
601 | 0 | INSTR_TIME_SET_CURRENT(planstart); |
602 | | |
603 | | /* Look it up in the hash table */ |
604 | 0 | entry = FetchPreparedStatement(execstmt->name, true); |
605 | | |
606 | | /* Shouldn't find a non-fixed-result cached plan */ |
607 | 0 | if (!entry->plansource->fixed_result) |
608 | 0 | elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans"); |
609 | | |
610 | 0 | query_string = entry->plansource->query_string; |
611 | | |
612 | | /* Evaluate parameters, if any */ |
613 | 0 | if (entry->plansource->num_params) |
614 | 0 | { |
615 | 0 | ParseState *pstate_params; |
616 | |
|
617 | 0 | pstate_params = make_parsestate(NULL); |
618 | 0 | pstate_params->p_sourcetext = pstate->p_sourcetext; |
619 | | |
620 | | /* |
621 | | * Need an EState to evaluate parameters; must not delete it till end |
622 | | * of query, in case parameters are pass-by-reference. Note that the |
623 | | * passed-in "params" could possibly be referenced in the parameter |
624 | | * expressions. |
625 | | */ |
626 | 0 | estate = CreateExecutorState(); |
627 | 0 | estate->es_param_list_info = params; |
628 | |
|
629 | 0 | paramLI = EvaluateParams(pstate_params, entry, execstmt->params, estate); |
630 | 0 | } |
631 | | |
632 | | /* Replan if needed, and acquire a transient refcount */ |
633 | 0 | cplan = GetCachedPlan(entry->plansource, paramLI, |
634 | 0 | CurrentResourceOwner, pstate->p_queryEnv); |
635 | |
|
636 | 0 | INSTR_TIME_SET_CURRENT(planduration); |
637 | 0 | INSTR_TIME_SUBTRACT(planduration, planstart); |
638 | |
|
639 | 0 | if (es->memory) |
640 | 0 | { |
641 | 0 | MemoryContextSwitchTo(saved_ctx); |
642 | 0 | MemoryContextMemConsumed(planner_ctx, &mem_counters); |
643 | 0 | } |
644 | | |
645 | | /* calc differences of buffer counters. */ |
646 | 0 | if (es->buffers) |
647 | 0 | { |
648 | 0 | memset(&bufusage, 0, sizeof(BufferUsage)); |
649 | 0 | BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start); |
650 | 0 | } |
651 | |
|
652 | 0 | plan_list = cplan->stmt_list; |
653 | | |
654 | | /* Explain each query */ |
655 | 0 | foreach(p, plan_list) |
656 | 0 | { |
657 | 0 | PlannedStmt *pstmt = lfirst_node(PlannedStmt, p); |
658 | |
|
659 | 0 | if (pstmt->commandType != CMD_UTILITY) |
660 | 0 | ExplainOnePlan(pstmt, into, es, query_string, paramLI, pstate->p_queryEnv, |
661 | 0 | &planduration, (es->buffers ? &bufusage : NULL), |
662 | 0 | es->memory ? &mem_counters : NULL); |
663 | 0 | else |
664 | 0 | ExplainOneUtility(pstmt->utilityStmt, into, es, pstate, paramLI); |
665 | | |
666 | | /* No need for CommandCounterIncrement, as ExplainOnePlan did it */ |
667 | | |
668 | | /* Separate plans with an appropriate separator */ |
669 | 0 | if (lnext(plan_list, p) != NULL) |
670 | 0 | ExplainSeparatePlans(es); |
671 | 0 | } |
672 | |
|
673 | 0 | if (estate) |
674 | 0 | FreeExecutorState(estate); |
675 | |
|
676 | 0 | ReleaseCachedPlan(cplan, CurrentResourceOwner); |
677 | 0 | } |
678 | | |
679 | | /* |
680 | | * This set returning function reads all the prepared statements and |
681 | | * returns a set of (name, statement, prepare_time, param_types, from_sql, |
682 | | * generic_plans, custom_plans). |
683 | | */ |
684 | | Datum |
685 | | pg_prepared_statement(PG_FUNCTION_ARGS) |
686 | 0 | { |
687 | 0 | ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
688 | | |
689 | | /* |
690 | | * We put all the tuples into a tuplestore in one scan of the hashtable. |
691 | | * This avoids any issue of the hashtable possibly changing between calls. |
692 | | */ |
693 | 0 | InitMaterializedSRF(fcinfo, 0); |
694 | | |
695 | | /* hash table might be uninitialized */ |
696 | 0 | if (prepared_queries) |
697 | 0 | { |
698 | 0 | HASH_SEQ_STATUS hash_seq; |
699 | 0 | PreparedStatement *prep_stmt; |
700 | |
|
701 | 0 | hash_seq_init(&hash_seq, prepared_queries); |
702 | 0 | while ((prep_stmt = hash_seq_search(&hash_seq)) != NULL) |
703 | 0 | { |
704 | 0 | TupleDesc result_desc; |
705 | 0 | Datum values[8]; |
706 | 0 | bool nulls[8] = {0}; |
707 | |
|
708 | 0 | result_desc = prep_stmt->plansource->resultDesc; |
709 | |
|
710 | 0 | values[0] = CStringGetTextDatum(prep_stmt->stmt_name); |
711 | 0 | values[1] = CStringGetTextDatum(prep_stmt->plansource->query_string); |
712 | 0 | values[2] = TimestampTzGetDatum(prep_stmt->prepare_time); |
713 | 0 | values[3] = build_regtype_array(prep_stmt->plansource->param_types, |
714 | 0 | prep_stmt->plansource->num_params); |
715 | 0 | if (result_desc) |
716 | 0 | { |
717 | 0 | Oid *result_types; |
718 | |
|
719 | 0 | result_types = palloc_array(Oid, result_desc->natts); |
720 | 0 | for (int i = 0; i < result_desc->natts; i++) |
721 | 0 | result_types[i] = TupleDescAttr(result_desc, i)->atttypid; |
722 | 0 | values[4] = build_regtype_array(result_types, result_desc->natts); |
723 | 0 | } |
724 | 0 | else |
725 | 0 | { |
726 | | /* no result descriptor (for example, DML statement) */ |
727 | 0 | nulls[4] = true; |
728 | 0 | } |
729 | 0 | values[5] = BoolGetDatum(prep_stmt->from_sql); |
730 | 0 | values[6] = Int64GetDatumFast(prep_stmt->plansource->num_generic_plans); |
731 | 0 | values[7] = Int64GetDatumFast(prep_stmt->plansource->num_custom_plans); |
732 | |
|
733 | 0 | tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, |
734 | 0 | values, nulls); |
735 | 0 | } |
736 | 0 | } |
737 | |
|
738 | 0 | return (Datum) 0; |
739 | 0 | } |
740 | | |
741 | | /* |
742 | | * This utility function takes a C array of Oids, and returns a Datum |
743 | | * pointing to a one-dimensional Postgres array of regtypes. An empty |
744 | | * array is returned as a zero-element array, not NULL. |
745 | | */ |
746 | | static Datum |
747 | | build_regtype_array(Oid *param_types, int num_params) |
748 | 0 | { |
749 | 0 | Datum *tmp_ary; |
750 | 0 | ArrayType *result; |
751 | 0 | int i; |
752 | |
|
753 | 0 | tmp_ary = palloc_array(Datum, num_params); |
754 | |
|
755 | 0 | for (i = 0; i < num_params; i++) |
756 | 0 | tmp_ary[i] = ObjectIdGetDatum(param_types[i]); |
757 | |
|
758 | 0 | result = construct_array_builtin(tmp_ary, num_params, REGTYPEOID); |
759 | 0 | return PointerGetDatum(result); |
760 | 0 | } |