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-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
2072 peter@eisentraut.org 59 :GIC 1011 : PrepareQuery(ParseState *pstate, PrepareStmt *stmt,
60 : : int stmt_location, int stmt_len)
61 : : {
62 : : RawStmt *rawstmt;
63 : : CachedPlanSource *plansource;
6752 tgl@sss.pgh.pa.us 64 : 1011 : Oid *argtypes = NULL;
65 : : int nargs;
66 : : List *query_list;
67 : :
68 : : /*
69 : : * Disallow empty-string statement name (conflicts with protocol-level
70 : : * unnamed statement).
71 : : */
8160 72 [ + - - + ]: 1011 : if (!stmt->name || stmt->name[0] == '\0')
8084 tgl@sss.pgh.pa.us 73 [ # # ]:UIC 0 : ereport(ERROR,
74 : : (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
75 : : errmsg("invalid statement name: must not be empty")));
76 : :
77 : : /*
78 : : * Need to wrap the contained statement in a RawStmt node to pass it to
79 : : * parse analysis.
80 : : */
3157 tgl@sss.pgh.pa.us 81 :GIC 1011 : rawstmt = makeNode(RawStmt);
1541 82 : 1011 : rawstmt->stmt = stmt->query;
3157 83 : 1011 : rawstmt->stmt_location = stmt_location;
84 : 1011 : 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 : : */
2072 peter@eisentraut.org 90 : 1011 : plansource = CreateCachedPlan(rawstmt, pstate->p_sourcetext,
91 : : CreateCommandTag(stmt->query));
92 : :
93 : : /* Transform list of TypeNames to array of type OIDs */
6752 tgl@sss.pgh.pa.us 94 : 1011 : nargs = list_length(stmt->argtypes);
95 : :
96 [ + + ]: 1011 : if (nargs)
97 : : {
98 : : int i;
99 : : ListCell *l;
100 : :
1090 peter@eisentraut.org 101 : 855 : argtypes = palloc_array(Oid, nargs);
6752 tgl@sss.pgh.pa.us 102 : 855 : i = 0;
103 : :
104 [ + - + + : 1858 : foreach(l, stmt->argtypes)
+ + ]
105 : : {
106 : 1006 : TypeName *tn = lfirst(l);
5430 peter_e@gmx.net 107 : 1006 : Oid toid = typenameTypeId(pstate, tn);
108 : :
6752 tgl@sss.pgh.pa.us 109 : 1003 : argtypes[i++] = toid;
110 : : }
111 : : }
112 : :
113 : : /*
114 : : * Analyze the statement using these parameter types (any parameters
115 : : * passed in from above us will not be visible to it), allowing
116 : : * information about unknown parameters to be deduced from context.
117 : : * Rewrite the query. The result could be 0, 1, or many queries.
118 : : */
1282 peter@eisentraut.org 119 : 1008 : query_list = pg_analyze_and_rewrite_varparams(rawstmt, pstate->p_sourcetext,
120 : : &argtypes, &nargs, NULL);
121 : :
122 : : /* Finish filling in the CachedPlanSource */
5104 tgl@sss.pgh.pa.us 123 : 1008 : CompleteCachedPlan(plansource,
124 : : query_list,
125 : : NULL,
126 : : argtypes,
127 : : nargs,
128 : : NULL,
129 : : NULL,
130 : : CURSOR_OPT_PARALLEL_OK, /* allow parallel mode */
131 : : true); /* fixed result */
132 : :
133 : : /*
134 : : * Save the results.
135 : : */
8160 136 : 1008 : StorePreparedStatement(stmt->name,
137 : : plansource,
138 : : true);
8411 139 : 1005 : }
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
2072 peter@eisentraut.org 150 : 8061 : ExecuteQuery(ParseState *pstate,
151 : : ExecuteStmt *stmt, IntoClause *intoClause,
152 : : ParamListInfo params,
153 : : DestReceiver *dest, QueryCompletion *qc)
154 : : {
155 : : PreparedStatement *entry;
156 : : CachedPlan *cplan;
157 : : List *plan_list;
8411 tgl@sss.pgh.pa.us 158 : 8061 : ParamListInfo paramLI = NULL;
8252 159 : 8061 : EState *estate = NULL;
160 : : Portal portal;
161 : : char *query_string;
162 : : int eflags;
163 : : long count;
164 : :
165 : : /* Look it up in the hash table */
8160 166 : 8061 : entry = FetchPreparedStatement(stmt->name, true);
167 : :
168 : : /* Shouldn't find a non-fixed-result cached plan */
6752 169 [ - + ]: 8061 : if (!entry->plansource->fixed_result)
6752 tgl@sss.pgh.pa.us 170 [ # # ]:UIC 0 : elog(ERROR, "EXECUTE does not support variable-result cached plans");
171 : :
172 : : /* Evaluate parameters, if any */
6752 tgl@sss.pgh.pa.us 173 [ + + ]:GIC 8061 : if (entry->plansource->num_params > 0)
174 : : {
175 : : /*
176 : : * Need an EState to evaluate parameters; must not delete it till end
177 : : * of query, in case parameters are pass-by-reference. Note that the
178 : : * passed-in "params" could possibly be referenced in the parameter
179 : : * expressions.
180 : : */
8252 181 : 7573 : estate = CreateExecutorState();
7221 182 : 7573 : estate->es_param_list_info = params;
2072 peter@eisentraut.org 183 : 7573 : paramLI = EvaluateParams(pstate, entry, stmt->params, estate);
184 : : }
185 : :
186 : : /* Create a new portal to run the query in */
8163 tgl@sss.pgh.pa.us 187 : 8043 : portal = CreateNewPortal();
188 : : /* Don't display the portal in pg_cursors, it is for internal use only */
7171 neilc@samurai.com 189 : 8043 : portal->visible = false;
190 : :
191 : : /* Copy the plan's saved query string into the portal's memory */
2821 peter_e@gmx.net 192 : 8043 : query_string = MemoryContextStrdup(portal->portalContext,
6259 tgl@sss.pgh.pa.us 193 : 8043 : entry->plansource->query_string);
194 : :
195 : : /* Replan if needed, and increment plan refcount for portal */
1685 196 : 8043 : cplan = GetCachedPlan(entry->plansource, paramLI, NULL, NULL);
4919 197 : 8028 : 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 : : */
1543 203 : 8028 : PortalDefineQuery(portal,
204 : : NULL,
205 : : query_string,
206 : 8028 : entry->plansource->commandTag,
207 : : plan_list,
208 : : 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 : : */
4919 223 [ + + ]: 8028 : if (intoClause)
224 : : {
225 : : PlannedStmt *pstmt;
226 : :
6773 227 [ - + ]: 23 : if (list_length(plan_list) != 1)
8084 tgl@sss.pgh.pa.us 228 [ # # ]:UIC 0 : ereport(ERROR,
229 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
230 : : errmsg("prepared statement is not a SELECT")));
3071 tgl@sss.pgh.pa.us 231 :GIC 23 : pstmt = linitial_node(PlannedStmt, plan_list);
3157 232 [ - + ]: 23 : if (pstmt->commandType != CMD_SELECT)
8084 tgl@sss.pgh.pa.us 233 [ # # ]:UIC 0 : ereport(ERROR,
234 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
235 : : errmsg("prepared statement is not a SELECT")));
236 : :
237 : : /* Set appropriate eflags */
4919 tgl@sss.pgh.pa.us 238 :GIC 23 : eflags = GetIntoRelEFlags(intoClause);
239 : :
240 : : /* And tell PortalRun whether to run to completion or not */
241 [ + + ]: 23 : if (intoClause->skipData)
242 : 6 : count = 0;
243 : : else
244 : 17 : count = FETCH_ALL;
245 : : }
246 : : else
247 : : {
248 : : /* Plain old EXECUTE */
249 : 8005 : eflags = 0;
250 : 8005 : count = FETCH_ALL;
251 : : }
252 : :
253 : : /*
254 : : * Run the portal as appropriate.
255 : : */
4667 256 : 8028 : PortalStart(portal, paramLI, eflags, GetActiveSnapshot());
257 : :
271 258 : 8028 : (void) PortalRun(portal, count, false, dest, dest, qc);
259 : :
8163 260 : 8009 : PortalDrop(portal, false);
261 : :
8252 262 [ + + ]: 8009 : if (estate)
263 : 7534 : FreeExecutorState(estate);
264 : :
265 : : /* No need to pfree other memory, MemoryContext will be reset */
8411 266 : 8009 : }
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
2072 peter@eisentraut.org 281 : 7721 : EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params,
282 : : EState *estate)
283 : : {
6752 tgl@sss.pgh.pa.us 284 : 7721 : Oid *param_types = pstmt->plansource->param_types;
285 : 7721 : int num_params = pstmt->plansource->num_params;
286 : 7721 : int nparams = list_length(params);
287 : : ParamListInfo paramLI;
288 : : List *exprstates;
289 : : ListCell *l;
290 : : int i;
291 : :
292 [ + + ]: 7721 : if (nparams != num_params)
293 [ + - ]: 6 : ereport(ERROR,
294 : : (errcode(ERRCODE_SYNTAX_ERROR),
295 : : errmsg("wrong number of parameters for prepared statement \"%s\"",
296 : : pstmt->stmt_name),
297 : : errdetail("Expected %d parameters but got %d.",
298 : : num_params, nparams)));
299 : :
300 : : /* Quick exit if no parameters */
301 [ - + ]: 7715 : if (num_params == 0)
7077 tgl@sss.pgh.pa.us 302 :UIC 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 : : */
3103 peter_e@gmx.net 308 :GIC 7715 : params = copyObject(params);
309 : :
6752 tgl@sss.pgh.pa.us 310 : 7715 : i = 0;
311 [ + - + + : 15735 : foreach(l, params)
+ + ]
312 : : {
313 : 8026 : Node *expr = lfirst(l);
314 : 8026 : Oid expected_type_id = param_types[i];
315 : : Oid given_type_id;
316 : :
4775 317 : 8026 : expr = transformExpr(pstate, expr, EXPR_KIND_EXECUTE_PARAMETER);
318 : :
6752 319 : 8026 : given_type_id = exprType(expr);
320 : :
321 : 8026 : expr = coerce_to_target_type(pstate, expr, given_type_id,
322 : : expected_type_id, -1,
323 : : COERCION_ASSIGNMENT,
324 : : COERCE_IMPLICIT_CAST,
325 : : -1);
326 : :
327 [ + + ]: 8023 : if (expr == NULL)
328 [ + - ]: 3 : ereport(ERROR,
329 : : (errcode(ERRCODE_DATATYPE_MISMATCH),
330 : : errmsg("parameter $%d of type %s cannot be coerced to the expected type %s",
331 : : i + 1,
332 : : format_type_be(given_type_id),
333 : : format_type_be(expected_type_id)),
334 : : errhint("You will need to rewrite or cast the expression."),
335 : : parser_errposition(pstate, exprLocation(lfirst(l)))));
336 : :
337 : : /* Take care of collations in the finished expression. */
5285 338 : 8020 : assign_expr_collations(pstate, expr);
339 : :
6752 340 : 8020 : lfirst(l) = expr;
341 : 8020 : i++;
342 : : }
343 : :
344 : : /* Prepare the expressions for execution */
3098 andres@anarazel.de 345 : 7709 : exprstates = ExecPrepareExprList(params, estate);
346 : :
2368 peter@eisentraut.org 347 : 7709 : paramLI = makeParamList(num_params);
348 : :
6752 tgl@sss.pgh.pa.us 349 : 7709 : i = 0;
350 [ + - + + : 15717 : foreach(l, exprstates)
+ + ]
351 : : {
3098 andres@anarazel.de 352 : 8014 : ExprState *n = (ExprState *) lfirst(l);
7077 tgl@sss.pgh.pa.us 353 : 8014 : ParamExternData *prm = ¶mLI->params[i];
354 : :
6752 355 : 8014 : prm->ptype = param_types[i];
5104 356 : 8014 : prm->pflags = PARAM_FLAG_CONST;
7077 357 : 8014 : prm->value = ExecEvalExprSwitchContext(n,
358 [ + + ]: 8014 : GetPerTupleExprContext(estate),
359 : : &prm->isnull);
360 : :
8252 361 : 8008 : i++;
362 : : }
363 : :
364 : 7703 : return paramLI;
365 : : }
366 : :
367 : :
368 : : /*
369 : : * Initialize query hash table upon first use.
370 : : */
371 : : static void
8411 372 : 500 : InitQueryHashTable(void)
373 : : {
374 : : HASHCTL hash_ctl;
375 : :
8160 376 : 500 : hash_ctl.keysize = NAMEDATALEN;
377 : 500 : hash_ctl.entrysize = sizeof(PreparedStatement);
378 : :
8411 379 : 500 : prepared_queries = hash_create("Prepared Queries",
380 : : 32,
381 : : &hash_ctl,
382 : : HASH_ELEM | HASH_STRINGS);
383 : 500 : }
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
8160 392 : 2329 : StorePreparedStatement(const char *stmt_name,
393 : : CachedPlanSource *plansource,
394 : : bool from_sql)
395 : : {
396 : : PreparedStatement *entry;
5104 397 : 2329 : TimestampTz cur_ts = GetCurrentStatementStartTimestamp();
398 : : bool found;
399 : :
400 : : /* Initialize the hash table, if necessary */
8411 401 [ + + ]: 2329 : if (!prepared_queries)
402 : 500 : InitQueryHashTable();
403 : :
404 : : /* Add entry to hash table */
8160 405 : 2329 : entry = (PreparedStatement *) hash_search(prepared_queries,
406 : : stmt_name,
407 : : HASH_ENTER,
408 : : &found);
409 : :
410 : : /* Shouldn't get a duplicate entry */
7405 411 [ + + ]: 2329 : if (found)
5104 412 [ + - ]: 3 : ereport(ERROR,
413 : : (errcode(ERRCODE_DUPLICATE_PSTATEMENT),
414 : : errmsg("prepared statement \"%s\" already exists",
415 : : stmt_name)));
416 : :
417 : : /* Fill in the hash table entry */
6752 418 : 2326 : entry->plansource = plansource;
6773 419 : 2326 : entry->from_sql = from_sql;
5104 420 : 2326 : entry->prepare_time = cur_ts;
421 : :
422 : : /* Now it's safe to move the CachedPlanSource to permanent memory */
423 : 2326 : SaveCachedPlan(plansource);
8411 424 : 2326 : }
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 *
8160 434 : 46012 : FetchPreparedStatement(const char *stmt_name, bool throwError)
435 : : {
436 : : 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 [ + + ]: 46012 : if (prepared_queries)
443 : 46011 : entry = (PreparedStatement *) hash_search(prepared_queries,
444 : : stmt_name,
445 : : HASH_FIND,
446 : : NULL);
447 : : else
448 : 1 : entry = NULL;
449 : :
450 [ + + + + ]: 46012 : if (!entry && throwError)
8084 451 [ + - ]: 5 : ereport(ERROR,
452 : : (errcode(ERRCODE_UNDEFINED_PSTATEMENT),
453 : : errmsg("prepared statement \"%s\" does not exist",
454 : : stmt_name)));
455 : :
8411 456 : 46007 : return entry;
457 : : }
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
8065 bruce@momjian.us 466 : 7990 : FetchPreparedStatementResultDesc(PreparedStatement *stmt)
467 : : {
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 : : */
6752 tgl@sss.pgh.pa.us 472 [ - + ]: 7990 : Assert(stmt->plansource->fixed_result);
473 [ + - ]: 7990 : if (stmt->plansource->resultDesc)
474 : 7990 : return CreateTupleDescCopy(stmt->plansource->resultDesc);
475 : : else
6752 tgl@sss.pgh.pa.us 476 :UIC 0 : return NULL;
477 : : }
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 *
7381 tgl@sss.pgh.pa.us 489 :GIC 7953 : FetchPreparedStatementTargetList(PreparedStatement *stmt)
490 : : {
491 : : List *tlist;
492 : :
493 : : /* Get the plan's primary targetlist */
3081 kgrittn@postgresql.o 494 : 7953 : tlist = CachedPlanGetTargetList(stmt->plansource, NULL);
495 : :
496 : : /* Copy into caller's context in case plan gets invalidated */
3103 peter_e@gmx.net 497 : 7953 : return copyObject(tlist);
498 : : }
499 : :
500 : : /*
501 : : * Implements the 'DEALLOCATE' utility statement: deletes the
502 : : * specified plan from storage.
503 : : */
504 : : void
8411 tgl@sss.pgh.pa.us 505 : 1186 : DeallocateQuery(DeallocateStmt *stmt)
506 : : {
6722 neilc@samurai.com 507 [ + + ]: 1186 : if (stmt->name)
508 : 1150 : DropPreparedStatement(stmt->name, true);
509 : : else
510 : 36 : DropAllPreparedStatements();
8160 tgl@sss.pgh.pa.us 511 : 1186 : }
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 : 1162 : DropPreparedStatement(const char *stmt_name, bool showError)
520 : : {
521 : : PreparedStatement *entry;
522 : :
523 : : /* Find the query's hash table entry; raise error if wanted */
524 : 1162 : entry = FetchPreparedStatement(stmt_name, showError);
525 : :
526 [ + + ]: 1162 : if (entry)
527 : : {
528 : : /* Release the plancache entry */
6752 529 : 1158 : DropCachedPlan(entry->plansource);
530 : :
531 : : /* Now we can remove the hash table entry */
8160 532 : 1158 : hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
533 : : }
8252 534 : 1162 : }
535 : :
536 : : /*
537 : : * Drop all cached statements.
538 : : */
539 : : void
6722 neilc@samurai.com 540 : 39 : DropAllPreparedStatements(void)
541 : : {
542 : : HASH_SEQ_STATUS seq;
543 : : PreparedStatement *entry;
544 : :
545 : : /* nothing cached */
546 [ - + ]: 39 : if (!prepared_queries)
6722 neilc@samurai.com 547 :UIC 0 : return;
548 : :
549 : : /* walk over cache */
6722 neilc@samurai.com 550 :GIC 39 : hash_seq_init(&seq, prepared_queries);
551 [ + + ]: 80 : while ((entry = hash_seq_search(&seq)) != NULL)
552 : : {
553 : : /* Release the plancache entry */
554 : 41 : DropCachedPlan(entry->plansource);
555 : :
556 : : /* Now we can remove the hash table entry */
557 : 41 : hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
558 : : }
559 : : }
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
4919 tgl@sss.pgh.pa.us 571 : 213 : ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
572 : : ParseState *pstate, ParamListInfo params)
573 : : {
574 : : PreparedStatement *entry;
575 : : const char *query_string;
576 : : CachedPlan *cplan;
577 : : List *plan_list;
578 : : ListCell *p;
8252 579 : 213 : ParamListInfo paramLI = NULL;
580 : 213 : EState *estate = NULL;
581 : : instr_time planstart;
582 : : instr_time planduration;
583 : : BufferUsage bufusage_start,
584 : : bufusage;
585 : : MemoryContextCounters mem_counters;
586 alvherre@alvh.no-ip. 586 : 213 : MemoryContext planner_ctx = NULL;
587 : 213 : MemoryContext saved_ctx = NULL;
588 : :
589 [ + + ]: 213 : if (es->memory)
590 : : {
591 : : /* See ExplainOneQuery about this */
592 [ - + ]: 3 : Assert(IsA(CurrentMemoryContext, AllocSetContext));
593 : 3 : planner_ctx = AllocSetContextCreate(CurrentMemoryContext,
594 : : "explain analyze planner context",
595 : : ALLOCSET_DEFAULT_SIZES);
596 : 3 : saved_ctx = MemoryContextSwitchTo(planner_ctx);
597 : : }
598 : :
1981 fujii@postgresql.org 599 [ - + ]: 213 : if (es->buffers)
1981 fujii@postgresql.org 600 :UIC 0 : bufusage_start = pgBufferUsage;
3104 sfrost@snowman.net 601 :GIC 213 : INSTR_TIME_SET_CURRENT(planstart);
602 : :
603 : : /* Look it up in the hash table */
8160 tgl@sss.pgh.pa.us 604 : 213 : entry = FetchPreparedStatement(execstmt->name, true);
605 : :
606 : : /* Shouldn't find a non-fixed-result cached plan */
6752 607 [ - + ]: 213 : if (!entry->plansource->fixed_result)
6752 tgl@sss.pgh.pa.us 608 [ # # ]:UIC 0 : elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans");
609 : :
6091 tgl@sss.pgh.pa.us 610 :GIC 213 : query_string = entry->plansource->query_string;
611 : :
612 : : /* Evaluate parameters, if any */
6752 613 [ + + ]: 213 : if (entry->plansource->num_params)
614 : : {
615 : : ParseState *pstate_params;
616 : :
313 michael@paquier.xyz 617 : 148 : pstate_params = make_parsestate(NULL);
618 : 148 : 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 : : */
8252 tgl@sss.pgh.pa.us 626 : 148 : estate = CreateExecutorState();
7221 627 : 148 : estate->es_param_list_info = params;
628 : :
313 michael@paquier.xyz 629 : 148 : paramLI = EvaluateParams(pstate_params, entry, execstmt->params, estate);
630 : : }
631 : :
632 : : /* Replan if needed, and acquire a transient refcount */
1685 tgl@sss.pgh.pa.us 633 : 213 : cplan = GetCachedPlan(entry->plansource, paramLI,
634 : : CurrentResourceOwner, pstate->p_queryEnv);
635 : :
3104 sfrost@snowman.net 636 : 213 : INSTR_TIME_SET_CURRENT(planduration);
637 : 213 : INSTR_TIME_SUBTRACT(planduration, planstart);
638 : :
586 alvherre@alvh.no-ip. 639 [ + + ]: 213 : if (es->memory)
640 : : {
641 : 3 : MemoryContextSwitchTo(saved_ctx);
642 : 3 : MemoryContextMemConsumed(planner_ctx, &mem_counters);
643 : : }
644 : :
645 : : /* calc differences of buffer counters. */
1981 fujii@postgresql.org 646 [ - + ]: 213 : if (es->buffers)
647 : : {
1981 fujii@postgresql.org 648 :UIC 0 : memset(&bufusage, 0, sizeof(BufferUsage));
649 : 0 : BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start);
650 : : }
651 : :
5104 tgl@sss.pgh.pa.us 652 :GIC 213 : plan_list = cplan->stmt_list;
653 : :
654 : : /* Explain each query */
6773 655 [ + - + + : 426 : foreach(p, plan_list)
+ + ]
656 : : {
3071 657 : 213 : PlannedStmt *pstmt = lfirst_node(PlannedStmt, p);
658 : :
3157 659 [ + - ]: 213 : if (pstmt->commandType != CMD_UTILITY)
107 amitlan@postgresql.o 660 : 426 : ExplainOnePlan(pstmt, into, es, query_string, paramLI, pstate->p_queryEnv,
586 alvherre@alvh.no-ip. 661 [ - + ]: 213 : &planduration, (es->buffers ? &bufusage : NULL),
662 [ + + ]: 213 : es->memory ? &mem_counters : NULL);
663 : : else
313 michael@paquier.xyz 664 :UIC 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 */
2245 tgl@sss.pgh.pa.us 669 [ - + ]:GIC 213 : if (lnext(plan_list, p) != NULL)
5871 tgl@sss.pgh.pa.us 670 :UIC 0 : ExplainSeparatePlans(es);
671 : : }
672 : :
8252 tgl@sss.pgh.pa.us 673 [ + + ]:GIC 213 : if (estate)
674 : 148 : FreeExecutorState(estate);
675 : :
1685 676 : 213 : ReleaseCachedPlan(cplan, CurrentResourceOwner);
8411 677 : 213 : }
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
7181 neilc@samurai.com 685 : 51 : pg_prepared_statement(PG_FUNCTION_ARGS)
686 : : {
6708 tgl@sss.pgh.pa.us 687 : 51 : 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 : : */
1054 michael@paquier.xyz 693 : 51 : InitMaterializedSRF(fcinfo, 0);
694 : :
695 : : /* hash table might be uninitialized */
6708 tgl@sss.pgh.pa.us 696 [ + + ]: 51 : if (prepared_queries)
697 : : {
698 : : HASH_SEQ_STATUS hash_seq;
699 : : PreparedStatement *prep_stmt;
700 : :
701 : 45 : hash_seq_init(&hash_seq, prepared_queries);
702 [ + + ]: 192 : while ((prep_stmt = hash_seq_search(&hash_seq)) != NULL)
703 : : {
704 : : TupleDesc result_desc;
705 : : Datum values[8];
1148 peter@eisentraut.org 706 : 147 : bool nulls[8] = {0};
707 : :
1159 708 : 147 : result_desc = prep_stmt->plansource->resultDesc;
709 : :
6374 tgl@sss.pgh.pa.us 710 : 147 : values[0] = CStringGetTextDatum(prep_stmt->stmt_name);
6259 711 : 147 : values[1] = CStringGetTextDatum(prep_stmt->plansource->query_string);
6708 712 : 147 : values[2] = TimestampTzGetDatum(prep_stmt->prepare_time);
713 : 294 : values[3] = build_regtype_array(prep_stmt->plansource->param_types,
2999 714 : 147 : prep_stmt->plansource->num_params);
1159 peter@eisentraut.org 715 [ + + ]: 147 : if (result_desc)
716 : : {
717 : : Oid *result_types;
718 : :
1090 719 : 144 : result_types = palloc_array(Oid, result_desc->natts);
1159 720 [ + + ]: 489 : for (int i = 0; i < result_desc->natts; i++)
431 drowley@postgresql.o 721 : 345 : result_types[i] = TupleDescAttr(result_desc, i)->atttypid;
1159 peter@eisentraut.org 722 : 144 : values[4] = build_regtype_array(result_types, result_desc->natts);
723 : : }
724 : : else
725 : : {
726 : : /* no result descriptor (for example, DML statement) */
727 : 3 : nulls[4] = true;
728 : : }
729 : 147 : values[5] = BoolGetDatum(prep_stmt->from_sql);
730 : 147 : values[6] = Int64GetDatumFast(prep_stmt->plansource->num_generic_plans);
731 : 147 : values[7] = Int64GetDatumFast(prep_stmt->plansource->num_custom_plans);
732 : :
1279 michael@paquier.xyz 733 : 147 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
734 : : values, nulls);
735 : : }
736 : : }
737 : :
6708 tgl@sss.pgh.pa.us 738 : 51 : return (Datum) 0;
739 : : }
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
6752 747 : 291 : build_regtype_array(Oid *param_types, int num_params)
748 : : {
749 : : Datum *tmp_ary;
750 : : ArrayType *result;
751 : : int i;
752 : :
1090 peter@eisentraut.org 753 : 291 : tmp_ary = palloc_array(Datum, num_params);
754 : :
6752 tgl@sss.pgh.pa.us 755 [ + + ]: 717 : for (i = 0; i < num_params; i++)
756 : 426 : tmp_ary[i] = ObjectIdGetDatum(param_types[i]);
757 : :
1163 peter@eisentraut.org 758 : 291 : result = construct_array_builtin(tmp_ary, num_params, REGTYPEOID);
7173 neilc@samurai.com 759 : 291 : return PointerGetDatum(result);
760 : : }
|