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