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