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