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