2003-02-03 00:46:38 +01:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* explain.c
|
2003-02-03 00:46:38 +01:00
|
|
|
* Explain query execution plans
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2015-01-06 17:43:47 +01:00
|
|
|
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994-5, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2003-02-03 00:46:38 +01:00
|
|
|
* IDENTIFICATION
|
2010-09-20 22:08:53 +02:00
|
|
|
* src/backend/commands/explain.c
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2003-02-03 00:46:38 +01:00
|
|
|
*-------------------------------------------------------------------------
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "postgres.h"
|
1996-11-06 09:21:43 +01:00
|
|
|
|
2006-07-13 18:49:20 +02:00
|
|
|
#include "access/xact.h"
|
2015-01-17 00:18:52 +01:00
|
|
|
#include "catalog/pg_collation.h"
|
2002-03-24 05:31:09 +01:00
|
|
|
#include "catalog/pg_type.h"
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
#include "commands/createas.h"
|
2009-07-27 01:34:18 +02:00
|
|
|
#include "commands/defrem.h"
|
2003-02-03 00:46:38 +01:00
|
|
|
#include "commands/prepare.h"
|
2010-02-01 16:43:36 +01:00
|
|
|
#include "executor/hashjoin.h"
|
2011-02-20 06:17:18 +01:00
|
|
|
#include "foreign/fdwapi.h"
|
2015-01-17 00:18:52 +01:00
|
|
|
#include "nodes/nodeFuncs.h"
|
2002-03-12 01:52:10 +01:00
|
|
|
#include "optimizer/clauses.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "parser/parsetree.h"
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "rewrite/rewriteHandler.h"
|
2007-03-13 01:33:44 +01:00
|
|
|
#include "tcop/tcopprot.h"
|
2002-03-12 01:52:10 +01:00
|
|
|
#include "utils/builtins.h"
|
2012-02-03 18:11:16 +01:00
|
|
|
#include "utils/json.h"
|
2002-03-22 03:56:37 +01:00
|
|
|
#include "utils/lsyscache.h"
|
2011-02-23 18:18:09 +01:00
|
|
|
#include "utils/rel.h"
|
2014-10-08 23:10:47 +02:00
|
|
|
#include "utils/ruleutils.h"
|
2008-03-26 19:48:59 +01:00
|
|
|
#include "utils/snapmgr.h"
|
2011-02-23 18:18:09 +01:00
|
|
|
#include "utils/tuplesort.h"
|
2015-01-17 00:18:52 +01:00
|
|
|
#include "utils/typcache.h"
|
2009-08-10 07:46:50 +02:00
|
|
|
#include "utils/xml.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
|
2002-03-12 01:52:10 +01:00
|
|
|
|
2007-05-25 19:54:25 +02:00
|
|
|
/* Hook for plugins to get control in ExplainOneQuery() */
|
|
|
|
ExplainOneQuery_hook_type ExplainOneQuery_hook = NULL;
|
2007-11-15 22:14:46 +01:00
|
|
|
|
2007-05-25 19:54:25 +02:00
|
|
|
/* Hook for plugins to get control in explain_get_index_name() */
|
|
|
|
explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
|
|
|
|
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/* OR-able flags for ExplainXMLTag() */
|
|
|
|
#define X_OPENING 0
|
|
|
|
#define X_CLOSING 1
|
|
|
|
#define X_CLOSE_IMMEDIATE 2
|
|
|
|
#define X_NOWHITESPACE 4
|
|
|
|
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
|
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
2013-04-13 01:25:20 +02:00
|
|
|
const char *queryString, ParamListInfo params);
|
2007-08-15 23:39:50 +02:00
|
|
|
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainState *es);
|
2005-04-16 22:07:35 +02:00
|
|
|
static double elapsed_time(instr_time *starttime);
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
static void ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used);
|
|
|
|
static void ExplainPreScanMemberNodes(List *plans, PlanState **planstates,
|
|
|
|
Bitmapset **rels_used);
|
|
|
|
static void ExplainPreScanSubPlans(List *plans, Bitmapset **rels_used);
|
2010-07-13 22:57:19 +02:00
|
|
|
static void ExplainNode(PlanState *planstate, List *ancestors,
|
2010-02-26 03:01:40 +01:00
|
|
|
const char *relationship, const char *plan_name,
|
|
|
|
ExplainState *es);
|
2010-07-13 22:57:19 +02:00
|
|
|
static void show_plan_tlist(PlanState *planstate, List *ancestors,
|
2011-04-10 17:42:00 +02:00
|
|
|
ExplainState *es);
|
2010-08-24 23:20:36 +02:00
|
|
|
static void show_expression(Node *node, const char *qlabel,
|
|
|
|
PlanState *planstate, List *ancestors,
|
|
|
|
bool useprefix, ExplainState *es);
|
2010-07-13 22:57:19 +02:00
|
|
|
static void show_qual(List *qual, const char *qlabel,
|
2011-04-10 17:42:00 +02:00
|
|
|
PlanState *planstate, List *ancestors,
|
|
|
|
bool useprefix, ExplainState *es);
|
2005-04-25 03:30:14 +02:00
|
|
|
static void show_scan_qual(List *qual, const char *qlabel,
|
2011-04-10 17:42:00 +02:00
|
|
|
PlanState *planstate, List *ancestors,
|
|
|
|
ExplainState *es);
|
2010-07-13 22:57:19 +02:00
|
|
|
static void show_upper_qual(List *qual, const char *qlabel,
|
2011-04-10 17:42:00 +02:00
|
|
|
PlanState *planstate, List *ancestors,
|
|
|
|
ExplainState *es);
|
2010-07-13 22:57:19 +02:00
|
|
|
static void show_sort_keys(SortState *sortstate, List *ancestors,
|
2011-04-10 17:42:00 +02:00
|
|
|
ExplainState *es);
|
2010-10-14 22:56:39 +02:00
|
|
|
static void show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
|
2011-04-10 17:42:00 +02:00
|
|
|
ExplainState *es);
|
2013-12-12 17:24:38 +01:00
|
|
|
static void show_agg_keys(AggState *astate, List *ancestors,
|
|
|
|
ExplainState *es);
|
|
|
|
static void show_group_keys(GroupState *gstate, List *ancestors,
|
|
|
|
ExplainState *es);
|
|
|
|
static void show_sort_group_keys(PlanState *planstate, const char *qlabel,
|
|
|
|
int nkeys, AttrNumber *keycols,
|
2015-01-17 00:18:52 +01:00
|
|
|
Oid *sortOperators, Oid *collations, bool *nullsFirst,
|
2013-12-12 17:24:38 +01:00
|
|
|
List *ancestors, ExplainState *es);
|
2015-01-17 00:18:52 +01:00
|
|
|
static void show_sortorder_options(StringInfo buf, Node *sortexpr,
|
|
|
|
Oid sortOperator, Oid collation, bool nullsFirst);
|
2009-08-10 07:46:50 +02:00
|
|
|
static void show_sort_info(SortState *sortstate, ExplainState *es);
|
2010-02-01 16:43:36 +01:00
|
|
|
static void show_hash_info(HashState *hashstate, ExplainState *es);
|
2014-01-13 20:42:16 +01:00
|
|
|
static void show_tidbitmap_info(BitmapHeapScanState *planstate,
|
2014-05-06 18:12:18 +02:00
|
|
|
ExplainState *es);
|
2011-09-22 17:29:18 +02:00
|
|
|
static void show_instrumentation_count(const char *qlabel, int which,
|
|
|
|
PlanState *planstate, ExplainState *es);
|
2011-02-20 06:17:18 +01:00
|
|
|
static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es);
|
2007-05-25 19:54:25 +02:00
|
|
|
static const char *explain_get_index_name(Oid indexId);
|
2011-10-11 20:20:06 +02:00
|
|
|
static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
|
|
|
|
ExplainState *es);
|
2009-07-24 23:08:42 +02:00
|
|
|
static void ExplainScanTarget(Scan *plan, ExplainState *es);
|
2011-03-01 17:32:13 +01:00
|
|
|
static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
|
|
|
|
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
|
2013-03-10 19:14:53 +01:00
|
|
|
static void show_modifytable_info(ModifyTableState *mtstate, ExplainState *es);
|
2010-07-13 22:57:19 +02:00
|
|
|
static void ExplainMemberNodes(List *plans, PlanState **planstates,
|
|
|
|
List *ancestors, ExplainState *es);
|
|
|
|
static void ExplainSubPlans(List *plans, List *ancestors,
|
2011-04-10 17:42:00 +02:00
|
|
|
const char *relationship, ExplainState *es);
|
2009-08-10 07:46:50 +02:00
|
|
|
static void ExplainProperty(const char *qlabel, const char *value,
|
2010-02-26 03:01:40 +01:00
|
|
|
bool numeric, ExplainState *es);
|
2009-08-10 07:46:50 +02:00
|
|
|
static void ExplainOpenGroup(const char *objtype, const char *labelname,
|
|
|
|
bool labeled, ExplainState *es);
|
|
|
|
static void ExplainCloseGroup(const char *objtype, const char *labelname,
|
2010-02-26 03:01:40 +01:00
|
|
|
bool labeled, ExplainState *es);
|
2009-08-10 07:46:50 +02:00
|
|
|
static void ExplainDummyGroup(const char *objtype, const char *labelname,
|
2010-02-26 03:01:40 +01:00
|
|
|
ExplainState *es);
|
2009-08-10 07:46:50 +02:00
|
|
|
static void ExplainXMLTag(const char *tagname, int flags, ExplainState *es);
|
|
|
|
static void ExplainJSONLineEnding(ExplainState *es);
|
2009-12-11 02:33:35 +01:00
|
|
|
static void ExplainYAMLLineStarting(ExplainState *es);
|
|
|
|
static void escape_yaml(StringInfo buf, const char *str);
|
2010-02-16 21:07:13 +01:00
|
|
|
|
2007-05-25 19:54:25 +02:00
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* ExplainQuery -
|
2002-03-24 05:31:09 +01:00
|
|
|
* execute an EXPLAIN command
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
void
|
2007-03-13 01:33:44 +01:00
|
|
|
ExplainQuery(ExplainStmt *stmt, const char *queryString,
|
|
|
|
ParamListInfo params, DestReceiver *dest)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2015-01-15 19:39:33 +01:00
|
|
|
ExplainState *es = NewExplainState();
|
2002-07-20 07:49:28 +02:00
|
|
|
TupOutputState *tstate;
|
1999-05-25 18:15:34 +02:00
|
|
|
List *rewritten;
|
2009-07-27 01:34:18 +02:00
|
|
|
ListCell *lc;
|
2012-06-10 21:20:04 +02:00
|
|
|
bool timing_set = false;
|
2009-07-27 01:34:18 +02:00
|
|
|
|
|
|
|
/* Parse options list. */
|
|
|
|
foreach(lc, stmt->options)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
DefElem *opt = (DefElem *) lfirst(lc);
|
2009-07-27 01:34:18 +02:00
|
|
|
|
|
|
|
if (strcmp(opt->defname, "analyze") == 0)
|
2015-01-15 19:39:33 +01:00
|
|
|
es->analyze = defGetBoolean(opt);
|
2009-07-27 01:34:18 +02:00
|
|
|
else if (strcmp(opt->defname, "verbose") == 0)
|
2015-01-15 19:39:33 +01:00
|
|
|
es->verbose = defGetBoolean(opt);
|
2009-07-27 01:34:18 +02:00
|
|
|
else if (strcmp(opt->defname, "costs") == 0)
|
2015-01-15 19:39:33 +01:00
|
|
|
es->costs = defGetBoolean(opt);
|
2009-12-15 05:57:48 +01:00
|
|
|
else if (strcmp(opt->defname, "buffers") == 0)
|
2015-01-15 19:39:33 +01:00
|
|
|
es->buffers = defGetBoolean(opt);
|
2012-02-07 17:23:04 +01:00
|
|
|
else if (strcmp(opt->defname, "timing") == 0)
|
|
|
|
{
|
|
|
|
timing_set = true;
|
2015-01-15 19:39:33 +01:00
|
|
|
es->timing = defGetBoolean(opt);
|
2012-02-07 17:23:04 +01:00
|
|
|
}
|
2009-08-10 07:46:50 +02:00
|
|
|
else if (strcmp(opt->defname, "format") == 0)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
char *p = defGetString(opt);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
if (strcmp(p, "text") == 0)
|
2015-01-15 19:39:33 +01:00
|
|
|
es->format = EXPLAIN_FORMAT_TEXT;
|
2009-08-10 07:46:50 +02:00
|
|
|
else if (strcmp(p, "xml") == 0)
|
2015-01-15 19:39:33 +01:00
|
|
|
es->format = EXPLAIN_FORMAT_XML;
|
2009-08-10 07:46:50 +02:00
|
|
|
else if (strcmp(p, "json") == 0)
|
2015-01-15 19:39:33 +01:00
|
|
|
es->format = EXPLAIN_FORMAT_JSON;
|
2009-12-11 02:33:35 +01:00
|
|
|
else if (strcmp(p, "yaml") == 0)
|
2015-01-15 19:39:33 +01:00
|
|
|
es->format = EXPLAIN_FORMAT_YAML;
|
2009-08-10 07:46:50 +02:00
|
|
|
else
|
|
|
|
ereport(ERROR,
|
2010-02-26 03:01:40 +01:00
|
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
|
|
errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
|
|
|
|
opt->defname, p)));
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
2009-07-27 01:34:18 +02:00
|
|
|
else
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
|
|
errmsg("unrecognized EXPLAIN option \"%s\"",
|
|
|
|
opt->defname)));
|
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
|
2015-01-15 19:39:33 +01:00
|
|
|
if (es->buffers && !es->analyze)
|
2009-12-15 05:57:48 +01:00
|
|
|
ereport(ERROR,
|
2010-02-26 03:01:40 +01:00
|
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
|
|
errmsg("EXPLAIN option BUFFERS requires ANALYZE")));
|
2012-06-10 21:20:04 +02:00
|
|
|
|
2012-02-07 17:23:04 +01:00
|
|
|
/* if the timing was not set explicitly, set default value */
|
2015-01-15 19:39:33 +01:00
|
|
|
es->timing = (timing_set) ? es->timing : es->analyze;
|
2012-02-07 17:23:04 +01:00
|
|
|
|
|
|
|
/* check that timing is used with EXPLAIN ANALYZE */
|
2015-01-15 19:39:33 +01:00
|
|
|
if (es->timing && !es->analyze)
|
2012-02-07 17:23:04 +01:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
|
|
errmsg("EXPLAIN option TIMING requires ANALYZE")));
|
2009-12-15 05:57:48 +01:00
|
|
|
|
2014-10-16 00:50:13 +02:00
|
|
|
/* currently, summary option is not exposed to users; just set it */
|
2015-01-15 19:39:33 +01:00
|
|
|
es->summary = es->analyze;
|
2014-10-16 00:50:13 +02:00
|
|
|
|
2004-12-12 21:17:06 +01:00
|
|
|
/*
|
2010-01-15 23:36:35 +01:00
|
|
|
* Parse analysis was done already, but we still have to run the rule
|
2010-02-26 03:01:40 +01:00
|
|
|
* rewriter. We do not do AcquireRewriteLocks: we assume the query either
|
|
|
|
* came straight from the parser, or suitable locks were acquired by
|
|
|
|
* plancache.c.
|
2007-03-13 01:33:44 +01:00
|
|
|
*
|
2010-01-15 23:36:35 +01:00
|
|
|
* Because the rewriter and planner tend to scribble on the input, we make
|
2014-05-06 18:12:18 +02:00
|
|
|
* a preliminary copy of the source querytree. This prevents problems in
|
2007-11-15 22:14:46 +01:00
|
|
|
* the case that the EXPLAIN is in a portal or plpgsql function and is
|
|
|
|
* executed repeatedly. (See also the same hack in DECLARE CURSOR and
|
|
|
|
* PREPARE.) XXX FIXME someday.
|
2004-12-12 21:17:06 +01:00
|
|
|
*/
|
2010-01-15 23:36:35 +01:00
|
|
|
Assert(IsA(stmt->query, Query));
|
|
|
|
rewritten = QueryRewrite((Query *) copyObject(stmt->query));
|
2004-12-12 21:17:06 +01:00
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/* emit opening boilerplate */
|
2015-01-15 19:39:33 +01:00
|
|
|
ExplainBeginOutput(es);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
if (rewritten == NIL)
|
1999-05-10 01:31:47 +02:00
|
|
|
{
|
2009-08-10 07:46:50 +02:00
|
|
|
/*
|
|
|
|
* In the case of an INSTEAD NOTHING, tell at least that. But in
|
|
|
|
* non-text format, the output is delimited, so this isn't necessary.
|
|
|
|
*/
|
2015-01-15 19:39:33 +01:00
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
appendStringInfoString(es->str, "Query rewrites to nothing\n");
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2002-03-24 05:31:09 +01:00
|
|
|
else
|
1998-10-21 18:21:29 +02:00
|
|
|
{
|
2009-07-27 01:34:18 +02:00
|
|
|
ListCell *l;
|
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Explain every plan */
|
|
|
|
foreach(l, rewritten)
|
2002-03-24 05:31:09 +01:00
|
|
|
{
|
2015-01-15 19:39:33 +01:00
|
|
|
ExplainOneQuery((Query *) lfirst(l), NULL, es,
|
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
2013-04-13 01:25:20 +02:00
|
|
|
queryString, params);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
/* Separate plans with an appropriate separator */
|
2007-03-13 01:33:44 +01:00
|
|
|
if (lnext(l) != NULL)
|
2015-01-15 19:39:33 +01:00
|
|
|
ExplainSeparatePlans(es);
|
2002-03-24 05:31:09 +01:00
|
|
|
}
|
1998-10-21 18:21:29 +02:00
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/* emit closing boilerplate */
|
2015-01-15 19:39:33 +01:00
|
|
|
ExplainEndOutput(es);
|
|
|
|
Assert(es->indent == 0);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
2009-07-27 01:34:18 +02:00
|
|
|
/* output tuples */
|
|
|
|
tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
|
2015-01-15 19:39:33 +01:00
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
do_text_output_multiline(tstate, es->str->data);
|
2009-08-10 07:46:50 +02:00
|
|
|
else
|
2015-01-15 19:39:33 +01:00
|
|
|
do_text_output_oneline(tstate, es->str->data);
|
2002-07-20 07:49:28 +02:00
|
|
|
end_tup_output(tstate);
|
2009-07-27 01:34:18 +02:00
|
|
|
|
2015-01-15 19:39:33 +01:00
|
|
|
pfree(es->str->data);
|
2009-07-27 01:34:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2015-01-15 19:39:33 +01:00
|
|
|
* Create a new ExplainState struct initialized with default options.
|
2009-07-27 01:34:18 +02:00
|
|
|
*/
|
2015-01-15 19:39:33 +01:00
|
|
|
ExplainState *
|
|
|
|
NewExplainState(void)
|
2009-07-27 01:34:18 +02:00
|
|
|
{
|
2015-01-15 19:39:33 +01:00
|
|
|
ExplainState *es = (ExplainState *) palloc0(sizeof(ExplainState));
|
|
|
|
|
|
|
|
/* Set default options (most fields can be left as zeroes). */
|
2009-07-27 01:34:18 +02:00
|
|
|
es->costs = true;
|
|
|
|
/* Prepare output buffer. */
|
|
|
|
es->str = makeStringInfo();
|
2015-01-15 19:39:33 +01:00
|
|
|
|
|
|
|
return es;
|
1998-10-21 18:21:29 +02:00
|
|
|
}
|
|
|
|
|
2003-05-06 22:26:28 +02:00
|
|
|
/*
|
|
|
|
* ExplainResultDesc -
|
|
|
|
* construct the result tupledesc for an EXPLAIN
|
|
|
|
*/
|
|
|
|
TupleDesc
|
|
|
|
ExplainResultDesc(ExplainStmt *stmt)
|
|
|
|
{
|
|
|
|
TupleDesc tupdesc;
|
2009-08-10 07:46:50 +02:00
|
|
|
ListCell *lc;
|
2012-01-31 17:48:23 +01:00
|
|
|
Oid result_type = TEXTOID;
|
2003-05-06 22:26:28 +02:00
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/* Check for XML format option */
|
|
|
|
foreach(lc, stmt->options)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
DefElem *opt = (DefElem *) lfirst(lc);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
if (strcmp(opt->defname, "format") == 0)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
char *p = defGetString(opt);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
2012-01-31 17:48:23 +01:00
|
|
|
if (strcmp(p, "xml") == 0)
|
|
|
|
result_type = XMLOID;
|
|
|
|
else if (strcmp(p, "json") == 0)
|
|
|
|
result_type = JSONOID;
|
|
|
|
else
|
|
|
|
result_type = TEXTOID;
|
2010-01-15 23:36:35 +01:00
|
|
|
/* don't "break", as ExplainQuery will use the last value */
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Need a tuple descriptor representing a single TEXT or XML column */
|
2003-05-06 22:26:28 +02:00
|
|
|
tupdesc = CreateTemplateTupleDesc(1, false);
|
|
|
|
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "QUERY PLAN",
|
2012-01-31 17:48:23 +01:00
|
|
|
result_type, -1, 0);
|
2003-05-06 22:26:28 +02:00
|
|
|
return tupdesc;
|
|
|
|
}
|
|
|
|
|
1998-10-21 18:21:29 +02:00
|
|
|
/*
|
|
|
|
* ExplainOneQuery -
|
2007-03-13 01:33:44 +01:00
|
|
|
* print out the execution plan for one Query
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
*
|
|
|
|
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
|
1998-10-21 18:21:29 +02:00
|
|
|
*/
|
|
|
|
static void
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
|
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
2013-04-13 01:25:20 +02:00
|
|
|
const char *queryString, ParamListInfo params)
|
1998-10-21 18:21:29 +02:00
|
|
|
{
|
2001-01-27 02:41:19 +01:00
|
|
|
/* planner will not cope with utility statements */
|
|
|
|
if (query->commandType == CMD_UTILITY)
|
|
|
|
{
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
ExplainOneUtility(query->utilityStmt, into, es, queryString, params);
|
2007-03-13 01:33:44 +01:00
|
|
|
return;
|
2001-01-27 02:41:19 +01:00
|
|
|
}
|
|
|
|
|
2007-05-25 19:54:25 +02:00
|
|
|
/* if an advisor plugin is present, let it manage things */
|
|
|
|
if (ExplainOneQuery_hook)
|
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
2013-04-13 01:25:20 +02:00
|
|
|
(*ExplainOneQuery_hook) (query, into, es, queryString, params);
|
2007-05-25 19:54:25 +02:00
|
|
|
else
|
|
|
|
{
|
2014-05-06 18:12:18 +02:00
|
|
|
PlannedStmt *plan;
|
|
|
|
instr_time planstart,
|
|
|
|
planduration;
|
2014-01-29 22:04:19 +01:00
|
|
|
|
|
|
|
INSTR_TIME_SET_CURRENT(planstart);
|
2005-10-21 18:43:33 +02:00
|
|
|
|
2007-05-25 19:54:25 +02:00
|
|
|
/* plan the query */
|
2008-08-19 20:30:04 +02:00
|
|
|
plan = pg_plan_query(query, 0, params);
|
2002-12-05 16:50:39 +01:00
|
|
|
|
2014-01-29 22:04:19 +01:00
|
|
|
INSTR_TIME_SET_CURRENT(planduration);
|
|
|
|
INSTR_TIME_SUBTRACT(planduration, planstart);
|
|
|
|
|
2007-05-25 19:54:25 +02:00
|
|
|
/* run it (if needed) and produce output */
|
2014-01-29 22:04:19 +01:00
|
|
|
ExplainOnePlan(plan, into, es, queryString, params, &planduration);
|
2007-05-25 19:54:25 +02:00
|
|
|
}
|
2003-02-03 00:46:38 +01:00
|
|
|
}
|
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/*
|
|
|
|
* ExplainOneUtility -
|
|
|
|
* print out the execution plan for one utility statement
|
|
|
|
* (In general, utility statements don't have plans, but there are some
|
|
|
|
* we treat as special cases)
|
|
|
|
*
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt.
|
|
|
|
*
|
2007-03-13 01:33:44 +01:00
|
|
|
* This is exported because it's called back from prepare.c in the
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
* EXPLAIN EXECUTE case.
|
2007-03-13 01:33:44 +01:00
|
|
|
*/
|
|
|
|
void
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
|
2009-07-27 01:34:18 +02:00
|
|
|
const char *queryString, ParamListInfo params)
|
2007-03-13 01:33:44 +01:00
|
|
|
{
|
|
|
|
if (utilityStmt == NULL)
|
|
|
|
return;
|
|
|
|
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
if (IsA(utilityStmt, CreateTableAsStmt))
|
|
|
|
{
|
|
|
|
/*
|
2012-06-10 21:20:04 +02:00
|
|
|
* We have to rewrite the contained SELECT and then pass it back to
|
|
|
|
* ExplainOneQuery. It's probably not really necessary to copy the
|
|
|
|
* contained parsetree another time, but let's be safe.
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
*/
|
|
|
|
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
|
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
2013-04-13 01:25:20 +02:00
|
|
|
List *rewritten;
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
|
|
|
|
Assert(IsA(ctas->query, Query));
|
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
2013-04-13 01:25:20 +02:00
|
|
|
rewritten = QueryRewrite((Query *) copyObject(ctas->query));
|
|
|
|
Assert(list_length(rewritten) == 1);
|
|
|
|
ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
|
|
|
|
queryString, params);
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
}
|
|
|
|
else if (IsA(utilityStmt, ExecuteStmt))
|
|
|
|
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
|
2009-07-27 01:34:18 +02:00
|
|
|
queryString, params);
|
2007-03-13 01:33:44 +01:00
|
|
|
else if (IsA(utilityStmt, NotifyStmt))
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
appendStringInfoString(es->str, "NOTIFY\n");
|
|
|
|
else
|
|
|
|
ExplainDummyGroup("Notify", NULL, es);
|
|
|
|
}
|
2007-03-13 01:33:44 +01:00
|
|
|
else
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
appendStringInfoString(es->str,
|
2010-02-26 03:01:40 +01:00
|
|
|
"Utility statements have no plan structure\n");
|
2009-08-10 07:46:50 +02:00
|
|
|
else
|
|
|
|
ExplainDummyGroup("Utility Statement", NULL, es);
|
|
|
|
}
|
2007-03-13 01:33:44 +01:00
|
|
|
}
|
|
|
|
|
2003-02-03 00:46:38 +01:00
|
|
|
/*
|
|
|
|
* ExplainOnePlan -
|
|
|
|
* given a planned query, execute it if needed, and then print
|
|
|
|
* EXPLAIN output
|
|
|
|
*
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
* "into" is NULL unless we are explaining the contents of a CreateTableAsStmt,
|
|
|
|
* in which case executing the query should result in creating that table.
|
|
|
|
*
|
2007-04-28 00:05:49 +02:00
|
|
|
* Since we ignore any DeclareCursorStmt that might be attached to the query,
|
|
|
|
* if you say EXPLAIN ANALYZE DECLARE CURSOR then we'll actually run the
|
|
|
|
* query. This is different from pre-8.3 behavior but seems more useful than
|
|
|
|
* not running the query. No cursor will be created, however.
|
|
|
|
*
|
2003-02-03 00:46:38 +01:00
|
|
|
* This is exported because it's called back from prepare.c in the
|
2007-05-25 19:54:25 +02:00
|
|
|
* EXPLAIN EXECUTE case, and because an index advisor plugin would need
|
|
|
|
* to call it.
|
2003-02-03 00:46:38 +01:00
|
|
|
*/
|
|
|
|
void
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
|
2014-01-29 22:04:19 +01:00
|
|
|
const char *queryString, ParamListInfo params,
|
|
|
|
const instr_time *planduration)
|
2003-02-03 00:46:38 +01:00
|
|
|
{
|
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
2013-04-13 01:25:20 +02:00
|
|
|
DestReceiver *dest;
|
2007-05-25 19:54:25 +02:00
|
|
|
QueryDesc *queryDesc;
|
2005-10-15 04:49:52 +02:00
|
|
|
instr_time starttime;
|
2003-02-03 00:46:38 +01:00
|
|
|
double totaltime = 0;
|
2006-02-28 05:10:28 +01:00
|
|
|
int eflags;
|
2009-12-15 05:57:48 +01:00
|
|
|
int instrument_option = 0;
|
|
|
|
|
2012-02-07 17:23:04 +01:00
|
|
|
if (es->analyze && es->timing)
|
2009-12-15 05:57:48 +01:00
|
|
|
instrument_option |= INSTRUMENT_TIMER;
|
2012-02-07 17:23:04 +01:00
|
|
|
else if (es->analyze)
|
|
|
|
instrument_option |= INSTRUMENT_ROWS;
|
|
|
|
|
2009-12-15 05:57:48 +01:00
|
|
|
if (es->buffers)
|
|
|
|
instrument_option |= INSTRUMENT_BUFFERS;
|
2003-02-03 00:46:38 +01:00
|
|
|
|
2013-05-20 04:03:32 +02:00
|
|
|
/*
|
2013-05-29 22:58:43 +02:00
|
|
|
* We always collect timing for the entire statement, even when node-level
|
2014-10-16 00:50:13 +02:00
|
|
|
* timing is off, so we don't look at es->timing here. (We could skip
|
|
|
|
* this if !es->summary, but it's hardly worth the complication.)
|
2013-05-20 04:03:32 +02:00
|
|
|
*/
|
2011-02-27 19:43:29 +01:00
|
|
|
INSTR_TIME_SET_CURRENT(starttime);
|
|
|
|
|
2007-05-25 19:54:25 +02:00
|
|
|
/*
|
2008-05-12 22:02:02 +02:00
|
|
|
* Use a snapshot with an updated command ID to ensure this query sees
|
|
|
|
* results of any previously executed queries.
|
2007-05-25 19:54:25 +02:00
|
|
|
*/
|
2011-03-01 05:27:18 +01:00
|
|
|
PushCopiedSnapshot(GetActiveSnapshot());
|
|
|
|
UpdateActiveSnapshotCommandId();
|
2007-05-25 19:54:25 +02:00
|
|
|
|
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
2013-04-13 01:25:20 +02:00
|
|
|
/*
|
|
|
|
* Normally we discard the query's output, but if explaining CREATE TABLE
|
|
|
|
* AS, we'd better use the appropriate tuple receiver.
|
|
|
|
*/
|
|
|
|
if (into)
|
|
|
|
dest = CreateIntoRelDestReceiver(into);
|
|
|
|
else
|
|
|
|
dest = None_Receiver;
|
|
|
|
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
/* Create a QueryDesc for the query */
|
2009-01-02 21:42:00 +01:00
|
|
|
queryDesc = CreateQueryDesc(plannedstmt, queryString,
|
2008-05-12 22:02:02 +02:00
|
|
|
GetActiveSnapshot(), InvalidSnapshot,
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
dest, params, instrument_option);
|
2007-05-25 19:54:25 +02:00
|
|
|
|
2006-02-28 05:10:28 +01:00
|
|
|
/* Select execution options */
|
2009-07-27 01:34:18 +02:00
|
|
|
if (es->analyze)
|
2006-02-28 05:10:28 +01:00
|
|
|
eflags = 0; /* default run-to-completion flags */
|
|
|
|
else
|
|
|
|
eflags = EXEC_FLAG_EXPLAIN_ONLY;
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
if (into)
|
|
|
|
eflags |= GetIntoRelEFlags(into);
|
2006-02-28 05:10:28 +01:00
|
|
|
|
2002-12-05 16:50:39 +01:00
|
|
|
/* call ExecutorStart to prepare the plan for execution */
|
2006-02-28 05:10:28 +01:00
|
|
|
ExecutorStart(queryDesc, eflags);
|
2002-12-05 16:50:39 +01:00
|
|
|
|
2001-09-18 03:59:07 +02:00
|
|
|
/* Execute the plan for statistics if asked for */
|
2009-07-27 01:34:18 +02:00
|
|
|
if (es->analyze)
|
2001-09-18 03:59:07 +02:00
|
|
|
{
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
ScanDirection dir;
|
|
|
|
|
|
|
|
/* EXPLAIN ANALYZE CREATE TABLE AS WITH NO DATA is weird */
|
|
|
|
if (into && into->skipData)
|
|
|
|
dir = NoMovementScanDirection;
|
|
|
|
else
|
|
|
|
dir = ForwardScanDirection;
|
|
|
|
|
2002-12-05 16:50:39 +01:00
|
|
|
/* run the plan */
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
2012-03-20 02:37:19 +01:00
|
|
|
ExecutorRun(queryDesc, dir, 0L);
|
2001-09-18 03:59:07 +02:00
|
|
|
|
2011-02-27 19:43:29 +01:00
|
|
|
/* run cleanup too */
|
|
|
|
ExecutorFinish(queryDesc);
|
|
|
|
|
|
|
|
/* We can't run ExecutorEnd 'till we're done printing the stats... */
|
2002-12-05 16:50:39 +01:00
|
|
|
totaltime += elapsed_time(&starttime);
|
2001-09-18 03:59:07 +02:00
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainOpenGroup("Query", NULL, true, es);
|
|
|
|
|
2008-11-19 02:10:24 +01:00
|
|
|
/* Create textual dump of plan tree */
|
2009-07-27 01:34:18 +02:00
|
|
|
ExplainPrintPlan(es, queryDesc);
|
2002-12-05 16:50:39 +01:00
|
|
|
|
2014-10-16 00:50:13 +02:00
|
|
|
if (es->summary && planduration)
|
2014-01-29 22:04:19 +01:00
|
|
|
{
|
2014-05-06 18:12:18 +02:00
|
|
|
double plantime = INSTR_TIME_GET_DOUBLE(*planduration);
|
2014-01-29 22:04:19 +01:00
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
appendStringInfo(es->str, "Planning time: %.3f ms\n",
|
|
|
|
1000.0 * plantime);
|
|
|
|
else
|
|
|
|
ExplainPropertyFloat("Planning Time", 1000.0 * plantime, 3, es);
|
|
|
|
}
|
|
|
|
|
2005-03-25 22:58:00 +01:00
|
|
|
/* Print info about runtime of triggers */
|
2009-07-27 01:34:18 +02:00
|
|
|
if (es->analyze)
|
2014-01-20 21:12:50 +01:00
|
|
|
ExplainPrintTriggers(es, queryDesc);
|
2005-03-25 22:58:00 +01:00
|
|
|
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Close down the query and free resources. Include time for this in the
|
2014-04-17 02:48:51 +02:00
|
|
|
* total execution time (although it should be pretty minimal).
|
2002-12-05 16:50:39 +01:00
|
|
|
*/
|
2005-03-20 23:27:52 +01:00
|
|
|
INSTR_TIME_SET_CURRENT(starttime);
|
2001-09-18 03:59:07 +02:00
|
|
|
|
2002-12-05 16:50:39 +01:00
|
|
|
ExecutorEnd(queryDesc);
|
2004-09-10 20:40:09 +02:00
|
|
|
|
2002-12-15 17:17:59 +01:00
|
|
|
FreeQueryDesc(queryDesc);
|
|
|
|
|
2008-05-12 22:02:02 +02:00
|
|
|
PopActiveSnapshot();
|
|
|
|
|
2004-09-13 22:10:13 +02:00
|
|
|
/* We need a CCI just in case query expanded to multiple plans */
|
2009-07-27 01:34:18 +02:00
|
|
|
if (es->analyze)
|
2004-09-13 22:10:13 +02:00
|
|
|
CommandCounterIncrement();
|
2002-12-05 16:50:39 +01:00
|
|
|
|
|
|
|
totaltime += elapsed_time(&starttime);
|
|
|
|
|
2014-10-16 00:50:13 +02:00
|
|
|
if (es->summary)
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
2014-04-17 02:48:51 +02:00
|
|
|
appendStringInfo(es->str, "Execution time: %.3f ms\n",
|
2009-08-10 07:46:50 +02:00
|
|
|
1000.0 * totaltime);
|
|
|
|
else
|
2014-04-17 02:48:51 +02:00
|
|
|
ExplainPropertyFloat("Execution Time", 1000.0 * totaltime,
|
2009-08-10 07:46:50 +02:00
|
|
|
3, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExplainCloseGroup("Query", NULL, true, es);
|
2008-11-19 02:10:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ExplainPrintPlan -
|
2009-07-27 01:34:18 +02:00
|
|
|
* convert a QueryDesc's plan tree to text and append it to es->str
|
2008-11-19 02:10:24 +01:00
|
|
|
*
|
2009-07-27 01:34:18 +02:00
|
|
|
* The caller should have set up the options fields of *es, as well as
|
2014-05-06 18:12:18 +02:00
|
|
|
* initializing the output buffer es->str. Other fields in *es are
|
2009-07-27 01:34:18 +02:00
|
|
|
* initialized here.
|
2008-11-19 02:10:24 +01:00
|
|
|
*
|
|
|
|
* NB: will not work on utility statements
|
|
|
|
*/
|
|
|
|
void
|
2009-07-27 01:34:18 +02:00
|
|
|
ExplainPrintPlan(ExplainState *es, QueryDesc *queryDesc)
|
2008-11-19 02:10:24 +01:00
|
|
|
{
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
Bitmapset *rels_used = NULL;
|
|
|
|
|
2008-11-19 02:10:24 +01:00
|
|
|
Assert(queryDesc->plannedstmt != NULL);
|
2009-07-27 01:34:18 +02:00
|
|
|
es->pstmt = queryDesc->plannedstmt;
|
|
|
|
es->rtable = queryDesc->plannedstmt->rtable;
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
ExplainPreScanNode(queryDesc->planstate, &rels_used);
|
|
|
|
es->rtable_names = select_rtable_names_for_explain(es->rtable, rels_used);
|
Improve performance of EXPLAIN with large range tables.
As of 9.3, ruleutils.c goes to some lengths to ensure that table and column
aliases used in its output are unique. Of course this takes more time than
was required before, which in itself isn't fatal. However, EXPLAIN was set
up so that recalculation of the unique aliases was repeated for each
subexpression printed in a plan. That results in O(N^2) time and memory
consumption for large plan trees, which did not happen in older branches.
Fortunately, the expensive work is the same across a whole plan tree,
so there is no need to repeat it; we can do most of the initialization
just once per query and re-use it for each subexpression. This buys
back most (not all) of the performance loss since 9.2.
We need an extra ExplainState field to hold the precalculated deparse
context. That's no problem in HEAD, but in the back branches, expanding
sizeof(ExplainState) seems risky because third-party extensions might
have local variables of that struct type. So, in 9.4 and 9.3, introduce
an auxiliary struct to keep sizeof(ExplainState) the same. We should
refactor the APIs to avoid such local variables in future, but that's
material for a separate HEAD-only commit.
Per gripe from Alexey Bashtanov. Back-patch to 9.3 where the issue
was introduced.
2015-01-15 19:18:12 +01:00
|
|
|
es->deparse_cxt = deparse_context_for_plan_rtable(es->rtable,
|
|
|
|
es->rtable_names);
|
2010-07-13 22:57:19 +02:00
|
|
|
ExplainNode(queryDesc->planstate, NIL, NULL, NULL, es);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
2014-01-20 21:12:50 +01:00
|
|
|
/*
|
|
|
|
* ExplainPrintTriggers -
|
|
|
|
* convert a QueryDesc's trigger statistics to text and append it to
|
|
|
|
* es->str
|
|
|
|
*
|
|
|
|
* The caller should have set up the options fields of *es, as well as
|
2014-05-06 18:12:18 +02:00
|
|
|
* initializing the output buffer es->str. Other fields in *es are
|
2014-01-20 21:12:50 +01:00
|
|
|
* initialized here.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ExplainPrintTriggers(ExplainState *es, QueryDesc *queryDesc)
|
|
|
|
{
|
|
|
|
ResultRelInfo *rInfo;
|
|
|
|
bool show_relname;
|
|
|
|
int numrels = queryDesc->estate->es_num_result_relations;
|
|
|
|
List *targrels = queryDesc->estate->es_trig_target_relations;
|
|
|
|
int nr;
|
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
ExplainOpenGroup("Triggers", "Triggers", false, es);
|
|
|
|
|
|
|
|
show_relname = (numrels > 1 || targrels != NIL);
|
|
|
|
rInfo = queryDesc->estate->es_result_relations;
|
|
|
|
for (nr = 0; nr < numrels; rInfo++, nr++)
|
|
|
|
report_triggers(rInfo, show_relname, es);
|
|
|
|
|
|
|
|
foreach(l, targrels)
|
|
|
|
{
|
|
|
|
rInfo = (ResultRelInfo *) lfirst(l);
|
|
|
|
report_triggers(rInfo, show_relname, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExplainCloseGroup("Triggers", "Triggers", false, es);
|
|
|
|
}
|
|
|
|
|
2010-02-16 23:19:59 +01:00
|
|
|
/*
|
|
|
|
* ExplainQueryText -
|
2010-02-26 03:01:40 +01:00
|
|
|
* add a "Query Text" node that contains the actual text of the query
|
|
|
|
*
|
2010-02-16 23:19:59 +01:00
|
|
|
* The caller should have set up the options fields of *es, as well as
|
2010-02-26 03:01:40 +01:00
|
|
|
* initializing the output buffer es->str.
|
2010-02-16 23:19:59 +01:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ExplainQueryText(ExplainState *es, QueryDesc *queryDesc)
|
|
|
|
{
|
|
|
|
if (queryDesc->sourceText)
|
|
|
|
ExplainPropertyText("Query Text", queryDesc->sourceText, es);
|
|
|
|
}
|
|
|
|
|
2007-08-15 23:39:50 +02:00
|
|
|
/*
|
|
|
|
* report_triggers -
|
|
|
|
* report execution stats for a single relation's triggers
|
|
|
|
*/
|
|
|
|
static void
|
2009-08-10 07:46:50 +02:00
|
|
|
report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es)
|
2007-08-15 23:39:50 +02:00
|
|
|
{
|
|
|
|
int nt;
|
|
|
|
|
|
|
|
if (!rInfo->ri_TrigDesc || !rInfo->ri_TrigInstrument)
|
|
|
|
return;
|
|
|
|
for (nt = 0; nt < rInfo->ri_TrigDesc->numtriggers; nt++)
|
|
|
|
{
|
|
|
|
Trigger *trig = rInfo->ri_TrigDesc->triggers + nt;
|
|
|
|
Instrumentation *instr = rInfo->ri_TrigInstrument + nt;
|
2009-08-10 07:46:50 +02:00
|
|
|
char *relname;
|
|
|
|
char *conname = NULL;
|
2007-08-15 23:39:50 +02:00
|
|
|
|
|
|
|
/* Must clean up instrumentation state */
|
|
|
|
InstrEndLoop(instr);
|
|
|
|
|
|
|
|
/*
|
2007-11-15 22:14:46 +01:00
|
|
|
* We ignore triggers that were never invoked; they likely aren't
|
|
|
|
* relevant to the current query type.
|
2007-08-15 23:39:50 +02:00
|
|
|
*/
|
|
|
|
if (instr->ntuples == 0)
|
|
|
|
continue;
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainOpenGroup("Trigger", NULL, true, es);
|
|
|
|
|
|
|
|
relname = RelationGetRelationName(rInfo->ri_RelationDesc);
|
|
|
|
if (OidIsValid(trig->tgconstraint))
|
|
|
|
conname = get_constraint_name(trig->tgconstraint);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In text format, we avoid printing both the trigger name and the
|
2010-02-26 03:01:40 +01:00
|
|
|
* constraint name unless VERBOSE is specified. In non-text formats
|
|
|
|
* we just print everything.
|
2009-08-10 07:46:50 +02:00
|
|
|
*/
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
2007-08-15 23:39:50 +02:00
|
|
|
{
|
2009-08-10 07:46:50 +02:00
|
|
|
if (es->verbose || conname == NULL)
|
|
|
|
appendStringInfo(es->str, "Trigger %s", trig->tgname);
|
|
|
|
else
|
|
|
|
appendStringInfoString(es->str, "Trigger");
|
|
|
|
if (conname)
|
|
|
|
appendStringInfo(es->str, " for constraint %s", conname);
|
|
|
|
if (show_relname)
|
|
|
|
appendStringInfo(es->str, " on %s", relname);
|
|
|
|
appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
|
|
|
|
1000.0 * instr->total, instr->ntuples);
|
2007-08-15 23:39:50 +02:00
|
|
|
}
|
|
|
|
else
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
|
|
|
ExplainPropertyText("Trigger Name", trig->tgname, es);
|
|
|
|
if (conname)
|
|
|
|
ExplainPropertyText("Constraint Name", conname, es);
|
|
|
|
ExplainPropertyText("Relation", relname, es);
|
|
|
|
ExplainPropertyFloat("Time", 1000.0 * instr->total, 3, es);
|
|
|
|
ExplainPropertyFloat("Calls", instr->ntuples, 0, es);
|
|
|
|
}
|
2007-08-15 23:39:50 +02:00
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
if (conname)
|
|
|
|
pfree(conname);
|
2007-08-15 23:39:50 +02:00
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainCloseGroup("Trigger", NULL, true, es);
|
2007-08-15 23:39:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-03-20 23:27:52 +01:00
|
|
|
/* Compute elapsed time in seconds since given timestamp */
|
2002-12-05 16:50:39 +01:00
|
|
|
static double
|
2005-04-16 22:07:35 +02:00
|
|
|
elapsed_time(instr_time *starttime)
|
2002-12-05 16:50:39 +01:00
|
|
|
{
|
2005-10-15 04:49:52 +02:00
|
|
|
instr_time endtime;
|
2002-12-05 16:50:39 +01:00
|
|
|
|
2005-03-20 23:27:52 +01:00
|
|
|
INSTR_TIME_SET_CURRENT(endtime);
|
2008-05-14 21:10:29 +02:00
|
|
|
INSTR_TIME_SUBTRACT(endtime, *starttime);
|
2005-03-20 23:27:52 +01:00
|
|
|
return INSTR_TIME_GET_DOUBLE(endtime);
|
2002-12-05 16:50:39 +01:00
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
/*
|
|
|
|
* ExplainPreScanNode -
|
|
|
|
* Prescan the planstate tree to identify which RTEs are referenced
|
|
|
|
*
|
|
|
|
* Adds the relid of each referenced RTE to *rels_used. The result controls
|
2012-12-31 21:13:26 +01:00
|
|
|
* which RTEs are assigned aliases by select_rtable_names_for_explain.
|
|
|
|
* This ensures that we don't confusingly assign un-suffixed aliases to RTEs
|
|
|
|
* that never appear in the EXPLAIN output (such as inheritance parents).
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
|
|
|
|
{
|
|
|
|
Plan *plan = planstate->plan;
|
|
|
|
|
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
|
|
|
case T_SeqScan:
|
|
|
|
case T_IndexScan:
|
|
|
|
case T_IndexOnlyScan:
|
|
|
|
case T_BitmapHeapScan:
|
|
|
|
case T_TidScan:
|
|
|
|
case T_SubqueryScan:
|
|
|
|
case T_FunctionScan:
|
|
|
|
case T_ValuesScan:
|
|
|
|
case T_CteScan:
|
|
|
|
case T_WorkTableScan:
|
|
|
|
case T_ForeignScan:
|
2014-11-07 23:26:02 +01:00
|
|
|
case T_CustomScan:
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
*rels_used = bms_add_member(*rels_used,
|
|
|
|
((Scan *) plan)->scanrelid);
|
|
|
|
break;
|
|
|
|
case T_ModifyTable:
|
|
|
|
*rels_used = bms_add_member(*rels_used,
|
2015-02-18 00:04:11 +01:00
|
|
|
((ModifyTable *) plan)->nominalRelation);
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* initPlan-s */
|
|
|
|
if (planstate->initPlan)
|
|
|
|
ExplainPreScanSubPlans(planstate->initPlan, rels_used);
|
|
|
|
|
|
|
|
/* lefttree */
|
|
|
|
if (outerPlanState(planstate))
|
|
|
|
ExplainPreScanNode(outerPlanState(planstate), rels_used);
|
|
|
|
|
|
|
|
/* righttree */
|
|
|
|
if (innerPlanState(planstate))
|
|
|
|
ExplainPreScanNode(innerPlanState(planstate), rels_used);
|
|
|
|
|
|
|
|
/* special child plans */
|
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
|
|
|
case T_ModifyTable:
|
|
|
|
ExplainPreScanMemberNodes(((ModifyTable *) plan)->plans,
|
|
|
|
((ModifyTableState *) planstate)->mt_plans,
|
|
|
|
rels_used);
|
|
|
|
break;
|
|
|
|
case T_Append:
|
|
|
|
ExplainPreScanMemberNodes(((Append *) plan)->appendplans,
|
|
|
|
((AppendState *) planstate)->appendplans,
|
|
|
|
rels_used);
|
|
|
|
break;
|
|
|
|
case T_MergeAppend:
|
|
|
|
ExplainPreScanMemberNodes(((MergeAppend *) plan)->mergeplans,
|
|
|
|
((MergeAppendState *) planstate)->mergeplans,
|
|
|
|
rels_used);
|
|
|
|
break;
|
|
|
|
case T_BitmapAnd:
|
|
|
|
ExplainPreScanMemberNodes(((BitmapAnd *) plan)->bitmapplans,
|
|
|
|
((BitmapAndState *) planstate)->bitmapplans,
|
|
|
|
rels_used);
|
|
|
|
break;
|
|
|
|
case T_BitmapOr:
|
|
|
|
ExplainPreScanMemberNodes(((BitmapOr *) plan)->bitmapplans,
|
|
|
|
((BitmapOrState *) planstate)->bitmapplans,
|
|
|
|
rels_used);
|
|
|
|
break;
|
|
|
|
case T_SubqueryScan:
|
|
|
|
ExplainPreScanNode(((SubqueryScanState *) planstate)->subplan,
|
|
|
|
rels_used);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* subPlan-s */
|
|
|
|
if (planstate->subPlan)
|
|
|
|
ExplainPreScanSubPlans(planstate->subPlan, rels_used);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prescan the constituent plans of a ModifyTable, Append, MergeAppend,
|
|
|
|
* BitmapAnd, or BitmapOr node.
|
|
|
|
*
|
|
|
|
* Note: we don't actually need to examine the Plan list members, but
|
|
|
|
* we need the list in order to determine the length of the PlanState array.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainPreScanMemberNodes(List *plans, PlanState **planstates,
|
|
|
|
Bitmapset **rels_used)
|
|
|
|
{
|
|
|
|
int nplans = list_length(plans);
|
|
|
|
int j;
|
|
|
|
|
|
|
|
for (j = 0; j < nplans; j++)
|
|
|
|
ExplainPreScanNode(planstates[j], rels_used);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prescan a list of SubPlans (or initPlans, which also use SubPlan nodes).
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainPreScanSubPlans(List *plans, Bitmapset **rels_used)
|
|
|
|
{
|
|
|
|
ListCell *lst;
|
|
|
|
|
|
|
|
foreach(lst, plans)
|
|
|
|
{
|
|
|
|
SubPlanState *sps = (SubPlanState *) lfirst(lst);
|
|
|
|
|
|
|
|
ExplainPreScanNode(sps->planstate, rels_used);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
1996-07-09 08:22:35 +02:00
|
|
|
/*
|
2009-07-24 23:08:42 +02:00
|
|
|
* ExplainNode -
|
2010-07-13 22:57:19 +02:00
|
|
|
* Appends a description of a plan tree to es->str
|
2002-03-22 03:56:37 +01:00
|
|
|
*
|
2010-07-13 22:57:19 +02:00
|
|
|
* planstate points to the executor state node for the current plan node.
|
|
|
|
* We need to work from a PlanState node, not just a Plan node, in order to
|
|
|
|
* get at the instrumentation data (if any) as well as the list of subplans.
|
2002-12-05 16:50:39 +01:00
|
|
|
*
|
2010-07-13 22:57:19 +02:00
|
|
|
* ancestors is a list of parent PlanState nodes, most-closely-nested first.
|
|
|
|
* These are needed in order to interpret PARAM_EXEC Params.
|
2009-07-24 23:08:42 +02:00
|
|
|
*
|
2009-08-10 07:46:50 +02:00
|
|
|
* relationship describes the relationship of this plan node to its parent
|
|
|
|
* (eg, "Outer", "Inner"); it can be null at top level. plan_name is an
|
|
|
|
* optional name to be attached to the node.
|
|
|
|
*
|
|
|
|
* In text format, es->indent is controlled in this function since we only
|
2010-07-13 22:57:19 +02:00
|
|
|
* want it to change at plan-node boundaries. In non-text formats, es->indent
|
2009-08-10 07:46:50 +02:00
|
|
|
* corresponds to the nesting depth of logical output groups, and therefore
|
|
|
|
* is controlled by ExplainOpenGroup/ExplainCloseGroup.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
static void
|
2010-07-13 22:57:19 +02:00
|
|
|
ExplainNode(PlanState *planstate, List *ancestors,
|
2009-08-10 07:46:50 +02:00
|
|
|
const char *relationship, const char *plan_name,
|
|
|
|
ExplainState *es)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2010-07-13 22:57:19 +02:00
|
|
|
Plan *plan = planstate->plan;
|
2009-08-10 07:46:50 +02:00
|
|
|
const char *pname; /* node type name for text output */
|
|
|
|
const char *sname; /* node type name for non-text output */
|
|
|
|
const char *strategy = NULL;
|
2009-10-10 03:43:50 +02:00
|
|
|
const char *operation = NULL;
|
2014-11-07 23:26:02 +01:00
|
|
|
const char *custom_name = NULL;
|
2009-08-10 07:46:50 +02:00
|
|
|
int save_indent = es->indent;
|
|
|
|
bool haschildren;
|
2009-07-24 23:08:42 +02:00
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
case T_Result:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Result";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
2009-10-10 03:43:50 +02:00
|
|
|
case T_ModifyTable:
|
|
|
|
sname = "ModifyTable";
|
|
|
|
switch (((ModifyTable *) plan)->operation)
|
|
|
|
{
|
|
|
|
case CMD_INSERT:
|
|
|
|
pname = operation = "Insert";
|
|
|
|
break;
|
|
|
|
case CMD_UPDATE:
|
|
|
|
pname = operation = "Update";
|
|
|
|
break;
|
|
|
|
case CMD_DELETE:
|
|
|
|
pname = operation = "Delete";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pname = "???";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
1997-09-08 04:41:22 +02:00
|
|
|
case T_Append:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Append";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
2010-10-14 22:56:39 +02:00
|
|
|
case T_MergeAppend:
|
|
|
|
pname = sname = "Merge Append";
|
|
|
|
break;
|
2008-10-04 23:56:55 +02:00
|
|
|
case T_RecursiveUnion:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Recursive Union";
|
2008-10-04 23:56:55 +02:00
|
|
|
break;
|
2005-04-20 00:35:18 +02:00
|
|
|
case T_BitmapAnd:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "BitmapAnd";
|
2005-04-20 00:35:18 +02:00
|
|
|
break;
|
|
|
|
case T_BitmapOr:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "BitmapOr";
|
2005-04-20 00:35:18 +02:00
|
|
|
break;
|
1997-09-08 04:41:22 +02:00
|
|
|
case T_NestLoop:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Nested Loop";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
|
|
|
case T_MergeJoin:
|
2010-02-26 03:01:40 +01:00
|
|
|
pname = "Merge"; /* "Join" gets added by jointype switch */
|
2009-08-10 07:46:50 +02:00
|
|
|
sname = "Merge Join";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
|
|
|
case T_HashJoin:
|
2010-02-26 03:01:40 +01:00
|
|
|
pname = "Hash"; /* "Join" gets added by jointype switch */
|
2009-08-10 07:46:50 +02:00
|
|
|
sname = "Hash Join";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
|
|
|
case T_SeqScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Seq Scan";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
|
|
|
case T_IndexScan:
|
2011-10-11 20:20:06 +02:00
|
|
|
pname = sname = "Index Scan";
|
|
|
|
break;
|
|
|
|
case T_IndexOnlyScan:
|
|
|
|
pname = sname = "Index Only Scan";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
2005-04-20 00:35:18 +02:00
|
|
|
case T_BitmapIndexScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Bitmap Index Scan";
|
2005-04-20 00:35:18 +02:00
|
|
|
break;
|
|
|
|
case T_BitmapHeapScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Bitmap Heap Scan";
|
2005-04-20 00:35:18 +02:00
|
|
|
break;
|
2000-09-29 20:21:41 +02:00
|
|
|
case T_TidScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Tid Scan";
|
2000-09-29 20:21:41 +02:00
|
|
|
break;
|
|
|
|
case T_SubqueryScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Subquery Scan";
|
2000-09-29 20:21:41 +02:00
|
|
|
break;
|
2002-05-12 22:10:05 +02:00
|
|
|
case T_FunctionScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Function Scan";
|
2002-05-12 22:10:05 +02:00
|
|
|
break;
|
2006-08-02 03:59:48 +02:00
|
|
|
case T_ValuesScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Values Scan";
|
2006-08-02 03:59:48 +02:00
|
|
|
break;
|
2008-10-04 23:56:55 +02:00
|
|
|
case T_CteScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "CTE Scan";
|
2008-10-04 23:56:55 +02:00
|
|
|
break;
|
|
|
|
case T_WorkTableScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "WorkTable Scan";
|
2008-10-04 23:56:55 +02:00
|
|
|
break;
|
2011-02-20 06:17:18 +01:00
|
|
|
case T_ForeignScan:
|
|
|
|
pname = sname = "Foreign Scan";
|
|
|
|
break;
|
2014-11-07 23:26:02 +01:00
|
|
|
case T_CustomScan:
|
|
|
|
sname = "Custom Scan";
|
|
|
|
custom_name = ((CustomScan *) plan)->methods->CustomName;
|
|
|
|
if (custom_name)
|
|
|
|
pname = psprintf("Custom Scan (%s)", custom_name);
|
|
|
|
else
|
|
|
|
pname = sname;
|
|
|
|
break;
|
1999-08-17 01:47:23 +02:00
|
|
|
case T_Material:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Materialize";
|
1999-08-17 01:47:23 +02:00
|
|
|
break;
|
1997-09-08 04:41:22 +02:00
|
|
|
case T_Sort:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Sort";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
|
|
|
case T_Group:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Group";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
|
|
|
case T_Agg:
|
2009-08-10 07:46:50 +02:00
|
|
|
sname = "Aggregate";
|
2002-11-06 01:00:45 +01:00
|
|
|
switch (((Agg *) plan)->aggstrategy)
|
|
|
|
{
|
|
|
|
case AGG_PLAIN:
|
|
|
|
pname = "Aggregate";
|
2009-08-10 07:46:50 +02:00
|
|
|
strategy = "Plain";
|
2002-11-06 01:00:45 +01:00
|
|
|
break;
|
|
|
|
case AGG_SORTED:
|
|
|
|
pname = "GroupAggregate";
|
2009-08-10 07:46:50 +02:00
|
|
|
strategy = "Sorted";
|
2002-11-06 01:00:45 +01:00
|
|
|
break;
|
|
|
|
case AGG_HASHED:
|
|
|
|
pname = "HashAggregate";
|
2009-08-10 07:46:50 +02:00
|
|
|
strategy = "Hashed";
|
2002-11-06 01:00:45 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pname = "Aggregate ???";
|
2009-08-10 07:46:50 +02:00
|
|
|
strategy = "???";
|
2002-11-06 01:00:45 +01:00
|
|
|
break;
|
|
|
|
}
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
2008-12-28 19:54:01 +01:00
|
|
|
case T_WindowAgg:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "WindowAgg";
|
2008-12-28 19:54:01 +01:00
|
|
|
break;
|
1997-09-08 04:41:22 +02:00
|
|
|
case T_Unique:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Unique";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
2000-10-05 21:11:39 +02:00
|
|
|
case T_SetOp:
|
2009-08-10 07:46:50 +02:00
|
|
|
sname = "SetOp";
|
2008-08-07 05:04:04 +02:00
|
|
|
switch (((SetOp *) plan)->strategy)
|
2000-10-05 21:11:39 +02:00
|
|
|
{
|
2008-08-07 05:04:04 +02:00
|
|
|
case SETOP_SORTED:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = "SetOp";
|
|
|
|
strategy = "Sorted";
|
2000-10-05 21:11:39 +02:00
|
|
|
break;
|
2008-08-07 05:04:04 +02:00
|
|
|
case SETOP_HASHED:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = "HashSetOp";
|
|
|
|
strategy = "Hashed";
|
2000-10-05 21:11:39 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pname = "SetOp ???";
|
2009-08-10 07:46:50 +02:00
|
|
|
strategy = "???";
|
2000-10-05 21:11:39 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2009-10-12 20:10:51 +02:00
|
|
|
case T_LockRows:
|
|
|
|
pname = sname = "LockRows";
|
|
|
|
break;
|
2000-10-26 23:38:24 +02:00
|
|
|
case T_Limit:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Limit";
|
2000-10-26 23:38:24 +02:00
|
|
|
break;
|
1997-09-08 04:41:22 +02:00
|
|
|
case T_Hash:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "Hash";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
|
|
|
default:
|
2009-08-10 07:46:50 +02:00
|
|
|
pname = sname = "???";
|
1997-09-08 04:41:22 +02:00
|
|
|
break;
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainOpenGroup("Plan",
|
|
|
|
relationship ? NULL : "Plan",
|
|
|
|
true, es);
|
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
if (plan_name)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfo(es->str, "%s\n", plan_name);
|
|
|
|
es->indent++;
|
|
|
|
}
|
|
|
|
if (es->indent)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfoString(es->str, "-> ");
|
|
|
|
es->indent += 2;
|
|
|
|
}
|
|
|
|
appendStringInfoString(es->str, pname);
|
|
|
|
es->indent++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ExplainPropertyText("Node Type", sname, es);
|
|
|
|
if (strategy)
|
|
|
|
ExplainPropertyText("Strategy", strategy, es);
|
2009-10-10 03:43:50 +02:00
|
|
|
if (operation)
|
|
|
|
ExplainPropertyText("Operation", operation, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
if (relationship)
|
|
|
|
ExplainPropertyText("Parent Relationship", relationship, es);
|
|
|
|
if (plan_name)
|
|
|
|
ExplainPropertyText("Subplan Name", plan_name, es);
|
2014-11-07 23:26:02 +01:00
|
|
|
if (custom_name)
|
|
|
|
ExplainPropertyText("Custom Plan Provider", custom_name, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
1998-04-27 18:57:09 +02:00
|
|
|
case T_SeqScan:
|
2005-04-20 00:35:18 +02:00
|
|
|
case T_BitmapHeapScan:
|
2000-02-15 21:49:31 +01:00
|
|
|
case T_TidScan:
|
2000-09-29 20:21:41 +02:00
|
|
|
case T_SubqueryScan:
|
2002-05-12 22:10:05 +02:00
|
|
|
case T_FunctionScan:
|
2006-08-02 03:59:48 +02:00
|
|
|
case T_ValuesScan:
|
2008-10-04 23:56:55 +02:00
|
|
|
case T_CteScan:
|
|
|
|
case T_WorkTableScan:
|
2011-02-20 06:17:18 +01:00
|
|
|
case T_ForeignScan:
|
2014-11-07 23:26:02 +01:00
|
|
|
case T_CustomScan:
|
2009-07-24 23:08:42 +02:00
|
|
|
ExplainScanTarget((Scan *) plan, es);
|
|
|
|
break;
|
2011-10-11 20:20:06 +02:00
|
|
|
case T_IndexScan:
|
|
|
|
{
|
|
|
|
IndexScan *indexscan = (IndexScan *) plan;
|
|
|
|
|
|
|
|
ExplainIndexScanDetails(indexscan->indexid,
|
|
|
|
indexscan->indexorderdir,
|
|
|
|
es);
|
|
|
|
ExplainScanTarget((Scan *) indexscan, es);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_IndexOnlyScan:
|
|
|
|
{
|
|
|
|
IndexOnlyScan *indexonlyscan = (IndexOnlyScan *) plan;
|
|
|
|
|
|
|
|
ExplainIndexScanDetails(indexonlyscan->indexid,
|
|
|
|
indexonlyscan->indexorderdir,
|
|
|
|
es);
|
|
|
|
ExplainScanTarget((Scan *) indexonlyscan, es);
|
|
|
|
}
|
|
|
|
break;
|
2009-07-24 23:08:42 +02:00
|
|
|
case T_BitmapIndexScan:
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
|
|
|
BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
|
|
|
|
const char *indexname =
|
2010-02-26 03:01:40 +01:00
|
|
|
explain_get_index_name(bitmapindexscan->indexid);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
appendStringInfo(es->str, " on %s", indexname);
|
|
|
|
else
|
|
|
|
ExplainPropertyText("Index Name", indexname, es);
|
|
|
|
}
|
|
|
|
break;
|
2011-03-01 17:32:13 +01:00
|
|
|
case T_ModifyTable:
|
|
|
|
ExplainModifyTarget((ModifyTable *) plan, es);
|
|
|
|
break;
|
2009-08-10 07:46:50 +02:00
|
|
|
case T_NestLoop:
|
|
|
|
case T_MergeJoin:
|
|
|
|
case T_HashJoin:
|
|
|
|
{
|
|
|
|
const char *jointype;
|
|
|
|
|
|
|
|
switch (((Join *) plan)->jointype)
|
|
|
|
{
|
|
|
|
case JOIN_INNER:
|
|
|
|
jointype = "Inner";
|
|
|
|
break;
|
|
|
|
case JOIN_LEFT:
|
|
|
|
jointype = "Left";
|
|
|
|
break;
|
|
|
|
case JOIN_FULL:
|
|
|
|
jointype = "Full";
|
|
|
|
break;
|
|
|
|
case JOIN_RIGHT:
|
|
|
|
jointype = "Right";
|
|
|
|
break;
|
|
|
|
case JOIN_SEMI:
|
|
|
|
jointype = "Semi";
|
|
|
|
break;
|
|
|
|
case JOIN_ANTI:
|
|
|
|
jointype = "Anti";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
jointype = "???";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* For historical reasons, the join type is interpolated
|
|
|
|
* into the node type name...
|
|
|
|
*/
|
|
|
|
if (((Join *) plan)->jointype != JOIN_INNER)
|
|
|
|
appendStringInfo(es->str, " %s Join", jointype);
|
|
|
|
else if (!IsA(plan, NestLoop))
|
2013-10-31 15:55:59 +01:00
|
|
|
appendStringInfoString(es->str, " Join");
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
ExplainPropertyText("Join Type", jointype, es);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_SetOp:
|
|
|
|
{
|
|
|
|
const char *setopcmd;
|
|
|
|
|
|
|
|
switch (((SetOp *) plan)->cmd)
|
|
|
|
{
|
|
|
|
case SETOPCMD_INTERSECT:
|
|
|
|
setopcmd = "Intersect";
|
|
|
|
break;
|
|
|
|
case SETOPCMD_INTERSECT_ALL:
|
|
|
|
setopcmd = "Intersect All";
|
|
|
|
break;
|
|
|
|
case SETOPCMD_EXCEPT:
|
|
|
|
setopcmd = "Except";
|
|
|
|
break;
|
|
|
|
case SETOPCMD_EXCEPT_ALL:
|
|
|
|
setopcmd = "Except All";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
setopcmd = "???";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
appendStringInfo(es->str, " %s", setopcmd);
|
|
|
|
else
|
|
|
|
ExplainPropertyText("Command", setopcmd, es);
|
|
|
|
}
|
2008-10-04 23:56:55 +02:00
|
|
|
break;
|
1997-09-08 04:41:22 +02:00
|
|
|
default:
|
|
|
|
break;
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2005-10-15 04:49:52 +02:00
|
|
|
|
2009-07-27 01:34:18 +02:00
|
|
|
if (es->costs)
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
appendStringInfo(es->str, " (cost=%.2f..%.2f rows=%.0f width=%d)",
|
|
|
|
plan->startup_cost, plan->total_cost,
|
|
|
|
plan->plan_rows, plan->plan_width);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ExplainPropertyFloat("Startup Cost", plan->startup_cost, 2, es);
|
|
|
|
ExplainPropertyFloat("Total Cost", plan->total_cost, 2, es);
|
|
|
|
ExplainPropertyFloat("Plan Rows", plan->plan_rows, 0, es);
|
|
|
|
ExplainPropertyInteger("Plan Width", plan->plan_width, es);
|
|
|
|
}
|
|
|
|
}
|
2001-09-18 03:59:07 +02:00
|
|
|
|
2005-06-04 04:07:09 +02:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* We have to forcibly clean up the instrumentation state because we
|
|
|
|
* haven't done ExecutorEnd yet. This is pretty grotty ...
|
2014-05-20 18:20:47 +02:00
|
|
|
*
|
|
|
|
* Note: contrib/auto_explain could cause instrumentation to be set up
|
|
|
|
* even though we didn't ask for it here. Be careful not to print any
|
|
|
|
* instrumentation results the user didn't ask for. But we do the
|
|
|
|
* InstrEndLoop call anyway, if possible, to reduce the number of cases
|
|
|
|
* auto_explain has to contend with.
|
2005-06-04 04:07:09 +02:00
|
|
|
*/
|
|
|
|
if (planstate->instrument)
|
|
|
|
InstrEndLoop(planstate->instrument);
|
2002-12-05 16:50:39 +01:00
|
|
|
|
2014-05-20 18:20:47 +02:00
|
|
|
if (es->analyze &&
|
|
|
|
planstate->instrument && planstate->instrument->nloops > 0)
|
2005-06-04 04:07:09 +02:00
|
|
|
{
|
|
|
|
double nloops = planstate->instrument->nloops;
|
2009-08-10 07:46:50 +02:00
|
|
|
double startup_sec = 1000.0 * planstate->instrument->startup / nloops;
|
|
|
|
double total_sec = 1000.0 * planstate->instrument->total / nloops;
|
|
|
|
double rows = planstate->instrument->ntuples / nloops;
|
2001-09-18 03:59:07 +02:00
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
2014-05-20 18:20:47 +02:00
|
|
|
if (es->timing)
|
2012-02-07 17:23:04 +01:00
|
|
|
appendStringInfo(es->str,
|
2012-06-10 21:20:04 +02:00
|
|
|
" (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
|
2012-02-07 17:23:04 +01:00
|
|
|
startup_sec, total_sec, rows, nloops);
|
|
|
|
else
|
|
|
|
appendStringInfo(es->str,
|
|
|
|
" (actual rows=%.0f loops=%.0f)",
|
|
|
|
rows, nloops);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-05-20 18:20:47 +02:00
|
|
|
if (es->timing)
|
2012-02-07 17:23:04 +01:00
|
|
|
{
|
|
|
|
ExplainPropertyFloat("Actual Startup Time", startup_sec, 3, es);
|
|
|
|
ExplainPropertyFloat("Actual Total Time", total_sec, 3, es);
|
|
|
|
}
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainPropertyFloat("Actual Rows", rows, 0, es);
|
|
|
|
ExplainPropertyFloat("Actual Loops", nloops, 0, es);
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2009-07-27 01:34:18 +02:00
|
|
|
else if (es->analyze)
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
2013-10-31 15:55:59 +01:00
|
|
|
appendStringInfoString(es->str, " (never executed)");
|
2012-02-07 17:23:04 +01:00
|
|
|
else
|
|
|
|
{
|
2014-05-20 18:20:47 +02:00
|
|
|
if (es->timing)
|
|
|
|
{
|
|
|
|
ExplainPropertyFloat("Actual Startup Time", 0.0, 3, es);
|
|
|
|
ExplainPropertyFloat("Actual Total Time", 0.0, 3, es);
|
|
|
|
}
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainPropertyFloat("Actual Rows", 0.0, 0, es);
|
|
|
|
ExplainPropertyFloat("Actual Loops", 0.0, 0, es);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* in text format, first line ends here */
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
1998-02-26 05:46:47 +01:00
|
|
|
|
2008-04-17 20:30:18 +02:00
|
|
|
/* target list */
|
2009-07-27 01:34:18 +02:00
|
|
|
if (es->verbose)
|
2010-07-13 22:57:19 +02:00
|
|
|
show_plan_tlist(planstate, ancestors, es);
|
2008-04-17 20:30:18 +02:00
|
|
|
|
2002-05-18 23:38:41 +02:00
|
|
|
/* quals, sort keys, etc */
|
2002-03-12 01:52:10 +01:00
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
|
|
|
case T_IndexScan:
|
2005-04-25 03:30:14 +02:00
|
|
|
show_scan_qual(((IndexScan *) plan)->indexqualorig,
|
2010-07-13 22:57:19 +02:00
|
|
|
"Index Cond", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (((IndexScan *) plan)->indexqualorig)
|
|
|
|
show_instrumentation_count("Rows Removed by Index Recheck", 2,
|
|
|
|
planstate, es);
|
2010-12-03 02:50:48 +01:00
|
|
|
show_scan_qual(((IndexScan *) plan)->indexorderbyorig,
|
|
|
|
"Order By", planstate, ancestors, es);
|
2010-07-13 22:57:19 +02:00
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
break;
|
2011-10-11 20:20:06 +02:00
|
|
|
case T_IndexOnlyScan:
|
|
|
|
show_scan_qual(((IndexOnlyScan *) plan)->indexqual,
|
|
|
|
"Index Cond", planstate, ancestors, es);
|
|
|
|
if (((IndexOnlyScan *) plan)->indexqual)
|
|
|
|
show_instrumentation_count("Rows Removed by Index Recheck", 2,
|
|
|
|
planstate, es);
|
|
|
|
show_scan_qual(((IndexOnlyScan *) plan)->indexorderby,
|
|
|
|
"Order By", planstate, ancestors, es);
|
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2012-01-26 02:40:34 +01:00
|
|
|
if (es->analyze)
|
|
|
|
ExplainPropertyLong("Heap Fetches",
|
2012-06-10 21:20:04 +02:00
|
|
|
((IndexOnlyScanState *) planstate)->ioss_HeapFetches, es);
|
2011-10-11 20:20:06 +02:00
|
|
|
break;
|
2005-04-20 00:35:18 +02:00
|
|
|
case T_BitmapIndexScan:
|
2005-04-25 03:30:14 +02:00
|
|
|
show_scan_qual(((BitmapIndexScan *) plan)->indexqualorig,
|
2010-07-13 22:57:19 +02:00
|
|
|
"Index Cond", planstate, ancestors, es);
|
2005-04-20 00:35:18 +02:00
|
|
|
break;
|
|
|
|
case T_BitmapHeapScan:
|
2005-04-25 03:30:14 +02:00
|
|
|
show_scan_qual(((BitmapHeapScan *) plan)->bitmapqualorig,
|
2010-07-13 22:57:19 +02:00
|
|
|
"Recheck Cond", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (((BitmapHeapScan *) plan)->bitmapqualorig)
|
|
|
|
show_instrumentation_count("Rows Removed by Index Recheck", 2,
|
|
|
|
planstate, es);
|
2014-01-13 20:42:16 +01:00
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
|
|
|
if (es->analyze)
|
|
|
|
show_tidbitmap_info((BitmapHeapScanState *) planstate, es);
|
|
|
|
break;
|
2002-03-12 01:52:10 +01:00
|
|
|
case T_SeqScan:
|
2006-08-02 03:59:48 +02:00
|
|
|
case T_ValuesScan:
|
2008-10-04 23:56:55 +02:00
|
|
|
case T_CteScan:
|
|
|
|
case T_WorkTableScan:
|
2007-02-23 22:59:45 +01:00
|
|
|
case T_SubqueryScan:
|
2010-07-13 22:57:19 +02:00
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
break;
|
2010-08-24 23:20:36 +02:00
|
|
|
case T_FunctionScan:
|
|
|
|
if (es->verbose)
|
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...)
as a single FROM-clause entry. The result is the concatenation of the
first row from each function, followed by the second row from each
function, etc; with NULLs inserted if any function produces fewer rows than
others. This is believed to be a much more useful behavior than what
Postgres currently does with multiple SRFs in a SELECT list.
This syntax also provides a reasonable way to combine use of column
definition lists with WITH ORDINALITY: put the column definition list
inside TABLE(), where it's clear that it doesn't control the ordinality
column as well.
Also implement SQL-compliant multiple-argument UNNEST(), by turning
UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)).
The SQL standard specifies TABLE() with only a single function, not
multiple functions, and it seems to require an implicit UNNEST() which is
not what this patch does. There may be something wrong with that reading
of the spec, though, because if it's right then the spec's TABLE() is just
a pointless alternative spelling of UNNEST(). After further review of
that, we might choose to adopt a different syntax for what this patch does,
but in any case this functionality seems clearly worthwhile.
Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and
significantly revised by me
2013-11-22 01:37:02 +01:00
|
|
|
{
|
|
|
|
List *fexprs = NIL;
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
foreach(lc, ((FunctionScan *) plan)->functions)
|
|
|
|
{
|
|
|
|
RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
|
|
|
|
|
|
|
|
fexprs = lappend(fexprs, rtfunc->funcexpr);
|
|
|
|
}
|
|
|
|
/* We rely on show_expression to insert commas as needed */
|
|
|
|
show_expression((Node *) fexprs,
|
2010-08-24 23:20:36 +02:00
|
|
|
"Function Call", planstate, ancestors,
|
|
|
|
es->verbose, es);
|
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...)
as a single FROM-clause entry. The result is the concatenation of the
first row from each function, followed by the second row from each
function, etc; with NULLs inserted if any function produces fewer rows than
others. This is believed to be a much more useful behavior than what
Postgres currently does with multiple SRFs in a SELECT list.
This syntax also provides a reasonable way to combine use of column
definition lists with WITH ORDINALITY: put the column definition list
inside TABLE(), where it's clear that it doesn't control the ordinality
column as well.
Also implement SQL-compliant multiple-argument UNNEST(), by turning
UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)).
The SQL standard specifies TABLE() with only a single function, not
multiple functions, and it seems to require an implicit UNNEST() which is
not what this patch does. There may be something wrong with that reading
of the spec, though, because if it's right then the spec's TABLE() is just
a pointless alternative spelling of UNNEST(). After further review of
that, we might choose to adopt a different syntax for what this patch does,
but in any case this functionality seems clearly worthwhile.
Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and
significantly revised by me
2013-11-22 01:37:02 +01:00
|
|
|
}
|
2010-08-24 23:20:36 +02:00
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2010-08-24 23:20:36 +02:00
|
|
|
break;
|
2005-11-26 23:14:57 +01:00
|
|
|
case T_TidScan:
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* The tidquals list has OR semantics, so be sure to show it
|
|
|
|
* as an OR condition.
|
|
|
|
*/
|
2006-10-04 02:30:14 +02:00
|
|
|
List *tidquals = ((TidScan *) plan)->tidquals;
|
2005-11-26 23:14:57 +01:00
|
|
|
|
|
|
|
if (list_length(tidquals) > 1)
|
|
|
|
tidquals = list_make1(make_orclause(tidquals));
|
2010-07-13 22:57:19 +02:00
|
|
|
show_scan_qual(tidquals, "TID Cond", planstate, ancestors, es);
|
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2005-11-26 23:14:57 +01:00
|
|
|
}
|
|
|
|
break;
|
2011-02-20 06:17:18 +01:00
|
|
|
case T_ForeignScan:
|
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2011-02-20 06:17:18 +01:00
|
|
|
show_foreignscan_info((ForeignScanState *) planstate, es);
|
|
|
|
break;
|
2014-11-07 23:26:02 +01:00
|
|
|
case T_CustomScan:
|
|
|
|
{
|
|
|
|
CustomScanState *css = (CustomScanState *) planstate;
|
|
|
|
|
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
|
|
|
if (css->methods->ExplainCustomScan)
|
|
|
|
css->methods->ExplainCustomScan(css, ancestors, es);
|
|
|
|
}
|
|
|
|
break;
|
2002-03-12 01:52:10 +01:00
|
|
|
case T_NestLoop:
|
2002-03-22 03:56:37 +01:00
|
|
|
show_upper_qual(((NestLoop *) plan)->join.joinqual,
|
2010-07-13 22:57:19 +02:00
|
|
|
"Join Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (((NestLoop *) plan)->join.joinqual)
|
|
|
|
show_instrumentation_count("Rows Removed by Join Filter", 1,
|
|
|
|
planstate, es);
|
2010-07-13 22:57:19 +02:00
|
|
|
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 2,
|
|
|
|
planstate, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
break;
|
|
|
|
case T_MergeJoin:
|
2002-03-22 03:56:37 +01:00
|
|
|
show_upper_qual(((MergeJoin *) plan)->mergeclauses,
|
2010-07-13 22:57:19 +02:00
|
|
|
"Merge Cond", planstate, ancestors, es);
|
2002-03-22 03:56:37 +01:00
|
|
|
show_upper_qual(((MergeJoin *) plan)->join.joinqual,
|
2010-07-13 22:57:19 +02:00
|
|
|
"Join Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (((MergeJoin *) plan)->join.joinqual)
|
|
|
|
show_instrumentation_count("Rows Removed by Join Filter", 1,
|
|
|
|
planstate, es);
|
2010-07-13 22:57:19 +02:00
|
|
|
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 2,
|
|
|
|
planstate, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
break;
|
|
|
|
case T_HashJoin:
|
2002-03-22 03:56:37 +01:00
|
|
|
show_upper_qual(((HashJoin *) plan)->hashclauses,
|
2010-07-13 22:57:19 +02:00
|
|
|
"Hash Cond", planstate, ancestors, es);
|
2002-03-22 03:56:37 +01:00
|
|
|
show_upper_qual(((HashJoin *) plan)->join.joinqual,
|
2010-07-13 22:57:19 +02:00
|
|
|
"Join Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (((HashJoin *) plan)->join.joinqual)
|
|
|
|
show_instrumentation_count("Rows Removed by Join Filter", 1,
|
|
|
|
planstate, es);
|
2010-07-13 22:57:19 +02:00
|
|
|
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 2,
|
|
|
|
planstate, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
break;
|
|
|
|
case T_Agg:
|
2013-12-12 17:24:38 +01:00
|
|
|
show_agg_keys((AggState *) planstate, ancestors, es);
|
|
|
|
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
|
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
|
|
|
break;
|
2002-03-12 01:52:10 +01:00
|
|
|
case T_Group:
|
2013-12-12 17:24:38 +01:00
|
|
|
show_group_keys((GroupState *) planstate, ancestors, es);
|
2010-07-13 22:57:19 +02:00
|
|
|
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
break;
|
2002-05-18 23:38:41 +02:00
|
|
|
case T_Sort:
|
2010-07-13 22:57:19 +02:00
|
|
|
show_sort_keys((SortState *) planstate, ancestors, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
show_sort_info((SortState *) planstate, es);
|
2002-05-18 23:38:41 +02:00
|
|
|
break;
|
2010-10-14 22:56:39 +02:00
|
|
|
case T_MergeAppend:
|
|
|
|
show_merge_append_keys((MergeAppendState *) planstate,
|
|
|
|
ancestors, es);
|
|
|
|
break;
|
2002-03-12 01:52:10 +01:00
|
|
|
case T_Result:
|
|
|
|
show_upper_qual((List *) ((Result *) plan)->resconstantqual,
|
2010-07-13 22:57:19 +02:00
|
|
|
"One-Time Filter", planstate, ancestors, es);
|
|
|
|
show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
break;
|
2013-03-10 19:14:53 +01:00
|
|
|
case T_ModifyTable:
|
|
|
|
show_modifytable_info((ModifyTableState *) planstate, es);
|
|
|
|
break;
|
2010-02-01 16:43:36 +01:00
|
|
|
case T_Hash:
|
|
|
|
show_hash_info((HashState *) planstate, es);
|
|
|
|
break;
|
2002-03-12 01:52:10 +01:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-12-15 05:57:48 +01:00
|
|
|
/* Show buffer usage */
|
2014-05-20 18:20:47 +02:00
|
|
|
if (es->buffers && planstate->instrument)
|
2009-12-15 05:57:48 +01:00
|
|
|
{
|
|
|
|
const BufferUsage *usage = &planstate->instrument->bufusage;
|
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
bool has_shared = (usage->shared_blks_hit > 0 ||
|
|
|
|
usage->shared_blks_read > 0 ||
|
2012-02-23 02:33:05 +01:00
|
|
|
usage->shared_blks_dirtied > 0 ||
|
|
|
|
usage->shared_blks_written > 0);
|
2010-02-26 03:01:40 +01:00
|
|
|
bool has_local = (usage->local_blks_hit > 0 ||
|
|
|
|
usage->local_blks_read > 0 ||
|
2012-02-23 02:33:05 +01:00
|
|
|
usage->local_blks_dirtied > 0 ||
|
|
|
|
usage->local_blks_written > 0);
|
2010-02-26 03:01:40 +01:00
|
|
|
bool has_temp = (usage->temp_blks_read > 0 ||
|
2012-02-23 02:33:05 +01:00
|
|
|
usage->temp_blks_written > 0);
|
2012-04-30 00:13:33 +02:00
|
|
|
bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) ||
|
2012-06-10 21:20:04 +02:00
|
|
|
!INSTR_TIME_IS_ZERO(usage->blk_write_time));
|
2009-12-15 05:57:48 +01:00
|
|
|
|
|
|
|
/* Show only positive counter values. */
|
|
|
|
if (has_shared || has_local || has_temp)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
2010-02-16 21:07:13 +01:00
|
|
|
appendStringInfoString(es->str, "Buffers:");
|
2009-12-15 05:57:48 +01:00
|
|
|
|
|
|
|
if (has_shared)
|
|
|
|
{
|
|
|
|
appendStringInfoString(es->str, " shared");
|
2010-02-16 21:07:13 +01:00
|
|
|
if (usage->shared_blks_hit > 0)
|
|
|
|
appendStringInfo(es->str, " hit=%ld",
|
2010-02-26 03:01:40 +01:00
|
|
|
usage->shared_blks_hit);
|
2009-12-15 05:57:48 +01:00
|
|
|
if (usage->shared_blks_read > 0)
|
2010-02-16 21:07:13 +01:00
|
|
|
appendStringInfo(es->str, " read=%ld",
|
2010-02-26 03:01:40 +01:00
|
|
|
usage->shared_blks_read);
|
2012-02-23 02:33:05 +01:00
|
|
|
if (usage->shared_blks_dirtied > 0)
|
|
|
|
appendStringInfo(es->str, " dirtied=%ld",
|
|
|
|
usage->shared_blks_dirtied);
|
2009-12-15 05:57:48 +01:00
|
|
|
if (usage->shared_blks_written > 0)
|
2010-02-16 21:07:13 +01:00
|
|
|
appendStringInfo(es->str, " written=%ld",
|
2010-02-26 03:01:40 +01:00
|
|
|
usage->shared_blks_written);
|
2009-12-15 05:57:48 +01:00
|
|
|
if (has_local || has_temp)
|
|
|
|
appendStringInfoChar(es->str, ',');
|
|
|
|
}
|
|
|
|
if (has_local)
|
|
|
|
{
|
2010-02-16 21:07:13 +01:00
|
|
|
appendStringInfoString(es->str, " local");
|
|
|
|
if (usage->local_blks_hit > 0)
|
|
|
|
appendStringInfo(es->str, " hit=%ld",
|
2010-02-26 03:01:40 +01:00
|
|
|
usage->local_blks_hit);
|
2010-02-16 21:07:13 +01:00
|
|
|
if (usage->local_blks_read > 0)
|
|
|
|
appendStringInfo(es->str, " read=%ld",
|
2010-02-26 03:01:40 +01:00
|
|
|
usage->local_blks_read);
|
2012-02-23 02:33:05 +01:00
|
|
|
if (usage->local_blks_dirtied > 0)
|
|
|
|
appendStringInfo(es->str, " dirtied=%ld",
|
|
|
|
usage->local_blks_dirtied);
|
2010-02-16 21:07:13 +01:00
|
|
|
if (usage->local_blks_written > 0)
|
|
|
|
appendStringInfo(es->str, " written=%ld",
|
2010-02-26 03:01:40 +01:00
|
|
|
usage->local_blks_written);
|
2009-12-15 05:57:48 +01:00
|
|
|
if (has_temp)
|
|
|
|
appendStringInfoChar(es->str, ',');
|
|
|
|
}
|
|
|
|
if (has_temp)
|
|
|
|
{
|
|
|
|
appendStringInfoString(es->str, " temp");
|
|
|
|
if (usage->temp_blks_read > 0)
|
2010-02-16 21:07:13 +01:00
|
|
|
appendStringInfo(es->str, " read=%ld",
|
2010-02-26 03:01:40 +01:00
|
|
|
usage->temp_blks_read);
|
2010-02-16 21:07:13 +01:00
|
|
|
if (usage->temp_blks_written > 0)
|
|
|
|
appendStringInfo(es->str, " written=%ld",
|
2010-02-26 03:01:40 +01:00
|
|
|
usage->temp_blks_written);
|
2009-12-15 05:57:48 +01:00
|
|
|
}
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
}
|
2012-03-27 20:52:37 +02:00
|
|
|
|
|
|
|
/* As above, show only positive counter values. */
|
|
|
|
if (has_timing)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfoString(es->str, "I/O Timings:");
|
2012-04-30 00:13:33 +02:00
|
|
|
if (!INSTR_TIME_IS_ZERO(usage->blk_read_time))
|
|
|
|
appendStringInfo(es->str, " read=%0.3f",
|
2012-06-10 21:20:04 +02:00
|
|
|
INSTR_TIME_GET_MILLISEC(usage->blk_read_time));
|
2012-04-30 00:13:33 +02:00
|
|
|
if (!INSTR_TIME_IS_ZERO(usage->blk_write_time))
|
|
|
|
appendStringInfo(es->str, " write=%0.3f",
|
2012-06-10 21:20:04 +02:00
|
|
|
INSTR_TIME_GET_MILLISEC(usage->blk_write_time));
|
2012-03-27 20:52:37 +02:00
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
}
|
2009-12-15 05:57:48 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ExplainPropertyLong("Shared Hit Blocks", usage->shared_blks_hit, es);
|
|
|
|
ExplainPropertyLong("Shared Read Blocks", usage->shared_blks_read, es);
|
2012-02-23 02:33:05 +01:00
|
|
|
ExplainPropertyLong("Shared Dirtied Blocks", usage->shared_blks_dirtied, es);
|
2009-12-15 05:57:48 +01:00
|
|
|
ExplainPropertyLong("Shared Written Blocks", usage->shared_blks_written, es);
|
|
|
|
ExplainPropertyLong("Local Hit Blocks", usage->local_blks_hit, es);
|
|
|
|
ExplainPropertyLong("Local Read Blocks", usage->local_blks_read, es);
|
2012-02-23 02:33:05 +01:00
|
|
|
ExplainPropertyLong("Local Dirtied Blocks", usage->local_blks_dirtied, es);
|
2009-12-15 05:57:48 +01:00
|
|
|
ExplainPropertyLong("Local Written Blocks", usage->local_blks_written, es);
|
|
|
|
ExplainPropertyLong("Temp Read Blocks", usage->temp_blks_read, es);
|
|
|
|
ExplainPropertyLong("Temp Written Blocks", usage->temp_blks_written, es);
|
2012-04-30 00:13:33 +02:00
|
|
|
ExplainPropertyFloat("I/O Read Time", INSTR_TIME_GET_MILLISEC(usage->blk_read_time), 3, es);
|
|
|
|
ExplainPropertyFloat("I/O Write Time", INSTR_TIME_GET_MILLISEC(usage->blk_write_time), 3, es);
|
2009-12-15 05:57:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/* Get ready to display the child plans */
|
2010-07-13 22:57:19 +02:00
|
|
|
haschildren = planstate->initPlan ||
|
|
|
|
outerPlanState(planstate) ||
|
|
|
|
innerPlanState(planstate) ||
|
2009-10-10 03:43:50 +02:00
|
|
|
IsA(plan, ModifyTable) ||
|
2009-08-10 07:46:50 +02:00
|
|
|
IsA(plan, Append) ||
|
2010-10-14 22:56:39 +02:00
|
|
|
IsA(plan, MergeAppend) ||
|
2009-08-10 07:46:50 +02:00
|
|
|
IsA(plan, BitmapAnd) ||
|
|
|
|
IsA(plan, BitmapOr) ||
|
|
|
|
IsA(plan, SubqueryScan) ||
|
|
|
|
planstate->subPlan;
|
|
|
|
if (haschildren)
|
2010-07-13 22:57:19 +02:00
|
|
|
{
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainOpenGroup("Plans", "Plans", false, es);
|
2010-07-13 22:57:19 +02:00
|
|
|
/* Pass current PlanState as head of ancestors list for children */
|
2010-11-23 21:27:50 +01:00
|
|
|
ancestors = lcons(planstate, ancestors);
|
2010-07-13 22:57:19 +02:00
|
|
|
}
|
2009-08-10 07:46:50 +02:00
|
|
|
|
1998-02-13 04:21:30 +01:00
|
|
|
/* initPlan-s */
|
2010-07-13 22:57:19 +02:00
|
|
|
if (planstate->initPlan)
|
|
|
|
ExplainSubPlans(planstate->initPlan, ancestors, "InitPlan", es);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/* lefttree */
|
2010-07-13 22:57:19 +02:00
|
|
|
if (outerPlanState(planstate))
|
|
|
|
ExplainNode(outerPlanState(planstate), ancestors,
|
2009-08-10 07:46:50 +02:00
|
|
|
"Outer", NULL, es);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/* righttree */
|
2010-07-13 22:57:19 +02:00
|
|
|
if (innerPlanState(planstate))
|
|
|
|
ExplainNode(innerPlanState(planstate), ancestors,
|
2009-08-10 07:46:50 +02:00
|
|
|
"Inner", NULL, es);
|
2000-09-29 20:21:41 +02:00
|
|
|
|
2009-07-24 23:08:42 +02:00
|
|
|
/* special child plans */
|
|
|
|
switch (nodeTag(plan))
|
2005-04-20 00:35:18 +02:00
|
|
|
{
|
2009-10-10 03:43:50 +02:00
|
|
|
case T_ModifyTable:
|
|
|
|
ExplainMemberNodes(((ModifyTable *) plan)->plans,
|
|
|
|
((ModifyTableState *) planstate)->mt_plans,
|
2010-07-13 22:57:19 +02:00
|
|
|
ancestors, es);
|
2009-10-10 03:43:50 +02:00
|
|
|
break;
|
2009-07-24 23:08:42 +02:00
|
|
|
case T_Append:
|
|
|
|
ExplainMemberNodes(((Append *) plan)->appendplans,
|
|
|
|
((AppendState *) planstate)->appendplans,
|
2010-07-13 22:57:19 +02:00
|
|
|
ancestors, es);
|
2009-07-24 23:08:42 +02:00
|
|
|
break;
|
2010-10-14 22:56:39 +02:00
|
|
|
case T_MergeAppend:
|
|
|
|
ExplainMemberNodes(((MergeAppend *) plan)->mergeplans,
|
|
|
|
((MergeAppendState *) planstate)->mergeplans,
|
|
|
|
ancestors, es);
|
|
|
|
break;
|
2009-07-24 23:08:42 +02:00
|
|
|
case T_BitmapAnd:
|
|
|
|
ExplainMemberNodes(((BitmapAnd *) plan)->bitmapplans,
|
|
|
|
((BitmapAndState *) planstate)->bitmapplans,
|
2010-07-13 22:57:19 +02:00
|
|
|
ancestors, es);
|
2009-07-24 23:08:42 +02:00
|
|
|
break;
|
|
|
|
case T_BitmapOr:
|
|
|
|
ExplainMemberNodes(((BitmapOr *) plan)->bitmapplans,
|
|
|
|
((BitmapOrState *) planstate)->bitmapplans,
|
2010-07-13 22:57:19 +02:00
|
|
|
ancestors, es);
|
2009-07-24 23:08:42 +02:00
|
|
|
break;
|
|
|
|
case T_SubqueryScan:
|
2010-07-13 22:57:19 +02:00
|
|
|
ExplainNode(((SubqueryScanState *) planstate)->subplan, ancestors,
|
|
|
|
"Subquery", NULL, es);
|
2009-07-24 23:08:42 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2000-09-29 20:21:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* subPlan-s */
|
2002-12-05 16:50:39 +01:00
|
|
|
if (planstate->subPlan)
|
2010-07-13 22:57:19 +02:00
|
|
|
ExplainSubPlans(planstate->subPlan, ancestors, "SubPlan", es);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
/* end of child plans */
|
|
|
|
if (haschildren)
|
2010-07-13 22:57:19 +02:00
|
|
|
{
|
2010-11-23 21:27:50 +01:00
|
|
|
ancestors = list_delete_first(ancestors);
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainCloseGroup("Plans", "Plans", false, es);
|
2010-07-13 22:57:19 +02:00
|
|
|
}
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
/* in text format, undo whatever indentation we added */
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
es->indent = save_indent;
|
|
|
|
|
|
|
|
ExplainCloseGroup("Plan",
|
|
|
|
relationship ? NULL : "Plan",
|
|
|
|
true, es);
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
2008-04-17 20:30:18 +02:00
|
|
|
/*
|
|
|
|
* Show the targetlist of a plan node
|
|
|
|
*/
|
|
|
|
static void
|
2010-07-13 22:57:19 +02:00
|
|
|
show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
|
2008-04-17 20:30:18 +02:00
|
|
|
{
|
2010-07-13 22:57:19 +02:00
|
|
|
Plan *plan = planstate->plan;
|
2008-04-17 20:30:18 +02:00
|
|
|
List *context;
|
2009-08-10 07:46:50 +02:00
|
|
|
List *result = NIL;
|
2008-04-17 20:30:18 +02:00
|
|
|
bool useprefix;
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
/* No work if empty tlist (this occurs eg in bitmap indexscans) */
|
|
|
|
if (plan->targetlist == NIL)
|
|
|
|
return;
|
|
|
|
/* The tlist of an Append isn't real helpful, so suppress it */
|
|
|
|
if (IsA(plan, Append))
|
|
|
|
return;
|
2010-10-14 22:56:39 +02:00
|
|
|
/* Likewise for MergeAppend and RecursiveUnion */
|
|
|
|
if (IsA(plan, MergeAppend))
|
|
|
|
return;
|
2008-10-04 23:56:55 +02:00
|
|
|
if (IsA(plan, RecursiveUnion))
|
|
|
|
return;
|
2008-04-17 20:30:18 +02:00
|
|
|
|
|
|
|
/* Set up deparsing context */
|
Improve performance of EXPLAIN with large range tables.
As of 9.3, ruleutils.c goes to some lengths to ensure that table and column
aliases used in its output are unique. Of course this takes more time than
was required before, which in itself isn't fatal. However, EXPLAIN was set
up so that recalculation of the unique aliases was repeated for each
subexpression printed in a plan. That results in O(N^2) time and memory
consumption for large plan trees, which did not happen in older branches.
Fortunately, the expensive work is the same across a whole plan tree,
so there is no need to repeat it; we can do most of the initialization
just once per query and re-use it for each subexpression. This buys
back most (not all) of the performance loss since 9.2.
We need an extra ExplainState field to hold the precalculated deparse
context. That's no problem in HEAD, but in the back branches, expanding
sizeof(ExplainState) seems risky because third-party extensions might
have local variables of that struct type. So, in 9.4 and 9.3, introduce
an auxiliary struct to keep sizeof(ExplainState) the same. We should
refactor the APIs to avoid such local variables in future, but that's
material for a separate HEAD-only commit.
Per gripe from Alexey Bashtanov. Back-patch to 9.3 where the issue
was introduced.
2015-01-15 19:18:12 +01:00
|
|
|
context = set_deparse_context_planstate(es->deparse_cxt,
|
|
|
|
(Node *) planstate,
|
|
|
|
ancestors);
|
2008-04-17 20:30:18 +02:00
|
|
|
useprefix = list_length(es->rtable) > 1;
|
|
|
|
|
2009-08-22 04:06:32 +02:00
|
|
|
/* Deparse each result column (we now include resjunk ones) */
|
2008-04-17 20:30:18 +02:00
|
|
|
foreach(lc, plan->targetlist)
|
|
|
|
{
|
|
|
|
TargetEntry *tle = (TargetEntry *) lfirst(lc);
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
result = lappend(result,
|
2010-02-26 03:01:40 +01:00
|
|
|
deparse_expression((Node *) tle->expr, context,
|
2009-08-22 04:06:32 +02:00
|
|
|
useprefix, false));
|
2008-04-17 20:30:18 +02:00
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/* Print results */
|
|
|
|
ExplainPropertyList("Output", result, es);
|
2008-04-17 20:30:18 +02:00
|
|
|
}
|
|
|
|
|
2002-03-12 01:52:10 +01:00
|
|
|
/*
|
2010-08-24 23:20:36 +02:00
|
|
|
* Show a generic expression
|
2002-03-12 01:52:10 +01:00
|
|
|
*/
|
|
|
|
static void
|
2010-08-24 23:20:36 +02:00
|
|
|
show_expression(Node *node, const char *qlabel,
|
|
|
|
PlanState *planstate, List *ancestors,
|
|
|
|
bool useprefix, ExplainState *es)
|
2002-03-12 01:52:10 +01:00
|
|
|
{
|
|
|
|
List *context;
|
|
|
|
char *exprstr;
|
|
|
|
|
2007-02-23 22:59:45 +01:00
|
|
|
/* Set up deparsing context */
|
Improve performance of EXPLAIN with large range tables.
As of 9.3, ruleutils.c goes to some lengths to ensure that table and column
aliases used in its output are unique. Of course this takes more time than
was required before, which in itself isn't fatal. However, EXPLAIN was set
up so that recalculation of the unique aliases was repeated for each
subexpression printed in a plan. That results in O(N^2) time and memory
consumption for large plan trees, which did not happen in older branches.
Fortunately, the expensive work is the same across a whole plan tree,
so there is no need to repeat it; we can do most of the initialization
just once per query and re-use it for each subexpression. This buys
back most (not all) of the performance loss since 9.2.
We need an extra ExplainState field to hold the precalculated deparse
context. That's no problem in HEAD, but in the back branches, expanding
sizeof(ExplainState) seems risky because third-party extensions might
have local variables of that struct type. So, in 9.4 and 9.3, introduce
an auxiliary struct to keep sizeof(ExplainState) the same. We should
refactor the APIs to avoid such local variables in future, but that's
material for a separate HEAD-only commit.
Per gripe from Alexey Bashtanov. Back-patch to 9.3 where the issue
was introduced.
2015-01-15 19:18:12 +01:00
|
|
|
context = set_deparse_context_planstate(es->deparse_cxt,
|
|
|
|
(Node *) planstate,
|
|
|
|
ancestors);
|
2002-03-12 01:52:10 +01:00
|
|
|
|
|
|
|
/* Deparse the expression */
|
2007-02-23 22:59:45 +01:00
|
|
|
exprstr = deparse_expression(node, context, useprefix, false);
|
2002-03-12 01:52:10 +01:00
|
|
|
|
2009-07-24 23:08:42 +02:00
|
|
|
/* And add to es->str */
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainPropertyText(qlabel, exprstr, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
}
|
|
|
|
|
2010-08-24 23:20:36 +02:00
|
|
|
/*
|
|
|
|
* Show a qualifier expression (which is a List with implicit AND semantics)
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_qual(List *qual, const char *qlabel,
|
|
|
|
PlanState *planstate, List *ancestors,
|
|
|
|
bool useprefix, ExplainState *es)
|
|
|
|
{
|
|
|
|
Node *node;
|
|
|
|
|
|
|
|
/* No work if empty qual */
|
|
|
|
if (qual == NIL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Convert AND list to explicit AND */
|
|
|
|
node = (Node *) make_ands_explicit(qual);
|
|
|
|
|
|
|
|
/* And show it */
|
|
|
|
show_expression(node, qlabel, planstate, ancestors, useprefix, es);
|
|
|
|
}
|
|
|
|
|
2002-03-12 01:52:10 +01:00
|
|
|
/*
|
2009-07-24 23:08:42 +02:00
|
|
|
* Show a qualifier expression for a scan plan node
|
2002-03-12 01:52:10 +01:00
|
|
|
*/
|
|
|
|
static void
|
2009-07-24 23:08:42 +02:00
|
|
|
show_scan_qual(List *qual, const char *qlabel,
|
2010-07-13 22:57:19 +02:00
|
|
|
PlanState *planstate, List *ancestors,
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainState *es)
|
2002-03-12 01:52:10 +01:00
|
|
|
{
|
2007-02-23 22:59:45 +01:00
|
|
|
bool useprefix;
|
2002-03-12 01:52:10 +01:00
|
|
|
|
2011-04-10 17:42:00 +02:00
|
|
|
useprefix = (IsA(planstate->plan, SubqueryScan) ||es->verbose);
|
2010-07-13 22:57:19 +02:00
|
|
|
show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
|
2009-07-24 23:08:42 +02:00
|
|
|
}
|
2002-03-12 01:52:10 +01:00
|
|
|
|
2009-07-24 23:08:42 +02:00
|
|
|
/*
|
|
|
|
* Show a qualifier expression for an upper-level plan node
|
|
|
|
*/
|
|
|
|
static void
|
2010-07-13 22:57:19 +02:00
|
|
|
show_upper_qual(List *qual, const char *qlabel,
|
|
|
|
PlanState *planstate, List *ancestors,
|
|
|
|
ExplainState *es)
|
2009-07-24 23:08:42 +02:00
|
|
|
{
|
|
|
|
bool useprefix;
|
2002-03-12 01:52:10 +01:00
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
useprefix = (list_length(es->rtable) > 1 || es->verbose);
|
2010-07-13 22:57:19 +02:00
|
|
|
show_qual(qual, qlabel, planstate, ancestors, useprefix, es);
|
2002-03-12 01:52:10 +01:00
|
|
|
}
|
|
|
|
|
2002-05-18 23:38:41 +02:00
|
|
|
/*
|
|
|
|
* Show the sort keys for a Sort node.
|
|
|
|
*/
|
|
|
|
static void
|
2010-07-13 22:57:19 +02:00
|
|
|
show_sort_keys(SortState *sortstate, List *ancestors, ExplainState *es)
|
2002-05-18 23:38:41 +02:00
|
|
|
{
|
2010-07-13 22:57:19 +02:00
|
|
|
Sort *plan = (Sort *) sortstate->ss.ps.plan;
|
2010-10-14 22:56:39 +02:00
|
|
|
|
2013-12-12 17:24:38 +01:00
|
|
|
show_sort_group_keys((PlanState *) sortstate, "Sort Key",
|
|
|
|
plan->numCols, plan->sortColIdx,
|
2015-01-17 00:18:52 +01:00
|
|
|
plan->sortOperators, plan->collations,
|
|
|
|
plan->nullsFirst,
|
2013-12-12 17:24:38 +01:00
|
|
|
ancestors, es);
|
2010-10-14 22:56:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Likewise, for a MergeAppend node.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_merge_append_keys(MergeAppendState *mstate, List *ancestors,
|
|
|
|
ExplainState *es)
|
|
|
|
{
|
|
|
|
MergeAppend *plan = (MergeAppend *) mstate->ps.plan;
|
|
|
|
|
2013-12-12 17:24:38 +01:00
|
|
|
show_sort_group_keys((PlanState *) mstate, "Sort Key",
|
|
|
|
plan->numCols, plan->sortColIdx,
|
2015-01-17 00:18:52 +01:00
|
|
|
plan->sortOperators, plan->collations,
|
|
|
|
plan->nullsFirst,
|
2013-12-12 17:24:38 +01:00
|
|
|
ancestors, es);
|
2010-10-14 22:56:39 +02:00
|
|
|
}
|
|
|
|
|
2013-12-12 17:24:38 +01:00
|
|
|
/*
|
|
|
|
* Show the grouping keys for an Agg node.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_agg_keys(AggState *astate, List *ancestors,
|
|
|
|
ExplainState *es)
|
|
|
|
{
|
|
|
|
Agg *plan = (Agg *) astate->ss.ps.plan;
|
|
|
|
|
|
|
|
if (plan->numCols > 0)
|
|
|
|
{
|
|
|
|
/* The key columns refer to the tlist of the child plan */
|
|
|
|
ancestors = lcons(astate, ancestors);
|
|
|
|
show_sort_group_keys(outerPlanState(astate), "Group Key",
|
|
|
|
plan->numCols, plan->grpColIdx,
|
2015-01-17 00:18:52 +01:00
|
|
|
NULL, NULL, NULL,
|
2013-12-12 17:24:38 +01:00
|
|
|
ancestors, es);
|
|
|
|
ancestors = list_delete_first(ancestors);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Show the grouping keys for a Group node.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_group_keys(GroupState *gstate, List *ancestors,
|
|
|
|
ExplainState *es)
|
|
|
|
{
|
|
|
|
Group *plan = (Group *) gstate->ss.ps.plan;
|
|
|
|
|
|
|
|
/* The key columns refer to the tlist of the child plan */
|
|
|
|
ancestors = lcons(gstate, ancestors);
|
|
|
|
show_sort_group_keys(outerPlanState(gstate), "Group Key",
|
|
|
|
plan->numCols, plan->grpColIdx,
|
2015-01-17 00:18:52 +01:00
|
|
|
NULL, NULL, NULL,
|
2013-12-12 17:24:38 +01:00
|
|
|
ancestors, es);
|
|
|
|
ancestors = list_delete_first(ancestors);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Common code to show sort/group keys, which are represented in plan nodes
|
2015-01-17 00:18:52 +01:00
|
|
|
* as arrays of targetlist indexes. If it's a sort key rather than a group
|
|
|
|
* key, also pass sort operators/collations/nullsFirst arrays.
|
2013-12-12 17:24:38 +01:00
|
|
|
*/
|
2010-10-14 22:56:39 +02:00
|
|
|
static void
|
2013-12-12 17:24:38 +01:00
|
|
|
show_sort_group_keys(PlanState *planstate, const char *qlabel,
|
|
|
|
int nkeys, AttrNumber *keycols,
|
2015-01-17 00:18:52 +01:00
|
|
|
Oid *sortOperators, Oid *collations, bool *nullsFirst,
|
2013-12-12 17:24:38 +01:00
|
|
|
List *ancestors, ExplainState *es)
|
2010-10-14 22:56:39 +02:00
|
|
|
{
|
|
|
|
Plan *plan = planstate->plan;
|
2002-05-18 23:38:41 +02:00
|
|
|
List *context;
|
2009-08-10 07:46:50 +02:00
|
|
|
List *result = NIL;
|
2015-01-17 00:18:52 +01:00
|
|
|
StringInfoData sortkeybuf;
|
2002-05-18 23:38:41 +02:00
|
|
|
bool useprefix;
|
|
|
|
int keyno;
|
|
|
|
|
|
|
|
if (nkeys <= 0)
|
|
|
|
return;
|
|
|
|
|
2015-01-17 00:18:52 +01:00
|
|
|
initStringInfo(&sortkeybuf);
|
|
|
|
|
2007-02-23 22:59:45 +01:00
|
|
|
/* Set up deparsing context */
|
Improve performance of EXPLAIN with large range tables.
As of 9.3, ruleutils.c goes to some lengths to ensure that table and column
aliases used in its output are unique. Of course this takes more time than
was required before, which in itself isn't fatal. However, EXPLAIN was set
up so that recalculation of the unique aliases was repeated for each
subexpression printed in a plan. That results in O(N^2) time and memory
consumption for large plan trees, which did not happen in older branches.
Fortunately, the expensive work is the same across a whole plan tree,
so there is no need to repeat it; we can do most of the initialization
just once per query and re-use it for each subexpression. This buys
back most (not all) of the performance loss since 9.2.
We need an extra ExplainState field to hold the precalculated deparse
context. That's no problem in HEAD, but in the back branches, expanding
sizeof(ExplainState) seems risky because third-party extensions might
have local variables of that struct type. So, in 9.4 and 9.3, introduce
an auxiliary struct to keep sizeof(ExplainState) the same. We should
refactor the APIs to avoid such local variables in future, but that's
material for a separate HEAD-only commit.
Per gripe from Alexey Bashtanov. Back-patch to 9.3 where the issue
was introduced.
2015-01-15 19:18:12 +01:00
|
|
|
context = set_deparse_context_planstate(es->deparse_cxt,
|
|
|
|
(Node *) planstate,
|
|
|
|
ancestors);
|
2009-08-10 07:46:50 +02:00
|
|
|
useprefix = (list_length(es->rtable) > 1 || es->verbose);
|
2002-05-18 23:38:41 +02:00
|
|
|
|
2003-05-06 02:20:33 +02:00
|
|
|
for (keyno = 0; keyno < nkeys; keyno++)
|
2002-05-18 23:38:41 +02:00
|
|
|
{
|
|
|
|
/* find key expression in tlist */
|
2003-05-06 02:20:33 +02:00
|
|
|
AttrNumber keyresno = keycols[keyno];
|
2010-10-14 22:56:39 +02:00
|
|
|
TargetEntry *target = get_tle_by_resno(plan->targetlist,
|
2010-07-13 22:57:19 +02:00
|
|
|
keyresno);
|
2015-01-17 00:18:52 +01:00
|
|
|
char *exprstr;
|
2003-05-06 02:20:33 +02:00
|
|
|
|
2003-08-11 22:46:47 +02:00
|
|
|
if (!target)
|
2003-07-20 23:56:35 +02:00
|
|
|
elog(ERROR, "no tlist entry for key %d", keyresno);
|
2003-08-11 22:46:47 +02:00
|
|
|
/* Deparse the expression, showing any top-level cast */
|
|
|
|
exprstr = deparse_expression((Node *) target->expr, context,
|
|
|
|
useprefix, true);
|
2015-01-17 00:18:52 +01:00
|
|
|
resetStringInfo(&sortkeybuf);
|
|
|
|
appendStringInfoString(&sortkeybuf, exprstr);
|
|
|
|
/* Append sort order information, if relevant */
|
|
|
|
if (sortOperators != NULL)
|
|
|
|
show_sortorder_options(&sortkeybuf,
|
|
|
|
(Node *) target->expr,
|
|
|
|
sortOperators[keyno],
|
|
|
|
collations[keyno],
|
|
|
|
nullsFirst[keyno]);
|
|
|
|
/* Emit one property-list item per sort key */
|
|
|
|
result = lappend(result, pstrdup(sortkeybuf.data));
|
2002-05-18 23:38:41 +02:00
|
|
|
}
|
|
|
|
|
2013-12-12 17:24:38 +01:00
|
|
|
ExplainPropertyList(qlabel, result, es);
|
2002-05-18 23:38:41 +02:00
|
|
|
}
|
2007-05-04 23:29:53 +02:00
|
|
|
|
2015-01-17 00:18:52 +01:00
|
|
|
/*
|
|
|
|
* Append nondefault characteristics of the sort ordering of a column to buf
|
|
|
|
* (collation, direction, NULLS FIRST/LAST)
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_sortorder_options(StringInfo buf, Node *sortexpr,
|
|
|
|
Oid sortOperator, Oid collation, bool nullsFirst)
|
|
|
|
{
|
|
|
|
Oid sortcoltype = exprType(sortexpr);
|
|
|
|
bool reverse = false;
|
|
|
|
TypeCacheEntry *typentry;
|
|
|
|
|
|
|
|
typentry = lookup_type_cache(sortcoltype,
|
|
|
|
TYPECACHE_LT_OPR | TYPECACHE_GT_OPR);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Print COLLATE if it's not default. There are some cases where this is
|
|
|
|
* redundant, eg if expression is a column whose declared collation is
|
|
|
|
* that collation, but it's hard to distinguish that here.
|
|
|
|
*/
|
|
|
|
if (OidIsValid(collation) && collation != DEFAULT_COLLATION_OID)
|
|
|
|
{
|
|
|
|
char *collname = get_collation_name(collation);
|
|
|
|
|
|
|
|
if (collname == NULL)
|
|
|
|
elog(ERROR, "cache lookup failed for collation %u", collation);
|
|
|
|
appendStringInfo(buf, " COLLATE %s", quote_identifier(collname));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Print direction if not ASC, or USING if non-default sort operator */
|
|
|
|
if (sortOperator == typentry->gt_opr)
|
|
|
|
{
|
|
|
|
appendStringInfoString(buf, " DESC");
|
|
|
|
reverse = true;
|
|
|
|
}
|
|
|
|
else if (sortOperator != typentry->lt_opr)
|
|
|
|
{
|
|
|
|
char *opname = get_opname(sortOperator);
|
|
|
|
|
|
|
|
if (opname == NULL)
|
|
|
|
elog(ERROR, "cache lookup failed for operator %u", sortOperator);
|
|
|
|
appendStringInfo(buf, " USING %s", opname);
|
|
|
|
/* Determine whether operator would be considered ASC or DESC */
|
|
|
|
(void) get_equality_op_for_ordering_op(sortOperator, &reverse);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add NULLS FIRST/LAST only if it wouldn't be default */
|
|
|
|
if (nullsFirst && !reverse)
|
|
|
|
{
|
|
|
|
appendStringInfoString(buf, " NULLS FIRST");
|
|
|
|
}
|
|
|
|
else if (!nullsFirst && reverse)
|
|
|
|
{
|
|
|
|
appendStringInfoString(buf, " NULLS LAST");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-05-04 23:29:53 +02:00
|
|
|
/*
|
2009-08-10 07:46:50 +02:00
|
|
|
* If it's EXPLAIN ANALYZE, show tuplesort stats for a sort node
|
2007-05-04 23:29:53 +02:00
|
|
|
*/
|
|
|
|
static void
|
2009-08-10 07:46:50 +02:00
|
|
|
show_sort_info(SortState *sortstate, ExplainState *es)
|
2007-05-04 23:29:53 +02:00
|
|
|
{
|
|
|
|
Assert(IsA(sortstate, SortState));
|
2009-07-27 01:34:18 +02:00
|
|
|
if (es->analyze && sortstate->sort_Done &&
|
2007-05-04 23:29:53 +02:00
|
|
|
sortstate->tuplesortstate != NULL)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
|
2009-08-10 07:46:50 +02:00
|
|
|
const char *sortMethod;
|
|
|
|
const char *spaceType;
|
|
|
|
long spaceUsed;
|
2007-05-04 23:29:53 +02:00
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
tuplesort_get_stats(state, &sortMethod, &spaceType, &spaceUsed);
|
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
2010-12-06 12:35:47 +01:00
|
|
|
appendStringInfo(es->str, "Sort Method: %s %s: %ldkB\n",
|
2009-08-10 07:46:50 +02:00
|
|
|
sortMethod, spaceType, spaceUsed);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ExplainPropertyText("Sort Method", sortMethod, es);
|
|
|
|
ExplainPropertyLong("Sort Space Used", spaceUsed, es);
|
|
|
|
ExplainPropertyText("Sort Space Type", spaceType, es);
|
|
|
|
}
|
2007-05-04 23:29:53 +02:00
|
|
|
}
|
|
|
|
}
|
2007-05-25 19:54:25 +02:00
|
|
|
|
2010-02-01 16:43:36 +01:00
|
|
|
/*
|
|
|
|
* Show information on hash buckets/batches.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_hash_info(HashState *hashstate, ExplainState *es)
|
|
|
|
{
|
|
|
|
HashJoinTable hashtable;
|
|
|
|
|
|
|
|
Assert(IsA(hashstate, HashState));
|
|
|
|
hashtable = hashstate->hashtable;
|
|
|
|
|
|
|
|
if (hashtable)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
long spacePeakKb = (hashtable->spacePeak + 1023) / 1024;
|
|
|
|
|
2010-02-01 16:43:36 +01:00
|
|
|
if (es->format != EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
ExplainPropertyLong("Hash Buckets", hashtable->nbuckets, es);
|
Increase number of hash join buckets for underestimate.
If we expect batching at the very beginning, we size nbuckets for
"full work_mem" (see how many tuples we can get into work_mem,
while not breaking NTUP_PER_BUCKET threshold).
If we expect to be fine without batching, we start with the 'right'
nbuckets and track the optimal nbuckets as we go (without actually
resizing the hash table). Once we hit work_mem (considering the
optimal nbuckets value), we keep the value.
At the end of the first batch, we check whether (nbuckets !=
nbuckets_optimal) and resize the hash table if needed. Also, we
keep this value for all batches (it's OK because it assumes full
work_mem, and it makes the batchno evaluation trivial). So the
resize happens only once.
There could be cases where it would improve performance to allow
the NTUP_PER_BUCKET threshold to be exceeded to keep everything in
one batch rather than spilling to a second batch, but attempts to
generate such a case have so far been unsuccessful; that issue may
be addressed with a follow-on patch after further investigation.
Tomas Vondra with minor format and comment cleanup by me
Reviewed by Robert Haas, Heikki Linnakangas, and Kevin Grittner
2014-10-13 17:16:36 +02:00
|
|
|
ExplainPropertyLong("Original Hash Buckets",
|
|
|
|
hashtable->nbuckets_original, es);
|
2010-02-01 16:43:36 +01:00
|
|
|
ExplainPropertyLong("Hash Batches", hashtable->nbatch, es);
|
|
|
|
ExplainPropertyLong("Original Hash Batches",
|
|
|
|
hashtable->nbatch_original, es);
|
|
|
|
ExplainPropertyLong("Peak Memory Usage", spacePeakKb, es);
|
|
|
|
}
|
2014-11-20 18:32:19 +01:00
|
|
|
else if (hashtable->nbatch_original != hashtable->nbatch ||
|
|
|
|
hashtable->nbuckets_original != hashtable->nbuckets)
|
2010-02-01 16:43:36 +01:00
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfo(es->str,
|
2014-11-20 18:32:19 +01:00
|
|
|
"Buckets: %d (originally %d) Batches: %d (originally %d) Memory Usage: %ldkB\n",
|
|
|
|
hashtable->nbuckets,
|
|
|
|
hashtable->nbuckets_original,
|
|
|
|
hashtable->nbatch,
|
|
|
|
hashtable->nbatch_original,
|
|
|
|
spacePeakKb);
|
2010-02-01 16:43:36 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfo(es->str,
|
2010-02-26 03:01:40 +01:00
|
|
|
"Buckets: %d Batches: %d Memory Usage: %ldkB\n",
|
2010-02-01 16:43:36 +01:00
|
|
|
hashtable->nbuckets, hashtable->nbatch,
|
|
|
|
spacePeakKb);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-13 20:42:16 +01:00
|
|
|
/*
|
|
|
|
* If it's EXPLAIN ANALYZE, show exact/lossy pages for a BitmapHeapScan node
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
|
|
|
|
{
|
|
|
|
if (es->format != EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
ExplainPropertyLong("Exact Heap Blocks", planstate->exact_pages, es);
|
|
|
|
ExplainPropertyLong("Lossy Heap Blocks", planstate->lossy_pages, es);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-07-14 13:40:14 +02:00
|
|
|
if (planstate->exact_pages > 0 || planstate->lossy_pages > 0)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfoString(es->str, "Heap Blocks:");
|
|
|
|
if (planstate->exact_pages > 0)
|
|
|
|
appendStringInfo(es->str, " exact=%ld", planstate->exact_pages);
|
|
|
|
if (planstate->lossy_pages > 0)
|
|
|
|
appendStringInfo(es->str, " lossy=%ld", planstate->lossy_pages);
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
}
|
2014-01-13 20:42:16 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-09-22 17:29:18 +02:00
|
|
|
/*
|
|
|
|
* If it's EXPLAIN ANALYZE, show instrumentation information for a plan node
|
|
|
|
*
|
|
|
|
* "which" identifies which instrumentation counter to print
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_instrumentation_count(const char *qlabel, int which,
|
|
|
|
PlanState *planstate, ExplainState *es)
|
|
|
|
{
|
|
|
|
double nfiltered;
|
|
|
|
double nloops;
|
|
|
|
|
|
|
|
if (!es->analyze || !planstate->instrument)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (which == 2)
|
|
|
|
nfiltered = planstate->instrument->nfiltered2;
|
|
|
|
else
|
|
|
|
nfiltered = planstate->instrument->nfiltered1;
|
|
|
|
nloops = planstate->instrument->nloops;
|
|
|
|
|
|
|
|
/* In text mode, suppress zero counts; they're not interesting enough */
|
|
|
|
if (nfiltered > 0 || es->format != EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
if (nloops > 0)
|
|
|
|
ExplainPropertyFloat(qlabel, nfiltered / nloops, 0, es);
|
|
|
|
else
|
|
|
|
ExplainPropertyFloat(qlabel, 0.0, 0, es);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-20 06:17:18 +01:00
|
|
|
/*
|
|
|
|
* Show extra information for a ForeignScan node.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
|
|
|
|
{
|
|
|
|
FdwRoutine *fdwroutine = fsstate->fdwroutine;
|
|
|
|
|
|
|
|
/* Let the FDW emit whatever fields it wants */
|
2013-03-10 19:14:53 +01:00
|
|
|
if (fdwroutine->ExplainForeignScan != NULL)
|
|
|
|
fdwroutine->ExplainForeignScan(fsstate, es);
|
2011-02-20 06:17:18 +01:00
|
|
|
}
|
|
|
|
|
2007-05-25 19:54:25 +02:00
|
|
|
/*
|
|
|
|
* Fetch the name of an index in an EXPLAIN
|
|
|
|
*
|
|
|
|
* We allow plugins to get control here so that plans involving hypothetical
|
|
|
|
* indexes can be explained.
|
|
|
|
*/
|
|
|
|
static const char *
|
|
|
|
explain_get_index_name(Oid indexId)
|
|
|
|
{
|
2007-11-15 22:14:46 +01:00
|
|
|
const char *result;
|
2007-05-25 19:54:25 +02:00
|
|
|
|
|
|
|
if (explain_get_index_name_hook)
|
|
|
|
result = (*explain_get_index_name_hook) (indexId);
|
|
|
|
else
|
|
|
|
result = NULL;
|
|
|
|
if (result == NULL)
|
|
|
|
{
|
|
|
|
/* default behavior: look in the catalogs and quote it */
|
|
|
|
result = get_rel_name(indexId);
|
|
|
|
if (result == NULL)
|
|
|
|
elog(ERROR, "cache lookup failed for index %u", indexId);
|
|
|
|
result = quote_identifier(result);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2009-07-24 23:08:42 +02:00
|
|
|
|
2011-10-11 20:20:06 +02:00
|
|
|
/*
|
|
|
|
* Add some additional details about an IndexScan or IndexOnlyScan
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
|
|
|
|
ExplainState *es)
|
|
|
|
{
|
|
|
|
const char *indexname = explain_get_index_name(indexid);
|
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
if (ScanDirectionIsBackward(indexorderdir))
|
|
|
|
appendStringInfoString(es->str, " Backward");
|
|
|
|
appendStringInfo(es->str, " using %s", indexname);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const char *scandir;
|
|
|
|
|
|
|
|
switch (indexorderdir)
|
|
|
|
{
|
|
|
|
case BackwardScanDirection:
|
|
|
|
scandir = "Backward";
|
|
|
|
break;
|
|
|
|
case NoMovementScanDirection:
|
|
|
|
scandir = "NoMovement";
|
|
|
|
break;
|
|
|
|
case ForwardScanDirection:
|
|
|
|
scandir = "Forward";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
scandir = "???";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ExplainPropertyText("Scan Direction", scandir, es);
|
|
|
|
ExplainPropertyText("Index Name", indexname, es);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-24 23:08:42 +02:00
|
|
|
/*
|
|
|
|
* Show the target of a Scan node
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainScanTarget(Scan *plan, ExplainState *es)
|
2011-03-01 17:32:13 +01:00
|
|
|
{
|
|
|
|
ExplainTargetRel((Plan *) plan, plan->scanrelid, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Show the target of a ModifyTable node
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainModifyTarget(ModifyTable *plan, ExplainState *es)
|
|
|
|
{
|
2015-02-18 00:04:11 +01:00
|
|
|
ExplainTargetRel((Plan *) plan, plan->nominalRelation, es);
|
2011-03-01 17:32:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Show the target relation of a scan or modify node
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
|
2009-07-24 23:08:42 +02:00
|
|
|
{
|
|
|
|
char *objectname = NULL;
|
2009-08-10 07:46:50 +02:00
|
|
|
char *namespace = NULL;
|
|
|
|
const char *objecttag = NULL;
|
2009-07-24 23:08:42 +02:00
|
|
|
RangeTblEntry *rte;
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
char *refname;
|
2009-07-24 23:08:42 +02:00
|
|
|
|
2011-03-01 17:32:13 +01:00
|
|
|
rte = rt_fetch(rti, es->rtable);
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
refname = (char *) list_nth(es->rtable_names, rti - 1);
|
2012-12-31 21:13:26 +01:00
|
|
|
if (refname == NULL)
|
|
|
|
refname = rte->eref->aliasname;
|
2009-07-24 23:08:42 +02:00
|
|
|
|
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
|
|
|
case T_SeqScan:
|
|
|
|
case T_IndexScan:
|
2011-10-11 20:20:06 +02:00
|
|
|
case T_IndexOnlyScan:
|
2009-07-24 23:08:42 +02:00
|
|
|
case T_BitmapHeapScan:
|
|
|
|
case T_TidScan:
|
2011-02-20 06:17:18 +01:00
|
|
|
case T_ForeignScan:
|
2014-11-20 18:32:19 +01:00
|
|
|
case T_CustomScan:
|
2011-03-01 17:32:13 +01:00
|
|
|
case T_ModifyTable:
|
2009-07-24 23:08:42 +02:00
|
|
|
/* Assert it's on a real relation */
|
|
|
|
Assert(rte->rtekind == RTE_RELATION);
|
|
|
|
objectname = get_rel_name(rte->relid);
|
2009-08-10 07:46:50 +02:00
|
|
|
if (es->verbose)
|
|
|
|
namespace = get_namespace_name(get_rel_namespace(rte->relid));
|
|
|
|
objecttag = "Relation Name";
|
2009-07-24 23:08:42 +02:00
|
|
|
break;
|
|
|
|
case T_FunctionScan:
|
|
|
|
{
|
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...)
as a single FROM-clause entry. The result is the concatenation of the
first row from each function, followed by the second row from each
function, etc; with NULLs inserted if any function produces fewer rows than
others. This is believed to be a much more useful behavior than what
Postgres currently does with multiple SRFs in a SELECT list.
This syntax also provides a reasonable way to combine use of column
definition lists with WITH ORDINALITY: put the column definition list
inside TABLE(), where it's clear that it doesn't control the ordinality
column as well.
Also implement SQL-compliant multiple-argument UNNEST(), by turning
UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)).
The SQL standard specifies TABLE() with only a single function, not
multiple functions, and it seems to require an implicit UNNEST() which is
not what this patch does. There may be something wrong with that reading
of the spec, though, because if it's right then the spec's TABLE() is just
a pointless alternative spelling of UNNEST(). After further review of
that, we might choose to adopt a different syntax for what this patch does,
but in any case this functionality seems clearly worthwhile.
Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and
significantly revised by me
2013-11-22 01:37:02 +01:00
|
|
|
FunctionScan *fscan = (FunctionScan *) plan;
|
2009-07-24 23:08:42 +02:00
|
|
|
|
|
|
|
/* Assert it's on a RangeFunction */
|
|
|
|
Assert(rte->rtekind == RTE_FUNCTION);
|
|
|
|
|
|
|
|
/*
|
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...)
as a single FROM-clause entry. The result is the concatenation of the
first row from each function, followed by the second row from each
function, etc; with NULLs inserted if any function produces fewer rows than
others. This is believed to be a much more useful behavior than what
Postgres currently does with multiple SRFs in a SELECT list.
This syntax also provides a reasonable way to combine use of column
definition lists with WITH ORDINALITY: put the column definition list
inside TABLE(), where it's clear that it doesn't control the ordinality
column as well.
Also implement SQL-compliant multiple-argument UNNEST(), by turning
UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)).
The SQL standard specifies TABLE() with only a single function, not
multiple functions, and it seems to require an implicit UNNEST() which is
not what this patch does. There may be something wrong with that reading
of the spec, though, because if it's right then the spec's TABLE() is just
a pointless alternative spelling of UNNEST(). After further review of
that, we might choose to adopt a different syntax for what this patch does,
but in any case this functionality seems clearly worthwhile.
Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and
significantly revised by me
2013-11-22 01:37:02 +01:00
|
|
|
* If the expression is still a function call of a single
|
|
|
|
* function, we can get the real name of the function.
|
|
|
|
* Otherwise, punt. (Even if it was a single function call
|
|
|
|
* originally, the optimizer could have simplified it away.)
|
2009-07-24 23:08:42 +02:00
|
|
|
*/
|
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...)
as a single FROM-clause entry. The result is the concatenation of the
first row from each function, followed by the second row from each
function, etc; with NULLs inserted if any function produces fewer rows than
others. This is believed to be a much more useful behavior than what
Postgres currently does with multiple SRFs in a SELECT list.
This syntax also provides a reasonable way to combine use of column
definition lists with WITH ORDINALITY: put the column definition list
inside TABLE(), where it's clear that it doesn't control the ordinality
column as well.
Also implement SQL-compliant multiple-argument UNNEST(), by turning
UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)).
The SQL standard specifies TABLE() with only a single function, not
multiple functions, and it seems to require an implicit UNNEST() which is
not what this patch does. There may be something wrong with that reading
of the spec, though, because if it's right then the spec's TABLE() is just
a pointless alternative spelling of UNNEST(). After further review of
that, we might choose to adopt a different syntax for what this patch does,
but in any case this functionality seems clearly worthwhile.
Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and
significantly revised by me
2013-11-22 01:37:02 +01:00
|
|
|
if (list_length(fscan->functions) == 1)
|
2009-07-24 23:08:42 +02:00
|
|
|
{
|
Support multi-argument UNNEST(), and TABLE() syntax for multiple functions.
This patch adds the ability to write TABLE( function1(), function2(), ...)
as a single FROM-clause entry. The result is the concatenation of the
first row from each function, followed by the second row from each
function, etc; with NULLs inserted if any function produces fewer rows than
others. This is believed to be a much more useful behavior than what
Postgres currently does with multiple SRFs in a SELECT list.
This syntax also provides a reasonable way to combine use of column
definition lists with WITH ORDINALITY: put the column definition list
inside TABLE(), where it's clear that it doesn't control the ordinality
column as well.
Also implement SQL-compliant multiple-argument UNNEST(), by turning
UNNEST(a,b,c) into TABLE(unnest(a), unnest(b), unnest(c)).
The SQL standard specifies TABLE() with only a single function, not
multiple functions, and it seems to require an implicit UNNEST() which is
not what this patch does. There may be something wrong with that reading
of the spec, though, because if it's right then the spec's TABLE() is just
a pointless alternative spelling of UNNEST(). After further review of
that, we might choose to adopt a different syntax for what this patch does,
but in any case this functionality seems clearly worthwhile.
Andrew Gierth, reviewed by Zoltán Böszörményi and Heikki Linnakangas, and
significantly revised by me
2013-11-22 01:37:02 +01:00
|
|
|
RangeTblFunction *rtfunc = (RangeTblFunction *) linitial(fscan->functions);
|
|
|
|
|
|
|
|
if (IsA(rtfunc->funcexpr, FuncExpr))
|
|
|
|
{
|
|
|
|
FuncExpr *funcexpr = (FuncExpr *) rtfunc->funcexpr;
|
|
|
|
Oid funcid = funcexpr->funcid;
|
|
|
|
|
|
|
|
objectname = get_func_name(funcid);
|
|
|
|
if (es->verbose)
|
|
|
|
namespace =
|
|
|
|
get_namespace_name(get_func_namespace(funcid));
|
|
|
|
}
|
2009-07-24 23:08:42 +02:00
|
|
|
}
|
2009-08-10 07:46:50 +02:00
|
|
|
objecttag = "Function Name";
|
2009-07-24 23:08:42 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_ValuesScan:
|
|
|
|
Assert(rte->rtekind == RTE_VALUES);
|
|
|
|
break;
|
|
|
|
case T_CteScan:
|
|
|
|
/* Assert it's on a non-self-reference CTE */
|
|
|
|
Assert(rte->rtekind == RTE_CTE);
|
|
|
|
Assert(!rte->self_reference);
|
|
|
|
objectname = rte->ctename;
|
2009-08-10 07:46:50 +02:00
|
|
|
objecttag = "CTE Name";
|
2009-07-24 23:08:42 +02:00
|
|
|
break;
|
|
|
|
case T_WorkTableScan:
|
|
|
|
/* Assert it's on a self-reference CTE */
|
|
|
|
Assert(rte->rtekind == RTE_CTE);
|
|
|
|
Assert(rte->self_reference);
|
|
|
|
objectname = rte->ctename;
|
2009-08-10 07:46:50 +02:00
|
|
|
objecttag = "CTE Name";
|
2009-07-24 23:08:42 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
appendStringInfoString(es->str, " on");
|
|
|
|
if (namespace != NULL)
|
|
|
|
appendStringInfo(es->str, " %s.%s", quote_identifier(namespace),
|
|
|
|
quote_identifier(objectname));
|
|
|
|
else if (objectname != NULL)
|
|
|
|
appendStringInfo(es->str, " %s", quote_identifier(objectname));
|
2012-12-31 21:13:26 +01:00
|
|
|
if (objectname == NULL || strcmp(refname, objectname) != 0)
|
Improve ruleutils.c's heuristics for dealing with rangetable aliases.
The previous scheme had bugs in some corner cases involving tables that had
been renamed since a view was made. This could result in dumped views that
failed to reload or reloaded incorrectly, as seen in bug #7553 from Lloyd
Albin, as well as in some pgsql-hackers discussion back in January. Also,
its behavior for printing EXPLAIN plans was sometimes confusing because of
willingness to use the same alias for multiple RTEs (it was Ashutosh
Bapat's complaint about that aspect that started the January thread).
To fix, ensure that each RTE in the query has a unique unqualified alias,
by modifying the alias if necessary (we add "_" and digits as needed to
create a non-conflicting name). Then we can just print its variables with
that alias, avoiding the confusing and bug-prone scheme of sometimes
schema-qualifying variable names. In EXPLAIN, it proves to be expedient to
take the further step of only assigning such aliases to RTEs that are
actually referenced in the query, since the planner has a habit of
generating extra RTEs with the same alias in situations such as
inheritance-tree expansion.
Although this fixes a bug of very long standing, I'm hesitant to back-patch
such a noticeable behavioral change. My experiments while creating a
regression test convinced me that actually incorrect output (as opposed to
confusing output) occurs only in very narrow cases, which is backed up by
the lack of previous complaints from the field. So we may be better off
living with it in released branches; and in any case it'd be smart to let
this ripen awhile in HEAD before we consider back-patching it.
2012-09-22 01:03:10 +02:00
|
|
|
appendStringInfo(es->str, " %s", quote_identifier(refname));
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (objecttag != NULL && objectname != NULL)
|
|
|
|
ExplainPropertyText(objecttag, objectname, es);
|
|
|
|
if (namespace != NULL)
|
|
|
|
ExplainPropertyText("Schema", namespace, es);
|
2012-12-31 21:13:26 +01:00
|
|
|
ExplainPropertyText("Alias", refname, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
2009-07-24 23:08:42 +02:00
|
|
|
}
|
|
|
|
|
2013-03-10 19:14:53 +01:00
|
|
|
/*
|
|
|
|
* Show extra information for a ModifyTable node
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
|
|
|
|
{
|
|
|
|
FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the first target relation is a foreign table, call its FDW to
|
2014-05-06 18:12:18 +02:00
|
|
|
* display whatever additional fields it wants to. For now, we ignore the
|
2013-03-10 19:14:53 +01:00
|
|
|
* possibility of other targets being foreign tables, although the API for
|
|
|
|
* ExplainForeignModify is designed to allow them to be processed.
|
|
|
|
*/
|
|
|
|
if (fdwroutine != NULL &&
|
|
|
|
fdwroutine->ExplainForeignModify != NULL)
|
|
|
|
{
|
|
|
|
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
|
|
|
|
List *fdw_private = (List *) linitial(node->fdwPrivLists);
|
|
|
|
|
|
|
|
fdwroutine->ExplainForeignModify(mtstate,
|
|
|
|
mtstate->resultRelInfo,
|
|
|
|
fdw_private,
|
|
|
|
0,
|
|
|
|
es);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-24 23:08:42 +02:00
|
|
|
/*
|
2010-10-14 22:56:39 +02:00
|
|
|
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
|
|
|
|
* BitmapAnd, or BitmapOr node.
|
2009-07-24 23:08:42 +02:00
|
|
|
*
|
2010-07-13 22:57:19 +02:00
|
|
|
* The ancestors list should already contain the immediate parent of these
|
|
|
|
* plans.
|
|
|
|
*
|
|
|
|
* Note: we don't actually need to examine the Plan list members, but
|
|
|
|
* we need the list in order to determine the length of the PlanState array.
|
2009-07-24 23:08:42 +02:00
|
|
|
*/
|
|
|
|
static void
|
2010-07-13 22:57:19 +02:00
|
|
|
ExplainMemberNodes(List *plans, PlanState **planstates,
|
|
|
|
List *ancestors, ExplainState *es)
|
2009-07-24 23:08:42 +02:00
|
|
|
{
|
2010-07-13 22:57:19 +02:00
|
|
|
int nplans = list_length(plans);
|
|
|
|
int j;
|
2009-07-24 23:08:42 +02:00
|
|
|
|
2010-07-13 22:57:19 +02:00
|
|
|
for (j = 0; j < nplans; j++)
|
|
|
|
ExplainNode(planstates[j], ancestors,
|
|
|
|
"Member", NULL, es);
|
2009-07-24 23:08:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Explain a list of SubPlans (or initPlans, which also use SubPlan nodes).
|
2010-07-13 22:57:19 +02:00
|
|
|
*
|
|
|
|
* The ancestors list should already contain the immediate parent of these
|
|
|
|
* SubPlanStates.
|
2009-07-24 23:08:42 +02:00
|
|
|
*/
|
|
|
|
static void
|
2010-07-13 22:57:19 +02:00
|
|
|
ExplainSubPlans(List *plans, List *ancestors,
|
|
|
|
const char *relationship, ExplainState *es)
|
2009-07-24 23:08:42 +02:00
|
|
|
{
|
|
|
|
ListCell *lst;
|
|
|
|
|
|
|
|
foreach(lst, plans)
|
|
|
|
{
|
|
|
|
SubPlanState *sps = (SubPlanState *) lfirst(lst);
|
|
|
|
SubPlan *sp = (SubPlan *) sps->xprstate.expr;
|
|
|
|
|
2010-07-13 22:57:19 +02:00
|
|
|
ExplainNode(sps->planstate, ancestors,
|
|
|
|
relationship, sp->plan_name, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Explain a property, such as sort keys or targets, that takes the form of
|
|
|
|
* a list of unlabeled items. "data" is a list of C strings.
|
|
|
|
*/
|
2011-02-20 06:17:18 +01:00
|
|
|
void
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainPropertyList(const char *qlabel, List *data, ExplainState *es)
|
|
|
|
{
|
|
|
|
ListCell *lc;
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfo(es->str, "%s: ", qlabel);
|
|
|
|
foreach(lc, data)
|
|
|
|
{
|
|
|
|
if (!first)
|
|
|
|
appendStringInfoString(es->str, ", ");
|
|
|
|
appendStringInfoString(es->str, (const char *) lfirst(lc));
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
ExplainXMLTag(qlabel, X_OPENING, es);
|
|
|
|
foreach(lc, data)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
char *str;
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2 + 2);
|
|
|
|
appendStringInfoString(es->str, "<Item>");
|
|
|
|
str = escape_xml((const char *) lfirst(lc));
|
|
|
|
appendStringInfoString(es->str, str);
|
|
|
|
pfree(str);
|
|
|
|
appendStringInfoString(es->str, "</Item>\n");
|
|
|
|
}
|
|
|
|
ExplainXMLTag(qlabel, X_CLOSING, es);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
|
|
|
ExplainJSONLineEnding(es);
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
escape_json(es->str, qlabel);
|
|
|
|
appendStringInfoString(es->str, ": [");
|
|
|
|
foreach(lc, data)
|
|
|
|
{
|
|
|
|
if (!first)
|
|
|
|
appendStringInfoString(es->str, ", ");
|
|
|
|
escape_json(es->str, (const char *) lfirst(lc));
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
appendStringInfoChar(es->str, ']');
|
|
|
|
break;
|
2009-12-11 02:33:35 +01:00
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_YAML:
|
|
|
|
ExplainYAMLLineStarting(es);
|
2010-06-10 03:26:30 +02:00
|
|
|
appendStringInfo(es->str, "%s: ", qlabel);
|
2009-12-11 02:33:35 +01:00
|
|
|
foreach(lc, data)
|
|
|
|
{
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2 + 2);
|
|
|
|
appendStringInfoString(es->str, "- ");
|
|
|
|
escape_yaml(es->str, (const char *) lfirst(lc));
|
|
|
|
}
|
|
|
|
break;
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Explain a simple property.
|
|
|
|
*
|
|
|
|
* If "numeric" is true, the value is a number (or other value that
|
|
|
|
* doesn't need quoting in JSON).
|
|
|
|
*
|
|
|
|
* This usually should not be invoked directly, but via one of the datatype
|
|
|
|
* specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainProperty(const char *qlabel, const char *value, bool numeric,
|
|
|
|
ExplainState *es)
|
|
|
|
{
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfo(es->str, "%s: %s\n", qlabel, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
char *str;
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
ExplainXMLTag(qlabel, X_OPENING | X_NOWHITESPACE, es);
|
|
|
|
str = escape_xml(value);
|
|
|
|
appendStringInfoString(es->str, str);
|
|
|
|
pfree(str);
|
|
|
|
ExplainXMLTag(qlabel, X_CLOSING | X_NOWHITESPACE, es);
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
|
|
|
ExplainJSONLineEnding(es);
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
escape_json(es->str, qlabel);
|
|
|
|
appendStringInfoString(es->str, ": ");
|
|
|
|
if (numeric)
|
|
|
|
appendStringInfoString(es->str, value);
|
|
|
|
else
|
|
|
|
escape_json(es->str, value);
|
|
|
|
break;
|
2009-12-11 02:33:35 +01:00
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_YAML:
|
|
|
|
ExplainYAMLLineStarting(es);
|
|
|
|
appendStringInfo(es->str, "%s: ", qlabel);
|
2010-06-10 03:26:30 +02:00
|
|
|
if (numeric)
|
|
|
|
appendStringInfoString(es->str, value);
|
|
|
|
else
|
|
|
|
escape_yaml(es->str, value);
|
2009-12-11 02:33:35 +01:00
|
|
|
break;
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-20 06:17:18 +01:00
|
|
|
/*
|
|
|
|
* Explain a string-valued property.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ExplainPropertyText(const char *qlabel, const char *value, ExplainState *es)
|
|
|
|
{
|
|
|
|
ExplainProperty(qlabel, value, false, es);
|
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/*
|
|
|
|
* Explain an integer-valued property.
|
|
|
|
*/
|
2011-02-20 06:17:18 +01:00
|
|
|
void
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainPropertyInteger(const char *qlabel, int value, ExplainState *es)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
char buf[32];
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
snprintf(buf, sizeof(buf), "%d", value);
|
|
|
|
ExplainProperty(qlabel, buf, true, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Explain a long-integer-valued property.
|
|
|
|
*/
|
2011-02-20 06:17:18 +01:00
|
|
|
void
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainPropertyLong(const char *qlabel, long value, ExplainState *es)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
char buf[32];
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
snprintf(buf, sizeof(buf), "%ld", value);
|
|
|
|
ExplainProperty(qlabel, buf, true, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Explain a float-valued property, using the specified number of
|
|
|
|
* fractional digits.
|
|
|
|
*/
|
2011-02-20 06:17:18 +01:00
|
|
|
void
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainPropertyFloat(const char *qlabel, double value, int ndigits,
|
|
|
|
ExplainState *es)
|
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
char buf[256];
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
snprintf(buf, sizeof(buf), "%.*f", ndigits, value);
|
|
|
|
ExplainProperty(qlabel, buf, true, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Open a group of related objects.
|
|
|
|
*
|
|
|
|
* objtype is the type of the group object, labelname is its label within
|
|
|
|
* a containing object (if any).
|
|
|
|
*
|
|
|
|
* If labeled is true, the group members will be labeled properties,
|
|
|
|
* while if it's false, they'll be unlabeled objects.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainOpenGroup(const char *objtype, const char *labelname,
|
|
|
|
bool labeled, ExplainState *es)
|
|
|
|
{
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
/* nothing to do */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
ExplainXMLTag(objtype, X_OPENING, es);
|
|
|
|
es->indent++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
|
|
|
ExplainJSONLineEnding(es);
|
|
|
|
appendStringInfoSpaces(es->str, 2 * es->indent);
|
|
|
|
if (labelname)
|
|
|
|
{
|
|
|
|
escape_json(es->str, labelname);
|
|
|
|
appendStringInfoString(es->str, ": ");
|
|
|
|
}
|
|
|
|
appendStringInfoChar(es->str, labeled ? '{' : '[');
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In JSON format, the grouping_stack is an integer list. 0 means
|
|
|
|
* we've emitted nothing at this grouping level, 1 means we've
|
2010-02-26 03:01:40 +01:00
|
|
|
* emitted something (and so the next item needs a comma). See
|
|
|
|
* ExplainJSONLineEnding().
|
2009-08-10 07:46:50 +02:00
|
|
|
*/
|
|
|
|
es->grouping_stack = lcons_int(0, es->grouping_stack);
|
|
|
|
es->indent++;
|
|
|
|
break;
|
2009-12-11 02:33:35 +01:00
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_YAML:
|
2009-12-16 23:16:16 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* In YAML format, the grouping stack is an integer list. 0 means
|
|
|
|
* we've emitted nothing at this grouping level AND this grouping
|
|
|
|
* level is unlabelled and must be marked with "- ". See
|
|
|
|
* ExplainYAMLLineStarting().
|
|
|
|
*/
|
2009-12-11 02:33:35 +01:00
|
|
|
ExplainYAMLLineStarting(es);
|
|
|
|
if (labelname)
|
|
|
|
{
|
2010-06-10 03:26:30 +02:00
|
|
|
appendStringInfo(es->str, "%s: ", labelname);
|
2009-12-11 02:33:35 +01:00
|
|
|
es->grouping_stack = lcons_int(1, es->grouping_stack);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-12-16 23:16:16 +01:00
|
|
|
appendStringInfoString(es->str, "- ");
|
2009-12-11 02:33:35 +01:00
|
|
|
es->grouping_stack = lcons_int(0, es->grouping_stack);
|
|
|
|
}
|
|
|
|
es->indent++;
|
|
|
|
break;
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Close a group of related objects.
|
|
|
|
* Parameters must match the corresponding ExplainOpenGroup call.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainCloseGroup(const char *objtype, const char *labelname,
|
|
|
|
bool labeled, ExplainState *es)
|
|
|
|
{
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
/* nothing to do */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
es->indent--;
|
|
|
|
ExplainXMLTag(objtype, X_CLOSING, es);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
|
|
|
es->indent--;
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
appendStringInfoSpaces(es->str, 2 * es->indent);
|
|
|
|
appendStringInfoChar(es->str, labeled ? '}' : ']');
|
|
|
|
es->grouping_stack = list_delete_first(es->grouping_stack);
|
|
|
|
break;
|
2009-12-11 02:33:35 +01:00
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_YAML:
|
|
|
|
es->indent--;
|
|
|
|
es->grouping_stack = list_delete_first(es->grouping_stack);
|
|
|
|
break;
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Emit a "dummy" group that never has any members.
|
|
|
|
*
|
|
|
|
* objtype is the type of the group object, labelname is its label within
|
|
|
|
* a containing object (if any).
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainDummyGroup(const char *objtype, const char *labelname, ExplainState *es)
|
|
|
|
{
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
/* nothing to do */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
ExplainXMLTag(objtype, X_CLOSE_IMMEDIATE, es);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
|
|
|
ExplainJSONLineEnding(es);
|
|
|
|
appendStringInfoSpaces(es->str, 2 * es->indent);
|
|
|
|
if (labelname)
|
|
|
|
{
|
|
|
|
escape_json(es->str, labelname);
|
|
|
|
appendStringInfoString(es->str, ": ");
|
|
|
|
}
|
|
|
|
escape_json(es->str, objtype);
|
|
|
|
break;
|
2009-12-11 02:33:35 +01:00
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_YAML:
|
|
|
|
ExplainYAMLLineStarting(es);
|
|
|
|
if (labelname)
|
2009-12-16 23:16:16 +01:00
|
|
|
{
|
|
|
|
escape_yaml(es->str, labelname);
|
|
|
|
appendStringInfoString(es->str, ": ");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
appendStringInfoString(es->str, "- ");
|
|
|
|
}
|
|
|
|
escape_yaml(es->str, objtype);
|
2009-12-11 02:33:35 +01:00
|
|
|
break;
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Emit the start-of-output boilerplate.
|
|
|
|
*
|
|
|
|
* This is just enough different from processing a subgroup that we need
|
|
|
|
* a separate pair of subroutines.
|
|
|
|
*/
|
2009-12-12 01:35:34 +01:00
|
|
|
void
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainBeginOutput(ExplainState *es)
|
|
|
|
{
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
/* nothing to do */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
appendStringInfoString(es->str,
|
2010-02-26 03:01:40 +01:00
|
|
|
"<explain xmlns=\"http://www.postgresql.org/2009/explain\">\n");
|
2009-08-10 07:46:50 +02:00
|
|
|
es->indent++;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
|
|
|
/* top-level structure is an array of plans */
|
|
|
|
appendStringInfoChar(es->str, '[');
|
|
|
|
es->grouping_stack = lcons_int(0, es->grouping_stack);
|
|
|
|
es->indent++;
|
|
|
|
break;
|
2009-12-11 02:33:35 +01:00
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_YAML:
|
|
|
|
es->grouping_stack = lcons_int(0, es->grouping_stack);
|
|
|
|
break;
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Emit the end-of-output boilerplate.
|
|
|
|
*/
|
2009-12-12 01:35:34 +01:00
|
|
|
void
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainEndOutput(ExplainState *es)
|
|
|
|
{
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
/* nothing to do */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
es->indent--;
|
|
|
|
appendStringInfoString(es->str, "</explain>");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
|
|
|
es->indent--;
|
|
|
|
appendStringInfoString(es->str, "\n]");
|
|
|
|
es->grouping_stack = list_delete_first(es->grouping_stack);
|
|
|
|
break;
|
2009-12-11 02:33:35 +01:00
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_YAML:
|
|
|
|
es->grouping_stack = list_delete_first(es->grouping_stack);
|
|
|
|
break;
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Put an appropriate separator between multiple plans
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ExplainSeparatePlans(ExplainState *es)
|
|
|
|
{
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
/* add a blank line */
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
2009-12-16 23:16:16 +01:00
|
|
|
case EXPLAIN_FORMAT_YAML:
|
|
|
|
/* nothing to do */
|
2009-08-10 07:46:50 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Emit opening or closing XML tag.
|
|
|
|
*
|
|
|
|
* "flags" must contain X_OPENING, X_CLOSING, or X_CLOSE_IMMEDIATE.
|
|
|
|
* Optionally, OR in X_NOWHITESPACE to suppress the whitespace we'd normally
|
|
|
|
* add.
|
|
|
|
*
|
|
|
|
* XML tag names can't contain white space, so we replace any spaces in
|
|
|
|
* "tagname" with dashes.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
|
|
|
|
{
|
|
|
|
const char *s;
|
|
|
|
|
|
|
|
if ((flags & X_NOWHITESPACE) == 0)
|
|
|
|
appendStringInfoSpaces(es->str, 2 * es->indent);
|
|
|
|
appendStringInfoCharMacro(es->str, '<');
|
|
|
|
if ((flags & X_CLOSING) != 0)
|
|
|
|
appendStringInfoCharMacro(es->str, '/');
|
|
|
|
for (s = tagname; *s; s++)
|
|
|
|
appendStringInfoCharMacro(es->str, (*s == ' ') ? '-' : *s);
|
|
|
|
if ((flags & X_CLOSE_IMMEDIATE) != 0)
|
|
|
|
appendStringInfoString(es->str, " /");
|
|
|
|
appendStringInfoCharMacro(es->str, '>');
|
|
|
|
if ((flags & X_NOWHITESPACE) == 0)
|
|
|
|
appendStringInfoCharMacro(es->str, '\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Emit a JSON line ending.
|
|
|
|
*
|
2014-05-06 18:12:18 +02:00
|
|
|
* JSON requires a comma after each property but the last. To facilitate this,
|
2009-08-10 07:46:50 +02:00
|
|
|
* in JSON format, the text emitted for each property begins just prior to the
|
|
|
|
* preceding line-break (and comma, if applicable).
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainJSONLineEnding(ExplainState *es)
|
|
|
|
{
|
|
|
|
Assert(es->format == EXPLAIN_FORMAT_JSON);
|
|
|
|
if (linitial_int(es->grouping_stack) != 0)
|
|
|
|
appendStringInfoChar(es->str, ',');
|
|
|
|
else
|
|
|
|
linitial_int(es->grouping_stack) = 1;
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
}
|
|
|
|
|
2009-12-11 02:33:35 +01:00
|
|
|
/*
|
|
|
|
* Indent a YAML line.
|
2009-12-16 23:16:16 +01:00
|
|
|
*
|
|
|
|
* YAML lines are ordinarily indented by two spaces per indentation level.
|
|
|
|
* The text emitted for each property begins just prior to the preceding
|
|
|
|
* line-break, except for the first property in an unlabelled group, for which
|
2014-05-06 18:12:18 +02:00
|
|
|
* it begins immediately after the "- " that introduces the group. The first
|
2009-12-16 23:16:16 +01:00
|
|
|
* property of the group appears on the same line as the opening "- ".
|
2009-12-11 02:33:35 +01:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainYAMLLineStarting(ExplainState *es)
|
|
|
|
{
|
|
|
|
Assert(es->format == EXPLAIN_FORMAT_YAML);
|
|
|
|
if (linitial_int(es->grouping_stack) == 0)
|
|
|
|
{
|
|
|
|
linitial_int(es->grouping_stack) = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2010-06-10 03:26:30 +02:00
|
|
|
* YAML is a superset of JSON; unfortuantely, the YAML quoting rules are
|
|
|
|
* ridiculously complicated -- as documented in sections 5.3 and 7.3.3 of
|
|
|
|
* http://yaml.org/spec/1.2/spec.html -- so we chose to just quote everything.
|
|
|
|
* Empty strings, strings with leading or trailing whitespace, and strings
|
|
|
|
* containing a variety of special characters must certainly be quoted or the
|
|
|
|
* output is invalid; and other seemingly harmless strings like "0xa" or
|
|
|
|
* "true" must be quoted, lest they be interpreted as a hexadecimal or Boolean
|
|
|
|
* constant rather than a string.
|
2009-12-11 02:33:35 +01:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
escape_yaml(StringInfo buf, const char *str)
|
|
|
|
{
|
2010-06-10 03:26:30 +02:00
|
|
|
escape_json(buf, str);
|
2009-12-11 02:33:35 +01:00
|
|
|
}
|