Coverage Report

Created: 2025-06-15 06:31

/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 = &paramLI->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
}