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