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
|
|
|
*
|
2018-01-03 05:30:12 +01:00
|
|
|
* Portions Copyright (c) 1996-2018, 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"
|
2017-12-05 19:55:56 +01:00
|
|
|
#include "executor/nodeHash.h"
|
2011-02-20 06:17:18 +01:00
|
|
|
#include "foreign/fdwapi.h"
|
2018-03-28 22:26:51 +02:00
|
|
|
#include "jit/jit.h"
|
2016-03-29 17:00:18 +02:00
|
|
|
#include "nodes/extensible.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"
|
2016-02-07 17:39:22 +01:00
|
|
|
#include "optimizer/planmain.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"
|
2016-08-12 18:13:04 +02:00
|
|
|
#include "storage/bufmgr.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
|
|
|
|
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
static void ExplainOneQuery(Query *query, int cursorOptions,
|
|
|
|
IntoClause *into, ExplainState *es,
|
2017-04-01 06:17:18 +02:00
|
|
|
const char *queryString, ParamListInfo params,
|
|
|
|
QueryEnvironment *queryEnv);
|
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);
|
2015-09-17 17:24:49 +02:00
|
|
|
static bool ExplainPreScanNode(PlanState *planstate, 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);
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
static void show_grouping_sets(PlanState *planstate, Agg *agg,
|
2015-05-24 03:35:49 +02:00
|
|
|
List *ancestors, ExplainState *es);
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
static void show_grouping_set_keys(PlanState *planstate,
|
2015-05-24 03:35:49 +02:00
|
|
|
Agg *aggnode, Sort *sortnode,
|
|
|
|
List *context, bool useprefix,
|
|
|
|
List *ancestors, ExplainState *es);
|
2013-12-12 17:24:38 +01:00
|
|
|
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);
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
static void show_tablesample(TableSampleClause *tsc, PlanState *planstate,
|
|
|
|
List *ancestors, ExplainState *es);
|
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);
|
2017-11-16 18:06:14 +01:00
|
|
|
static void show_eval_params(Bitmapset *bms_params, ExplainState *es);
|
2007-05-25 19:54:25 +02:00
|
|
|
static const char *explain_get_index_name(Oid indexId);
|
2015-12-09 19:18:09 +01:00
|
|
|
static void show_buffer_usage(ExplainState *es, const BufferUsage *usage);
|
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);
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
static void show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
|
|
|
|
ExplainState *es);
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
static void ExplainMemberNodes(PlanState **planstates, int nsubnodes,
|
|
|
|
int nplans, List *ancestors, ExplainState *es);
|
2010-07-13 22:57:19 +02:00
|
|
|
static void ExplainSubPlans(List *plans, List *ancestors,
|
2011-04-10 17:42:00 +02:00
|
|
|
const char *relationship, ExplainState *es);
|
2015-06-26 15:40:47 +02:00
|
|
|
static void ExplainCustomChildren(CustomScanState *css,
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
List *ancestors, ExplainState *es);
|
2018-03-17 07:13:12 +01:00
|
|
|
static void ExplainProperty(const char *qlabel, const char *unit,
|
|
|
|
const char *value, bool numeric, 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
|
2016-09-06 18:00:00 +02:00
|
|
|
ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
|
2017-04-01 06:17:18 +02:00
|
|
|
ParamListInfo params, QueryEnvironment *queryEnv,
|
|
|
|
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;
|
2017-03-08 21:14:03 +01:00
|
|
|
bool summary_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
|
|
|
}
|
2017-03-08 21:14:03 +01:00
|
|
|
else if (strcmp(opt->defname, "summary") == 0)
|
|
|
|
{
|
|
|
|
summary_set = true;
|
|
|
|
es->summary = defGetBoolean(opt);
|
|
|
|
}
|
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),
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
errmsg("unrecognized value for EXPLAIN option \"%s\": \"%s\"",
|
|
|
|
opt->defname, p),
|
2016-09-06 18:00:00 +02:00
|
|
|
parser_errposition(pstate, opt->location)));
|
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\"",
|
2016-09-06 18:00:00 +02:00
|
|
|
opt->defname),
|
|
|
|
parser_errposition(pstate, opt->location)));
|
2009-07-27 01:34:18 +02:00
|
|
|
}
|
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
|
|
|
|
2017-03-08 21:14:03 +01:00
|
|
|
/* if the summary was not set explicitly, set default value */
|
|
|
|
es->summary = (summary_set) ? 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
|
|
|
*/
|
2017-01-27 04:09:34 +01:00
|
|
|
rewritten = QueryRewrite(castNode(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
|
|
|
{
|
Improve castNode notation by introducing list-extraction-specific variants.
This extends the castNode() notation introduced by commit 5bcab1114 to
provide, in one step, extraction of a list cell's pointer and coercion to
a concrete node type. For example, "lfirst_node(Foo, lc)" is the same
as "castNode(Foo, lfirst(lc))". Almost half of the uses of castNode
that have appeared so far include a list extraction call, so this is
pretty widely useful, and it saves a few more keystrokes compared to the
old way.
As with the previous patch, back-patch the addition of these macros to
pg_list.h, so that the notation will be available when back-patching.
Patch by me, after an idea of Andrew Gierth's.
Discussion: https://postgr.es/m/14197.1491841216@sss.pgh.pa.us
2017-04-10 19:51:29 +02:00
|
|
|
ExplainOneQuery(lfirst_node(Query, l),
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
CURSOR_OPT_PARALLEL_OK, NULL, es,
|
2017-04-01 06:17:18 +02:00
|
|
|
queryString, params, queryEnv);
|
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
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
ExplainOneQuery(Query *query, int cursorOptions,
|
|
|
|
IntoClause *into, ExplainState *es,
|
2017-04-01 06:17:18 +02:00
|
|
|
const char *queryString, ParamListInfo params,
|
|
|
|
QueryEnvironment *queryEnv)
|
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)
|
|
|
|
{
|
2017-04-01 06:17:18 +02:00
|
|
|
ExplainOneUtility(query->utilityStmt, into, es, queryString, params,
|
|
|
|
queryEnv);
|
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)
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
(*ExplainOneQuery_hook) (query, cursorOptions, into, es,
|
2018-01-11 18:16:18 +01:00
|
|
|
queryString, params, queryEnv);
|
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 */
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
plan = pg_plan_query(query, cursorOptions, 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 */
|
2017-04-01 06:17:18 +02:00
|
|
|
ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
|
|
|
|
&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,
|
2017-04-01 06:17:18 +02:00
|
|
|
const char *queryString, ParamListInfo params,
|
|
|
|
QueryEnvironment *queryEnv)
|
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
|
|
|
|
2017-01-27 04:09:34 +01:00
|
|
|
rewritten = QueryRewrite(castNode(Query, copyObject(ctas->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
|
|
|
Assert(list_length(rewritten) == 1);
|
Improve castNode notation by introducing list-extraction-specific variants.
This extends the castNode() notation introduced by commit 5bcab1114 to
provide, in one step, extraction of a list cell's pointer and coercion to
a concrete node type. For example, "lfirst_node(Foo, lc)" is the same
as "castNode(Foo, lfirst(lc))". Almost half of the uses of castNode
that have appeared so far include a list extraction call, so this is
pretty widely useful, and it saves a few more keystrokes compared to the
old way.
As with the previous patch, back-patch the addition of these macros to
pg_list.h, so that the notation will be available when back-patching.
Patch by me, after an idea of Andrew Gierth's.
Discussion: https://postgr.es/m/14197.1491841216@sss.pgh.pa.us
2017-04-10 19:51:29 +02:00
|
|
|
ExplainOneQuery(linitial_node(Query, rewritten),
|
2017-10-05 17:34:38 +02:00
|
|
|
CURSOR_OPT_PARALLEL_OK, ctas->into, es,
|
2017-04-01 06:17:18 +02:00
|
|
|
queryString, params, queryEnv);
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
}
|
|
|
|
else if (IsA(utilityStmt, DeclareCursorStmt))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Likewise for DECLARE CURSOR.
|
|
|
|
*
|
|
|
|
* Notice that 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.
|
|
|
|
*/
|
|
|
|
DeclareCursorStmt *dcs = (DeclareCursorStmt *) utilityStmt;
|
|
|
|
List *rewritten;
|
|
|
|
|
2017-01-27 04:09:34 +01:00
|
|
|
rewritten = QueryRewrite(castNode(Query, copyObject(dcs->query)));
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
Assert(list_length(rewritten) == 1);
|
Improve castNode notation by introducing list-extraction-specific variants.
This extends the castNode() notation introduced by commit 5bcab1114 to
provide, in one step, extraction of a list cell's pointer and coercion to
a concrete node type. For example, "lfirst_node(Foo, lc)" is the same
as "castNode(Foo, lfirst(lc))". Almost half of the uses of castNode
that have appeared so far include a list extraction call, so this is
pretty widely useful, and it saves a few more keystrokes compared to the
old way.
As with the previous patch, back-patch the addition of these macros to
pg_list.h, so that the notation will be available when back-patching.
Patch by me, after an idea of Andrew Gierth's.
Discussion: https://postgr.es/m/14197.1491841216@sss.pgh.pa.us
2017-04-10 19:51:29 +02:00
|
|
|
ExplainOneQuery(linitial_node(Query, rewritten),
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
dcs->options, NULL, es,
|
2017-04-01 06:17:18 +02:00
|
|
|
queryString, params, queryEnv);
|
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,
|
2017-04-01 06:17:18 +02:00
|
|
|
queryString, params, queryEnv);
|
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,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02: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.
|
|
|
|
*
|
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,
|
2017-04-01 06:17:18 +02:00
|
|
|
QueryEnvironment *queryEnv, 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;
|
|
|
|
|
Change representation of statement lists, and add statement location info.
This patch makes several changes that improve the consistency of
representation of lists of statements. It's always been the case
that the output of parse analysis is a list of Query nodes, whatever
the types of the individual statements in the list. This patch brings
similar consistency to the outputs of raw parsing and planning steps:
* The output of raw parsing is now always a list of RawStmt nodes;
the statement-type-dependent nodes are one level down from that.
* The output of pg_plan_queries() is now always a list of PlannedStmt
nodes, even for utility statements. In the case of a utility statement,
"planning" just consists of wrapping a CMD_UTILITY PlannedStmt around
the utility node. This list representation is now used in Portal and
CachedPlan plan lists, replacing the former convention of intermixing
PlannedStmts with bare utility-statement nodes.
Now, every list of statements has a consistent head-node type depending
on how far along it is in processing. This allows changing many places
that formerly used generic "Node *" pointers to use a more specific
pointer type, thus reducing the number of IsA() tests and casts needed,
as well as improving code clarity.
Also, the post-parse-analysis representation of DECLARE CURSOR is changed
so that it looks more like EXPLAIN, PREPARE, etc. That is, the contained
SELECT remains a child of the DeclareCursorStmt rather than getting flipped
around to be the other way. It's now true for both Query and PlannedStmt
that utilityStmt is non-null if and only if commandType is CMD_UTILITY.
That allows simplifying a lot of places that were testing both fields.
(I think some of those were just defensive programming, but in many places,
it was actually necessary to avoid confusing DECLARE CURSOR with SELECT.)
Because PlannedStmt carries a canSetTag field, we're also able to get rid
of some ad-hoc rules about how to reconstruct canSetTag for a bare utility
statement; specifically, the assumption that a utility is canSetTag if and
only if it's the only one in its list. While I see no near-term need for
relaxing that restriction, it's nice to get rid of the ad-hocery.
The API of ProcessUtility() is changed so that what it's passed is the
wrapper PlannedStmt not just the bare utility statement. This will affect
all users of ProcessUtility_hook, but the changes are pretty trivial; see
the affected contrib modules for examples of the minimum change needed.
(Most compilers should give pointer-type-mismatch warnings for uncorrected
code.)
There's also a change in the API of ExplainOneQuery_hook, to pass through
cursorOptions instead of expecting hook functions to know what to pick.
This is needed because of the DECLARE CURSOR changes, but really should
have been done in 9.6; it's unlikely that any extant hook functions
know about using CURSOR_OPT_PARALLEL_OK.
Finally, teach gram.y to save statement boundary locations in RawStmt
nodes, and pass those through to Query and PlannedStmt nodes. This allows
more intelligent handling of cases where a source query string contains
multiple statements. This patch doesn't actually do anything with the
information, but a follow-on patch will. (Passing this information through
cleanly is the true motivation for these changes; while I think this is all
good cleanup, it's unlikely we'd have bothered without this end goal.)
catversion bump because addition of location fields to struct Query
affects stored rules.
This patch is by me, but it owes a good deal to Fabien Coelho who did
a lot of preliminary work on the problem, and also reviewed the patch.
Discussion: https://postgr.es/m/alpine.DEB.2.20.1612200926310.29821@lancre
2017-01-14 22:02:35 +01:00
|
|
|
Assert(plannedstmt->commandType != CMD_UTILITY);
|
|
|
|
|
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,
|
2017-04-01 06:17:18 +02:00
|
|
|
dest, params, queryEnv, 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 */
|
2017-03-23 18:05:48 +01:00
|
|
|
ExecutorRun(queryDesc, dir, 0L, true);
|
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
|
|
|
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Planning Time", "ms", 1000.0 * plantime, 3, es);
|
2014-01-29 22:04:19 +01:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2018-03-28 22:26:51 +02:00
|
|
|
/*
|
|
|
|
* Print info about JITing. Tied to es->costs because we don't want to
|
|
|
|
* display this in regression tests, as it'd cause output differences
|
|
|
|
* depending on build options. Might want to separate that out from COSTS
|
|
|
|
* at a later stage.
|
|
|
|
*/
|
|
|
|
if (queryDesc->estate->es_jit && es->costs &&
|
|
|
|
queryDesc->estate->es_jit->created_functions > 0)
|
|
|
|
ExplainPrintJIT(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);
|
|
|
|
|
2017-03-08 21:14:03 +01:00
|
|
|
/*
|
|
|
|
* We only report execution time if we actually ran the query (that is,
|
|
|
|
* the user specified ANALYZE), and if summary reporting is enabled (the
|
|
|
|
* user can set SUMMARY OFF to not have the timing information included in
|
|
|
|
* the output). By default, ANALYZE sets SUMMARY to true.
|
|
|
|
*/
|
|
|
|
if (es->summary && es->analyze)
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Execution Time", "ms", 1000.0 * totaltime, 3,
|
|
|
|
es);
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
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
|
Print a given subplan only once in EXPLAIN.
We have, for a very long time, allowed the same subplan (same member of the
PlannedStmt.subplans list) to be referenced by more than one SubPlan node;
this avoids problems for cases such as subplans within an IndexScan's
indxqual and indxqualorig fields. However, EXPLAIN had not gotten the memo
and would print each reference as though it were an independent identical
subplan. To fix, track plan_ids of subplans we've printed and don't print
the same plan_id twice. Per report from Pavel Stehule.
BTW: the particular case of IndexScan didn't cause visible duplication
in a plain EXPLAIN, only EXPLAIN ANALYZE, because in the former case we
short-circuit executor startup before the indxqual field is processed by
ExecInitExpr. That seems like it could easily lead to other EXPLAIN
problems in future, but it's not clear how to avoid it without breaking
the "EXPLAIN a plan using hypothetical indexes" use-case. For now I've
left that issue alone.
Although this is a longstanding bug, it's purely cosmetic (no great harm
is done by the repeat printout) and we haven't had field complaints before.
So I'm hesitant to back-patch it, especially since there is some small risk
of ABI problems due to the need to add a new field to ExplainState.
In passing, rearrange order of fields in ExplainState to be less random,
and update some obsolete comments about when/where to initialize them.
Report: <CAFj8pRAimq+NK-menjt+3J4-LFoodDD8Or6=Lc_stcFD+eD4DA@mail.gmail.com>
2016-07-12 00:14:29 +02:00
|
|
|
* initializing the output buffer es->str. Also, output formatting state
|
|
|
|
* such as the indent level is assumed valid. Plan-tree-specific fields
|
|
|
|
* in *es are 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;
|
2016-04-06 17:34:02 +02:00
|
|
|
PlanState *ps;
|
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
|
|
|
|
Print a given subplan only once in EXPLAIN.
We have, for a very long time, allowed the same subplan (same member of the
PlannedStmt.subplans list) to be referenced by more than one SubPlan node;
this avoids problems for cases such as subplans within an IndexScan's
indxqual and indxqualorig fields. However, EXPLAIN had not gotten the memo
and would print each reference as though it were an independent identical
subplan. To fix, track plan_ids of subplans we've printed and don't print
the same plan_id twice. Per report from Pavel Stehule.
BTW: the particular case of IndexScan didn't cause visible duplication
in a plain EXPLAIN, only EXPLAIN ANALYZE, because in the former case we
short-circuit executor startup before the indxqual field is processed by
ExecInitExpr. That seems like it could easily lead to other EXPLAIN
problems in future, but it's not clear how to avoid it without breaking
the "EXPLAIN a plan using hypothetical indexes" use-case. For now I've
left that issue alone.
Although this is a longstanding bug, it's purely cosmetic (no great harm
is done by the repeat printout) and we haven't had field complaints before.
So I'm hesitant to back-patch it, especially since there is some small risk
of ABI problems due to the need to add a new field to ExplainState.
In passing, rearrange order of fields in ExplainState to be less random,
and update some obsolete comments about when/where to initialize them.
Report: <CAFj8pRAimq+NK-menjt+3J4-LFoodDD8Or6=Lc_stcFD+eD4DA@mail.gmail.com>
2016-07-12 00:14:29 +02:00
|
|
|
/* Set up ExplainState fields associated with this plan tree */
|
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);
|
Print a given subplan only once in EXPLAIN.
We have, for a very long time, allowed the same subplan (same member of the
PlannedStmt.subplans list) to be referenced by more than one SubPlan node;
this avoids problems for cases such as subplans within an IndexScan's
indxqual and indxqualorig fields. However, EXPLAIN had not gotten the memo
and would print each reference as though it were an independent identical
subplan. To fix, track plan_ids of subplans we've printed and don't print
the same plan_id twice. Per report from Pavel Stehule.
BTW: the particular case of IndexScan didn't cause visible duplication
in a plain EXPLAIN, only EXPLAIN ANALYZE, because in the former case we
short-circuit executor startup before the indxqual field is processed by
ExecInitExpr. That seems like it could easily lead to other EXPLAIN
problems in future, but it's not clear how to avoid it without breaking
the "EXPLAIN a plan using hypothetical indexes" use-case. For now I've
left that issue alone.
Although this is a longstanding bug, it's purely cosmetic (no great harm
is done by the repeat printout) and we haven't had field complaints before.
So I'm hesitant to back-patch it, especially since there is some small risk
of ABI problems due to the need to add a new field to ExplainState.
In passing, rearrange order of fields in ExplainState to be less random,
and update some obsolete comments about when/where to initialize them.
Report: <CAFj8pRAimq+NK-menjt+3J4-LFoodDD8Or6=Lc_stcFD+eD4DA@mail.gmail.com>
2016-07-12 00:14:29 +02:00
|
|
|
es->printed_subplans = NULL;
|
2016-02-07 17:39:22 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Sometimes we mark a Gather node as "invisible", which means that it's
|
|
|
|
* not displayed in EXPLAIN output. The purpose of this is to allow
|
|
|
|
* running regression tests with force_parallel_mode=regress to get the
|
|
|
|
* same results as running the same tests with force_parallel_mode=off.
|
|
|
|
*/
|
|
|
|
ps = queryDesc->planstate;
|
|
|
|
if (IsA(ps, GatherState) &&((Gather *) ps->plan)->invisible)
|
|
|
|
ps = outerPlanState(ps);
|
|
|
|
ExplainNode(ps, 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;
|
2017-08-18 19:01:05 +02:00
|
|
|
int numrootrels = queryDesc->estate->es_num_root_result_relations;
|
2018-02-08 20:29:05 +01:00
|
|
|
List *routerels;
|
|
|
|
List *targrels;
|
2014-01-20 21:12:50 +01:00
|
|
|
int nr;
|
|
|
|
ListCell *l;
|
|
|
|
|
2018-02-08 20:29:05 +01:00
|
|
|
routerels = queryDesc->estate->es_tuple_routing_result_relations;
|
|
|
|
targrels = queryDesc->estate->es_trig_target_relations;
|
|
|
|
|
2014-01-20 21:12:50 +01:00
|
|
|
ExplainOpenGroup("Triggers", "Triggers", false, es);
|
|
|
|
|
2017-08-18 19:01:05 +02:00
|
|
|
show_relname = (numrels > 1 || numrootrels > 0 ||
|
2018-02-08 20:29:05 +01:00
|
|
|
routerels != NIL || targrels != NIL);
|
2014-01-20 21:12:50 +01:00
|
|
|
rInfo = queryDesc->estate->es_result_relations;
|
|
|
|
for (nr = 0; nr < numrels; rInfo++, nr++)
|
|
|
|
report_triggers(rInfo, show_relname, es);
|
|
|
|
|
2017-08-18 19:01:05 +02:00
|
|
|
rInfo = queryDesc->estate->es_root_result_relations;
|
|
|
|
for (nr = 0; nr < numrootrels; rInfo++, nr++)
|
|
|
|
report_triggers(rInfo, show_relname, es);
|
|
|
|
|
2018-02-08 20:29:05 +01:00
|
|
|
foreach(l, routerels)
|
2017-08-18 19:01:05 +02:00
|
|
|
{
|
|
|
|
rInfo = (ResultRelInfo *) lfirst(l);
|
|
|
|
report_triggers(rInfo, show_relname, es);
|
|
|
|
}
|
|
|
|
|
2014-01-20 21:12:50 +01:00
|
|
|
foreach(l, targrels)
|
|
|
|
{
|
|
|
|
rInfo = (ResultRelInfo *) lfirst(l);
|
|
|
|
report_triggers(rInfo, show_relname, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExplainCloseGroup("Triggers", "Triggers", false, es);
|
|
|
|
}
|
|
|
|
|
2018-03-28 22:26:51 +02:00
|
|
|
/*
|
|
|
|
* ExplainPrintJIT -
|
|
|
|
* Append information about JITing to es->str.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ExplainPrintJIT(ExplainState *es, QueryDesc *queryDesc)
|
|
|
|
{
|
|
|
|
JitContext *jc = queryDesc->estate->es_jit;
|
|
|
|
|
|
|
|
ExplainOpenGroup("JIT", "JIT", true, es);
|
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
es->indent += 1;
|
|
|
|
appendStringInfo(es->str, "JIT:\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
ExplainPropertyInteger("Functions", NULL, jc->created_functions, es);
|
|
|
|
if (es->analyze && es->timing)
|
|
|
|
ExplainPropertyFloat("Generation Time", "ms",
|
|
|
|
1000.0 * INSTR_TIME_GET_DOUBLE(jc->generation_counter),
|
|
|
|
3, es);
|
|
|
|
|
|
|
|
ExplainPropertyBool("Inlining", jc->flags & PGJIT_INLINE, es);
|
|
|
|
|
|
|
|
if (es->analyze && es->timing)
|
|
|
|
ExplainPropertyFloat("Inlining Time", "ms",
|
|
|
|
1000.0 * INSTR_TIME_GET_DOUBLE(jc->inlining_counter),
|
|
|
|
3, es);
|
|
|
|
|
|
|
|
ExplainPropertyBool("Optimization", jc->flags & PGJIT_OPT3, es);
|
|
|
|
if (es->analyze && es->timing)
|
|
|
|
ExplainPropertyFloat("Optimization Time", "ms",
|
|
|
|
1000.0 * INSTR_TIME_GET_DOUBLE(jc->optimization_counter),
|
|
|
|
3, es);
|
|
|
|
|
|
|
|
if (es->analyze && es->timing)
|
|
|
|
ExplainPropertyFloat("Emission Time", "ms",
|
|
|
|
1000.0 * INSTR_TIME_GET_DOUBLE(jc->emission_counter),
|
|
|
|
3, es);
|
|
|
|
|
|
|
|
ExplainCloseGroup("JIT", "JIT", true, es);
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
es->indent -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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);
|
2016-08-12 18:13:04 +02:00
|
|
|
if (es->timing)
|
|
|
|
appendStringInfo(es->str, ": time=%.3f calls=%.0f\n",
|
|
|
|
1000.0 * instr->total, instr->ntuples);
|
|
|
|
else
|
|
|
|
appendStringInfo(es->str, ": calls=%.0f\n", 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);
|
2016-08-12 18:13:04 +02:00
|
|
|
if (es->timing)
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Time", "ms", 1000.0 * instr->total, 3,
|
|
|
|
es);
|
|
|
|
ExplainPropertyFloat("Calls", NULL, instr->ntuples, 0, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
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
|
|
|
*/
|
2015-09-17 17:24:49 +02:00
|
|
|
static bool
|
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(PlanState *planstate, Bitmapset **rels_used)
|
|
|
|
{
|
|
|
|
Plan *plan = planstate->plan;
|
|
|
|
|
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
|
|
|
case T_SeqScan:
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
case T_SampleScan:
|
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
|
|
|
case T_IndexScan:
|
|
|
|
case T_IndexOnlyScan:
|
|
|
|
case T_BitmapHeapScan:
|
|
|
|
case T_TidScan:
|
|
|
|
case T_SubqueryScan:
|
|
|
|
case T_FunctionScan:
|
2017-03-08 16:39:37 +01:00
|
|
|
case T_TableFuncScan:
|
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
|
|
|
case T_ValuesScan:
|
|
|
|
case T_CteScan:
|
2017-04-01 06:17:18 +02:00
|
|
|
case T_NamedTuplestoreScan:
|
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
|
|
|
case T_WorkTableScan:
|
|
|
|
*rels_used = bms_add_member(*rels_used,
|
|
|
|
((Scan *) plan)->scanrelid);
|
|
|
|
break;
|
2015-05-01 14:50:35 +02:00
|
|
|
case T_ForeignScan:
|
|
|
|
*rels_used = bms_add_members(*rels_used,
|
Code review for foreign/custom join pushdown patch.
Commit e7cb7ee14555cc9c5773e2c102efd6371f6f2005 included some design
decisions that seem pretty questionable to me, and there was quite a lot
of stuff not to like about the documentation and comments. Clean up
as follows:
* Consider foreign joins only between foreign tables on the same server,
rather than between any two foreign tables with the same underlying FDW
handler function. In most if not all cases, the FDW would simply have had
to apply the same-server restriction itself (far more expensively, both for
lack of caching and because it would be repeated for each combination of
input sub-joins), or else risk nasty bugs. Anyone who's really intent on
doing something outside this restriction can always use the
set_join_pathlist_hook.
* Rename fdw_ps_tlist/custom_ps_tlist to fdw_scan_tlist/custom_scan_tlist
to better reflect what they're for, and allow these custom scan tlists
to be used even for base relations.
* Change make_foreignscan() API to include passing the fdw_scan_tlist
value, since the FDW is required to set that. Backwards compatibility
doesn't seem like an adequate reason to expect FDWs to set it in some
ad-hoc extra step, and anyway existing FDWs can just pass NIL.
* Change the API of path-generating subroutines of add_paths_to_joinrel,
and in particular that of GetForeignJoinPaths and set_join_pathlist_hook,
so that various less-used parameters are passed in a struct rather than
as separate parameter-list entries. The objective here is to reduce the
probability that future additions to those parameter lists will result in
source-level API breaks for users of these hooks. It's possible that this
is even a small win for the core code, since most CPU architectures can't
pass more than half a dozen parameters efficiently anyway. I kept root,
joinrel, outerrel, innerrel, and jointype as separate parameters to reduce
code churn in joinpath.c --- in particular, putting jointype into the
struct would have been problematic because of the subroutines' habit of
changing their local copies of that variable.
* Avoid ad-hocery in ExecAssignScanProjectionInfo. It was probably all
right for it to know about IndexOnlyScan, but if the list is to grow
we should refactor the knowledge out to the callers.
* Restore nodeForeignscan.c's previous use of the relcache to avoid
extra GetFdwRoutine lookups for base-relation scans.
* Lots of cleanup of documentation and missed comments. Re-order some
code additions into more logical places.
2015-05-10 20:36:30 +02:00
|
|
|
((ForeignScan *) plan)->fs_relids);
|
2015-05-01 14:50:35 +02:00
|
|
|
break;
|
|
|
|
case T_CustomScan:
|
|
|
|
*rels_used = bms_add_members(*rels_used,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
((CustomScan *) plan)->custom_relids);
|
2015-05-01 14:50:35 +02:00
|
|
|
break;
|
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
|
|
|
case T_ModifyTable:
|
|
|
|
*rels_used = bms_add_member(*rels_used,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
((ModifyTable *) plan)->nominalRelation);
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
if (((ModifyTable *) plan)->exclRelRTI)
|
|
|
|
*rels_used = bms_add_member(*rels_used,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
((ModifyTable *) plan)->exclRelRTI);
|
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;
|
|
|
|
}
|
|
|
|
|
2015-09-17 17:24:49 +02:00
|
|
|
return planstate_tree_walker(planstate, ExplainPreScanNode, rels_used);
|
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
|
|
|
}
|
|
|
|
|
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;
|
Fix some infelicities in EXPLAIN output for parallel query plans.
In non-text output formats, parallelized aggregates were reporting
"Partial" or "Finalize" as a field named "Operation", which might be all
right in the absence of any context --- but other plan node types use that
field to report SQL-visible semantics, such as Select/Insert/Update/Delete.
So that naming choice didn't seem good to me. I changed it to "Partial
Mode".
Also, the field did not appear at all for a non-parallelized Agg plan node,
which is contrary to expectation in non-text formats. We're notionally
producing objects that conform to a schema, so the set of fields for a
given node type and EXPLAIN mode should be well-defined. I set it up to
fill in "Simple" in such cases.
Other fields that were added for parallel query, namely "Parallel Aware"
and Gather's "Single Copy", had not gotten the word on that point either.
Make them appear always in non-text output.
Also, the latter two fields were nominally producing boolean output, but
were getting it wrong, because bool values shouldn't be quoted in JSON or
YAML. Somehow we'd not needed an ExplainPropertyBool formatting subroutine
before 9.6; but now we do, so invent it.
Discussion: <16002.1466972724@sss.pgh.pa.us>
2016-06-30 00:51:20 +02:00
|
|
|
const char *partialmode = 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;
|
Move targetlist SRF handling from expression evaluation to new executor node.
Evaluation of set returning functions (SRFs_ in the targetlist (like SELECT
generate_series(1,5)) so far was done in the expression evaluation (i.e.
ExecEvalExpr()) and projection (i.e. ExecProject/ExecTargetList) code.
This meant that most executor nodes performing projection, and most
expression evaluation functions, had to deal with the possibility that an
evaluated expression could return a set of return values.
That's bad because it leads to repeated code in a lot of places. It also,
and that's my (Andres's) motivation, made it a lot harder to implement a
more efficient way of doing expression evaluation.
To fix this, introduce a new executor node (ProjectSet) that can evaluate
targetlists containing one or more SRFs. To avoid the complexity of the old
way of handling nested expressions returning sets (e.g. having to pass up
ExprDoneCond, and dealing with arguments to functions returning sets etc.),
those SRFs can only be at the top level of the node's targetlist. The
planner makes sure (via split_pathtarget_at_srfs()) that SRF evaluation is
only necessary in ProjectSet nodes and that SRFs are only present at the
top level of the node's targetlist. If there are nested SRFs the planner
creates multiple stacked ProjectSet nodes. The ProjectSet nodes always get
input from an underlying node.
We also discussed and prototyped evaluating targetlist SRFs using ROWS
FROM(), but that turned out to be more complicated than we'd hoped.
While moving SRF evaluation to ProjectSet would allow to retain the old
"least common multiple" behavior when multiple SRFs are present in one
targetlist (i.e. continue returning rows until all SRFs are at the end of
their input at the same time), we decided to instead only return rows till
all SRFs are exhausted, returning NULL for already exhausted ones. We
deemed the previous behavior to be too confusing, unexpected and actually
not particularly useful.
As a side effect, the previously prohibited case of multiple set returning
arguments to a function, is now allowed. Not because it's particularly
desirable, but because it ends up working and there seems to be no argument
for adding code to prohibit it.
Currently the behavior for COALESCE and CASE containing SRFs has changed,
returning multiple rows from the expression, even when the SRF containing
"arm" of the expression is not evaluated. That's because the SRFs are
evaluated in a separate ProjectSet node. As that's quite confusing, we're
likely to instead prohibit SRFs in those places. But that's still being
discussed, and the code would reside in places not touched here, so that's
a task for later.
There's a lot of, now superfluous, code dealing with set return expressions
around. But as the changes to get rid of those are verbose largely boring,
it seems better for readability to keep the cleanup as a separate commit.
Author: Tom Lane and Andres Freund
Discussion: https://postgr.es/m/20160822214023.aaxz5l4igypowyri@alap3.anarazel.de
2017-01-18 21:46:50 +01:00
|
|
|
case T_ProjectSet:
|
|
|
|
pname = sname = "ProjectSet";
|
|
|
|
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;
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
case T_SampleScan:
|
|
|
|
pname = sname = "Sample Scan";
|
|
|
|
break;
|
Add a Gather executor node.
A Gather executor node runs any number of copies of a plan in an equal
number of workers and merges all of the results into a single tuple
stream. It can also run the plan itself, if the workers are
unavailable or haven't started up yet. It is intended to work with
the Partial Seq Scan node which will be added in future commits.
It could also be used to implement parallel query of a different sort
by itself, without help from Partial Seq Scan, if the single_copy mode
is used. In that mode, a worker executes the plan, and the parallel
leader does not, merely collecting the worker's results. So, a Gather
node could be inserted into a plan to split the execution of that plan
across two processes. Nested Gather nodes aren't currently supported,
but we might want to add support for that in the future.
There's nothing in the planner to actually generate Gather nodes yet,
so it's not quite time to break out the champagne. But we're getting
close.
Amit Kapila. Some designs suggestions were provided by me, and I also
reviewed the patch. Single-copy mode, documentation, and other minor
changes also by me.
2015-10-01 01:23:36 +02:00
|
|
|
case T_Gather:
|
|
|
|
pname = sname = "Gather";
|
|
|
|
break;
|
2017-03-09 13:40:36 +01:00
|
|
|
case T_GatherMerge:
|
|
|
|
pname = sname = "Gather Merge";
|
|
|
|
break;
|
1997-09-08 04:41:22 +02:00
|
|
|
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;
|
2017-03-08 16:39:37 +01:00
|
|
|
case T_TableFuncScan:
|
|
|
|
pname = sname = "Table Function Scan";
|
|
|
|
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;
|
2017-04-01 06:17:18 +02:00
|
|
|
case T_NamedTuplestoreScan:
|
|
|
|
pname = sname = "Named Tuplestore Scan";
|
|
|
|
break;
|
2008-10-04 23:56:55 +02:00
|
|
|
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:
|
2016-03-18 18:48:58 +01:00
|
|
|
sname = "Foreign Scan";
|
|
|
|
switch (((ForeignScan *) plan)->operation)
|
|
|
|
{
|
|
|
|
case CMD_SELECT:
|
|
|
|
pname = "Foreign Scan";
|
|
|
|
operation = "Select";
|
|
|
|
break;
|
|
|
|
case CMD_INSERT:
|
|
|
|
pname = "Foreign Insert";
|
|
|
|
operation = "Insert";
|
|
|
|
break;
|
|
|
|
case CMD_UPDATE:
|
|
|
|
pname = "Foreign Update";
|
|
|
|
operation = "Update";
|
|
|
|
break;
|
|
|
|
case CMD_DELETE:
|
|
|
|
pname = "Foreign Delete";
|
|
|
|
operation = "Delete";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pname = "???";
|
|
|
|
break;
|
|
|
|
}
|
2011-02-20 06:17:18 +01:00
|
|
|
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:
|
2002-11-06 01:00:45 +01:00
|
|
|
{
|
2016-01-20 19:46:50 +01:00
|
|
|
Agg *agg = (Agg *) plan;
|
|
|
|
|
Fix some infelicities in EXPLAIN output for parallel query plans.
In non-text output formats, parallelized aggregates were reporting
"Partial" or "Finalize" as a field named "Operation", which might be all
right in the absence of any context --- but other plan node types use that
field to report SQL-visible semantics, such as Select/Insert/Update/Delete.
So that naming choice didn't seem good to me. I changed it to "Partial
Mode".
Also, the field did not appear at all for a non-parallelized Agg plan node,
which is contrary to expectation in non-text formats. We're notionally
producing objects that conform to a schema, so the set of fields for a
given node type and EXPLAIN mode should be well-defined. I set it up to
fill in "Simple" in such cases.
Other fields that were added for parallel query, namely "Parallel Aware"
and Gather's "Single Copy", had not gotten the word on that point either.
Make them appear always in non-text output.
Also, the latter two fields were nominally producing boolean output, but
were getting it wrong, because bool values shouldn't be quoted in JSON or
YAML. Somehow we'd not needed an ExplainPropertyBool formatting subroutine
before 9.6; but now we do, so invent it.
Discussion: <16002.1466972724@sss.pgh.pa.us>
2016-06-30 00:51:20 +02:00
|
|
|
sname = "Aggregate";
|
2016-01-20 19:46:50 +01:00
|
|
|
switch (agg->aggstrategy)
|
|
|
|
{
|
|
|
|
case AGG_PLAIN:
|
|
|
|
pname = "Aggregate";
|
|
|
|
strategy = "Plain";
|
|
|
|
break;
|
|
|
|
case AGG_SORTED:
|
|
|
|
pname = "GroupAggregate";
|
|
|
|
strategy = "Sorted";
|
|
|
|
break;
|
|
|
|
case AGG_HASHED:
|
|
|
|
pname = "HashAggregate";
|
|
|
|
strategy = "Hashed";
|
|
|
|
break;
|
2017-03-27 05:20:54 +02:00
|
|
|
case AGG_MIXED:
|
|
|
|
pname = "MixedAggregate";
|
|
|
|
strategy = "Mixed";
|
|
|
|
break;
|
2016-01-20 19:46:50 +01:00
|
|
|
default:
|
|
|
|
pname = "Aggregate ???";
|
|
|
|
strategy = "???";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
Fix some infelicities in EXPLAIN output for parallel query plans.
In non-text output formats, parallelized aggregates were reporting
"Partial" or "Finalize" as a field named "Operation", which might be all
right in the absence of any context --- but other plan node types use that
field to report SQL-visible semantics, such as Select/Insert/Update/Delete.
So that naming choice didn't seem good to me. I changed it to "Partial
Mode".
Also, the field did not appear at all for a non-parallelized Agg plan node,
which is contrary to expectation in non-text formats. We're notionally
producing objects that conform to a schema, so the set of fields for a
given node type and EXPLAIN mode should be well-defined. I set it up to
fill in "Simple" in such cases.
Other fields that were added for parallel query, namely "Parallel Aware"
and Gather's "Single Copy", had not gotten the word on that point either.
Make them appear always in non-text output.
Also, the latter two fields were nominally producing boolean output, but
were getting it wrong, because bool values shouldn't be quoted in JSON or
YAML. Somehow we'd not needed an ExplainPropertyBool formatting subroutine
before 9.6; but now we do, so invent it.
Discussion: <16002.1466972724@sss.pgh.pa.us>
2016-06-30 00:51:20 +02:00
|
|
|
if (DO_AGGSPLIT_SKIPFINAL(agg->aggsplit))
|
|
|
|
{
|
|
|
|
partialmode = "Partial";
|
|
|
|
pname = psprintf("%s %s", partialmode, pname);
|
|
|
|
}
|
|
|
|
else if (DO_AGGSPLIT_COMBINE(agg->aggsplit))
|
|
|
|
{
|
|
|
|
partialmode = "Finalize";
|
|
|
|
pname = psprintf("%s %s", partialmode, pname);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
partialmode = "Simple";
|
2002-11-06 01:00:45 +01:00
|
|
|
}
|
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;
|
|
|
|
}
|
2015-11-11 14:57:52 +01:00
|
|
|
if (plan->parallel_aware)
|
|
|
|
appendStringInfoString(es->str, "Parallel ");
|
2009-08-10 07:46:50 +02:00
|
|
|
appendStringInfoString(es->str, pname);
|
|
|
|
es->indent++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ExplainPropertyText("Node Type", sname, es);
|
|
|
|
if (strategy)
|
|
|
|
ExplainPropertyText("Strategy", strategy, es);
|
Fix some infelicities in EXPLAIN output for parallel query plans.
In non-text output formats, parallelized aggregates were reporting
"Partial" or "Finalize" as a field named "Operation", which might be all
right in the absence of any context --- but other plan node types use that
field to report SQL-visible semantics, such as Select/Insert/Update/Delete.
So that naming choice didn't seem good to me. I changed it to "Partial
Mode".
Also, the field did not appear at all for a non-parallelized Agg plan node,
which is contrary to expectation in non-text formats. We're notionally
producing objects that conform to a schema, so the set of fields for a
given node type and EXPLAIN mode should be well-defined. I set it up to
fill in "Simple" in such cases.
Other fields that were added for parallel query, namely "Parallel Aware"
and Gather's "Single Copy", had not gotten the word on that point either.
Make them appear always in non-text output.
Also, the latter two fields were nominally producing boolean output, but
were getting it wrong, because bool values shouldn't be quoted in JSON or
YAML. Somehow we'd not needed an ExplainPropertyBool formatting subroutine
before 9.6; but now we do, so invent it.
Discussion: <16002.1466972724@sss.pgh.pa.us>
2016-06-30 00:51:20 +02:00
|
|
|
if (partialmode)
|
|
|
|
ExplainPropertyText("Partial Mode", partialmode, 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);
|
Fix some infelicities in EXPLAIN output for parallel query plans.
In non-text output formats, parallelized aggregates were reporting
"Partial" or "Finalize" as a field named "Operation", which might be all
right in the absence of any context --- but other plan node types use that
field to report SQL-visible semantics, such as Select/Insert/Update/Delete.
So that naming choice didn't seem good to me. I changed it to "Partial
Mode".
Also, the field did not appear at all for a non-parallelized Agg plan node,
which is contrary to expectation in non-text formats. We're notionally
producing objects that conform to a schema, so the set of fields for a
given node type and EXPLAIN mode should be well-defined. I set it up to
fill in "Simple" in such cases.
Other fields that were added for parallel query, namely "Parallel Aware"
and Gather's "Single Copy", had not gotten the word on that point either.
Make them appear always in non-text output.
Also, the latter two fields were nominally producing boolean output, but
were getting it wrong, because bool values shouldn't be quoted in JSON or
YAML. Somehow we'd not needed an ExplainPropertyBool formatting subroutine
before 9.6; but now we do, so invent it.
Discussion: <16002.1466972724@sss.pgh.pa.us>
2016-06-30 00:51:20 +02:00
|
|
|
ExplainPropertyBool("Parallel Aware", plan->parallel_aware, 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:
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
case T_SampleScan:
|
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:
|
2017-03-08 16:39:37 +01:00
|
|
|
case T_TableFuncScan:
|
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:
|
2015-05-01 14:50:35 +02:00
|
|
|
ExplainScanTarget((Scan *) plan, es);
|
|
|
|
break;
|
2011-02-20 06:17:18 +01:00
|
|
|
case T_ForeignScan:
|
2014-11-07 23:26:02 +01:00
|
|
|
case T_CustomScan:
|
2015-05-01 14:50:35 +02:00
|
|
|
if (((Scan *) plan)->scanrelid > 0)
|
|
|
|
ExplainScanTarget((Scan *) plan, es);
|
2009-07-24 23:08:42 +02:00
|
|
|
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
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Startup Cost", NULL, plan->startup_cost,
|
|
|
|
2, es);
|
|
|
|
ExplainPropertyFloat("Total Cost", NULL, plan->total_cost,
|
|
|
|
2, es);
|
|
|
|
ExplainPropertyFloat("Plan Rows", NULL, plan->plan_rows,
|
|
|
|
0, es);
|
|
|
|
ExplainPropertyInteger("Plan Width", NULL, plan->plan_width,
|
|
|
|
es);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
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;
|
2018-03-17 07:13:12 +01:00
|
|
|
double startup_ms = 1000.0 * planstate->instrument->startup / nloops;
|
|
|
|
double total_ms = 1000.0 * planstate->instrument->total / nloops;
|
2009-08-10 07:46:50 +02:00
|
|
|
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,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
" (actual time=%.3f..%.3f rows=%.0f loops=%.0f)",
|
2018-03-17 07:13:12 +01:00
|
|
|
startup_ms, total_ms, rows, nloops);
|
2012-02-07 17:23:04 +01:00
|
|
|
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
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Actual Startup Time", "s", startup_ms,
|
|
|
|
3, es);
|
|
|
|
ExplainPropertyFloat("Actual Total Time", "s", total_ms,
|
|
|
|
3, es);
|
2012-02-07 17:23:04 +01:00
|
|
|
}
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es);
|
|
|
|
ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
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)
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Actual Startup Time", "ms", 0.0, 3, es);
|
|
|
|
ExplainPropertyFloat("Actual Total Time", "ms", 0.0, 3, es);
|
2014-05-20 18:20:47 +02:00
|
|
|
}
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Actual Rows", NULL, 0.0, 0, es);
|
|
|
|
ExplainPropertyFloat("Actual Loops", NULL, 0.0, 0, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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
|
|
|
|
2017-04-08 04:20:03 +02:00
|
|
|
/* unique join */
|
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
|
|
|
case T_NestLoop:
|
|
|
|
case T_MergeJoin:
|
|
|
|
case T_HashJoin:
|
|
|
|
/* try not to be too chatty about this in text mode */
|
|
|
|
if (es->format != EXPLAIN_FORMAT_TEXT ||
|
|
|
|
(es->verbose && ((Join *) plan)->inner_unique))
|
|
|
|
ExplainPropertyBool("Inner Unique",
|
|
|
|
((Join *) plan)->inner_unique,
|
|
|
|
es);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
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)
|
2018-04-10 20:56:15 +02:00
|
|
|
ExplainPropertyFloat("Heap Fetches", NULL,
|
|
|
|
planstate->instrument->ntuples2, 0, 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;
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
case T_SampleScan:
|
|
|
|
show_tablesample(((SampleScan *) plan)->tablesample,
|
|
|
|
planstate, ancestors, es);
|
2018-05-02 01:35:08 +02:00
|
|
|
/* fall through to print additional fields the same as SeqScan */
|
|
|
|
/* FALLTHROUGH */
|
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:
|
2017-04-01 06:17:18 +02:00
|
|
|
case T_NamedTuplestoreScan:
|
2008-10-04 23:56:55 +02:00
|
|
|
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;
|
Add a Gather executor node.
A Gather executor node runs any number of copies of a plan in an equal
number of workers and merges all of the results into a single tuple
stream. It can also run the plan itself, if the workers are
unavailable or haven't started up yet. It is intended to work with
the Partial Seq Scan node which will be added in future commits.
It could also be used to implement parallel query of a different sort
by itself, without help from Partial Seq Scan, if the single_copy mode
is used. In that mode, a worker executes the plan, and the parallel
leader does not, merely collecting the worker's results. So, a Gather
node could be inserted into a plan to split the execution of that plan
across two processes. Nested Gather nodes aren't currently supported,
but we might want to add support for that in the future.
There's nothing in the planner to actually generate Gather nodes yet,
so it's not quite time to break out the champagne. But we're getting
close.
Amit Kapila. Some designs suggestions were provided by me, and I also
reviewed the patch. Single-copy mode, documentation, and other minor
changes also by me.
2015-10-01 01:23:36 +02:00
|
|
|
case T_Gather:
|
|
|
|
{
|
2016-04-06 17:34:02 +02:00
|
|
|
Gather *gather = (Gather *) plan;
|
Add a Gather executor node.
A Gather executor node runs any number of copies of a plan in an equal
number of workers and merges all of the results into a single tuple
stream. It can also run the plan itself, if the workers are
unavailable or haven't started up yet. It is intended to work with
the Partial Seq Scan node which will be added in future commits.
It could also be used to implement parallel query of a different sort
by itself, without help from Partial Seq Scan, if the single_copy mode
is used. In that mode, a worker executes the plan, and the parallel
leader does not, merely collecting the worker's results. So, a Gather
node could be inserted into a plan to split the execution of that plan
across two processes. Nested Gather nodes aren't currently supported,
but we might want to add support for that in the future.
There's nothing in the planner to actually generate Gather nodes yet,
so it's not quite time to break out the champagne. But we're getting
close.
Amit Kapila. Some designs suggestions were provided by me, and I also
reviewed the patch. Single-copy mode, documentation, and other minor
changes also by me.
2015-10-01 01:23:36 +02:00
|
|
|
|
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Workers Planned", NULL,
|
Add a Gather executor node.
A Gather executor node runs any number of copies of a plan in an equal
number of workers and merges all of the results into a single tuple
stream. It can also run the plan itself, if the workers are
unavailable or haven't started up yet. It is intended to work with
the Partial Seq Scan node which will be added in future commits.
It could also be used to implement parallel query of a different sort
by itself, without help from Partial Seq Scan, if the single_copy mode
is used. In that mode, a worker executes the plan, and the parallel
leader does not, merely collecting the worker's results. So, a Gather
node could be inserted into a plan to split the execution of that plan
across two processes. Nested Gather nodes aren't currently supported,
but we might want to add support for that in the future.
There's nothing in the planner to actually generate Gather nodes yet,
so it's not quite time to break out the champagne. But we're getting
close.
Amit Kapila. Some designs suggestions were provided by me, and I also
reviewed the patch. Single-copy mode, documentation, and other minor
changes also by me.
2015-10-01 01:23:36 +02:00
|
|
|
gather->num_workers, es);
|
2017-11-16 18:06:14 +01:00
|
|
|
|
|
|
|
/* Show params evaluated at gather node */
|
|
|
|
if (gather->initParam)
|
|
|
|
show_eval_params(gather->initParam, es);
|
|
|
|
|
2016-04-15 17:49:41 +02:00
|
|
|
if (es->analyze)
|
|
|
|
{
|
|
|
|
int nworkers;
|
|
|
|
|
|
|
|
nworkers = ((GatherState *) planstate)->nworkers_launched;
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Workers Launched", NULL,
|
2016-04-15 17:49:41 +02:00
|
|
|
nworkers, es);
|
|
|
|
}
|
Fix some infelicities in EXPLAIN output for parallel query plans.
In non-text output formats, parallelized aggregates were reporting
"Partial" or "Finalize" as a field named "Operation", which might be all
right in the absence of any context --- but other plan node types use that
field to report SQL-visible semantics, such as Select/Insert/Update/Delete.
So that naming choice didn't seem good to me. I changed it to "Partial
Mode".
Also, the field did not appear at all for a non-parallelized Agg plan node,
which is contrary to expectation in non-text formats. We're notionally
producing objects that conform to a schema, so the set of fields for a
given node type and EXPLAIN mode should be well-defined. I set it up to
fill in "Simple" in such cases.
Other fields that were added for parallel query, namely "Parallel Aware"
and Gather's "Single Copy", had not gotten the word on that point either.
Make them appear always in non-text output.
Also, the latter two fields were nominally producing boolean output, but
were getting it wrong, because bool values shouldn't be quoted in JSON or
YAML. Somehow we'd not needed an ExplainPropertyBool formatting subroutine
before 9.6; but now we do, so invent it.
Discussion: <16002.1466972724@sss.pgh.pa.us>
2016-06-30 00:51:20 +02:00
|
|
|
if (gather->single_copy || es->format != EXPLAIN_FORMAT_TEXT)
|
|
|
|
ExplainPropertyBool("Single Copy", gather->single_copy, es);
|
Add a Gather executor node.
A Gather executor node runs any number of copies of a plan in an equal
number of workers and merges all of the results into a single tuple
stream. It can also run the plan itself, if the workers are
unavailable or haven't started up yet. It is intended to work with
the Partial Seq Scan node which will be added in future commits.
It could also be used to implement parallel query of a different sort
by itself, without help from Partial Seq Scan, if the single_copy mode
is used. In that mode, a worker executes the plan, and the parallel
leader does not, merely collecting the worker's results. So, a Gather
node could be inserted into a plan to split the execution of that plan
across two processes. Nested Gather nodes aren't currently supported,
but we might want to add support for that in the future.
There's nothing in the planner to actually generate Gather nodes yet,
so it's not quite time to break out the champagne. But we're getting
close.
Amit Kapila. Some designs suggestions were provided by me, and I also
reviewed the patch. Single-copy mode, documentation, and other minor
changes also by me.
2015-10-01 01:23:36 +02:00
|
|
|
}
|
|
|
|
break;
|
2017-03-09 13:40:36 +01:00
|
|
|
case T_GatherMerge:
|
|
|
|
{
|
|
|
|
GatherMerge *gm = (GatherMerge *) plan;
|
|
|
|
|
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Workers Planned", NULL,
|
2017-03-09 13:40:36 +01:00
|
|
|
gm->num_workers, es);
|
2017-11-16 18:06:14 +01:00
|
|
|
|
|
|
|
/* Show params evaluated at gather-merge node */
|
|
|
|
if (gm->initParam)
|
|
|
|
show_eval_params(gm->initParam, es);
|
|
|
|
|
2017-03-09 13:40:36 +01:00
|
|
|
if (es->analyze)
|
|
|
|
{
|
|
|
|
int nworkers;
|
|
|
|
|
|
|
|
nworkers = ((GatherMergeState *) planstate)->nworkers_launched;
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Workers Launched", NULL,
|
2017-03-09 13:40:36 +01:00
|
|
|
nworkers, es);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
2017-03-08 16:39:37 +01:00
|
|
|
case T_TableFuncScan:
|
|
|
|
if (es->verbose)
|
|
|
|
{
|
|
|
|
TableFunc *tablefunc = ((TableFuncScan *) plan)->tablefunc;
|
|
|
|
|
|
|
|
show_expression((Node *) tablefunc,
|
|
|
|
"Table Function Call", planstate, ancestors,
|
|
|
|
es->verbose, es);
|
|
|
|
}
|
|
|
|
show_scan_qual(plan->qual, "Filter", planstate, ancestors, es);
|
|
|
|
if (plan->qual)
|
|
|
|
show_instrumentation_count("Rows Removed by Filter", 1,
|
|
|
|
planstate, es);
|
|
|
|
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:
|
2017-01-27 01:47:03 +01:00
|
|
|
show_agg_keys(castNode(AggState, planstate), ancestors, es);
|
2013-12-12 17:24:38 +01:00
|
|
|
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:
|
2017-01-27 01:47:03 +01:00
|
|
|
show_group_keys(castNode(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:
|
2017-01-27 01:47:03 +01:00
|
|
|
show_sort_keys(castNode(SortState, planstate), ancestors, es);
|
|
|
|
show_sort_info(castNode(SortState, planstate), es);
|
2002-05-18 23:38:41 +02:00
|
|
|
break;
|
2010-10-14 22:56:39 +02:00
|
|
|
case T_MergeAppend:
|
2017-01-27 01:47:03 +01:00
|
|
|
show_merge_append_keys(castNode(MergeAppendState, planstate),
|
2010-10-14 22:56:39 +02:00
|
|
|
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:
|
2017-01-27 01:47:03 +01:00
|
|
|
show_modifytable_info(castNode(ModifyTableState, planstate), ancestors,
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
es);
|
2013-03-10 19:14:53 +01:00
|
|
|
break;
|
2010-02-01 16:43:36 +01:00
|
|
|
case T_Hash:
|
2017-01-27 01:47:03 +01:00
|
|
|
show_hash_info(castNode(HashState, planstate), es);
|
2010-02-01 16:43:36 +01:00
|
|
|
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)
|
2015-12-09 19:18:09 +01:00
|
|
|
show_buffer_usage(es, &planstate->instrument->bufusage);
|
|
|
|
|
|
|
|
/* Show worker detail */
|
|
|
|
if (es->analyze && es->verbose && planstate->worker_instrument)
|
2009-12-15 05:57:48 +01:00
|
|
|
{
|
2015-12-09 19:18:09 +01:00
|
|
|
WorkerInstrumentation *w = planstate->worker_instrument;
|
|
|
|
bool opened_group = false;
|
|
|
|
int n;
|
2009-12-15 05:57:48 +01:00
|
|
|
|
2015-12-09 19:18:09 +01:00
|
|
|
for (n = 0; n < w->num_workers; ++n)
|
2009-12-15 05:57:48 +01:00
|
|
|
{
|
2015-12-09 19:18:09 +01:00
|
|
|
Instrumentation *instrument = &w->instrument[n];
|
|
|
|
double nloops = instrument->nloops;
|
2018-03-17 07:13:12 +01:00
|
|
|
double startup_ms;
|
|
|
|
double total_ms;
|
2015-12-09 19:18:09 +01:00
|
|
|
double rows;
|
|
|
|
|
|
|
|
if (nloops <= 0)
|
|
|
|
continue;
|
2018-03-17 07:13:12 +01:00
|
|
|
startup_ms = 1000.0 * instrument->startup / nloops;
|
|
|
|
total_ms = 1000.0 * instrument->total / nloops;
|
2015-12-09 19:18:09 +01:00
|
|
|
rows = instrument->ntuples / nloops;
|
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
2009-12-15 05:57:48 +01:00
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
2015-12-09 19:18:09 +01:00
|
|
|
appendStringInfo(es->str, "Worker %d: ", n);
|
|
|
|
if (es->timing)
|
|
|
|
appendStringInfo(es->str,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
"actual time=%.3f..%.3f rows=%.0f loops=%.0f\n",
|
2018-03-17 07:13:12 +01:00
|
|
|
startup_ms, total_ms, rows, nloops);
|
2015-12-09 19:18:09 +01:00
|
|
|
else
|
|
|
|
appendStringInfo(es->str,
|
|
|
|
"actual rows=%.0f loops=%.0f\n",
|
|
|
|
rows, nloops);
|
|
|
|
es->indent++;
|
|
|
|
if (es->buffers)
|
|
|
|
show_buffer_usage(es, &instrument->bufusage);
|
|
|
|
es->indent--;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!opened_group)
|
2009-12-15 05:57:48 +01:00
|
|
|
{
|
2015-12-09 19:18:09 +01:00
|
|
|
ExplainOpenGroup("Workers", "Workers", false, es);
|
|
|
|
opened_group = true;
|
2009-12-15 05:57:48 +01:00
|
|
|
}
|
2015-12-09 19:18:09 +01:00
|
|
|
ExplainOpenGroup("Worker", NULL, true, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Worker Number", NULL, n, es);
|
2015-12-09 19:18:09 +01:00
|
|
|
|
|
|
|
if (es->timing)
|
2009-12-15 05:57:48 +01:00
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Actual Startup Time", "ms",
|
|
|
|
startup_ms, 3, es);
|
|
|
|
ExplainPropertyFloat("Actual Total Time", "ms",
|
|
|
|
total_ms, 3, es);
|
2009-12-15 05:57:48 +01:00
|
|
|
}
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Actual Rows", NULL, rows, 0, es);
|
|
|
|
ExplainPropertyFloat("Actual Loops", NULL, nloops, 0, es);
|
2012-03-27 20:52:37 +02:00
|
|
|
|
2015-12-09 19:18:09 +01:00
|
|
|
if (es->buffers)
|
|
|
|
show_buffer_usage(es, &instrument->bufusage);
|
|
|
|
|
|
|
|
ExplainCloseGroup("Worker", NULL, true, es);
|
2012-03-27 20:52:37 +02:00
|
|
|
}
|
2009-12-15 05:57:48 +01:00
|
|
|
}
|
2015-12-09 19:18:09 +01:00
|
|
|
|
|
|
|
if (opened_group)
|
|
|
|
ExplainCloseGroup("Workers", "Workers", false, 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) ||
|
2015-06-26 15:40:47 +02:00
|
|
|
(IsA(planstate, CustomScanState) &&
|
|
|
|
((CustomScanState *) planstate)->custom_ps != NIL) ||
|
2009-08-10 07:46:50 +02:00
|
|
|
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:
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
ExplainMemberNodes(((ModifyTableState *) planstate)->mt_plans,
|
|
|
|
((ModifyTableState *) planstate)->mt_nplans,
|
|
|
|
list_length(((ModifyTable *) plan)->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:
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
ExplainMemberNodes(((AppendState *) planstate)->appendplans,
|
|
|
|
((AppendState *) planstate)->as_nplans,
|
|
|
|
list_length(((Append *) plan)->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:
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
ExplainMemberNodes(((MergeAppendState *) planstate)->mergeplans,
|
|
|
|
((MergeAppendState *) planstate)->ms_nplans,
|
|
|
|
list_length(((MergeAppend *) plan)->mergeplans),
|
2010-10-14 22:56:39 +02:00
|
|
|
ancestors, es);
|
|
|
|
break;
|
2009-07-24 23:08:42 +02:00
|
|
|
case T_BitmapAnd:
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
ExplainMemberNodes(((BitmapAndState *) planstate)->bitmapplans,
|
|
|
|
((BitmapAndState *) planstate)->nplans,
|
|
|
|
list_length(((BitmapAnd *) plan)->bitmapplans),
|
2010-07-13 22:57:19 +02:00
|
|
|
ancestors, es);
|
2009-07-24 23:08:42 +02:00
|
|
|
break;
|
|
|
|
case T_BitmapOr:
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
ExplainMemberNodes(((BitmapOrState *) planstate)->bitmapplans,
|
|
|
|
((BitmapOrState *) planstate)->nplans,
|
|
|
|
list_length(((BitmapOr *) plan)->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;
|
2015-06-26 15:40:47 +02:00
|
|
|
case T_CustomScan:
|
|
|
|
ExplainCustomChildren((CustomScanState *) planstate,
|
|
|
|
ancestors, es);
|
|
|
|
break;
|
2009-07-24 23:08:42 +02:00
|
|
|
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;
|
2016-04-06 17:34:02 +02:00
|
|
|
|
2016-03-18 18:48:58 +01:00
|
|
|
/*
|
|
|
|
* Likewise for ForeignScan that executes a direct INSERT/UPDATE/DELETE
|
|
|
|
*
|
|
|
|
* Note: the tlist for a ForeignScan that executes a direct INSERT/UPDATE
|
|
|
|
* might contain subplan output expressions that are confusing in this
|
|
|
|
* context. The tlist for a ForeignScan that executes a direct UPDATE/
|
|
|
|
* DELETE always contains "junk" target columns to identify the exact row
|
|
|
|
* to update or delete, which would be confusing in this context. So, we
|
|
|
|
* suppress it in all the cases.
|
|
|
|
*/
|
|
|
|
if (IsA(plan, ForeignScan) &&
|
|
|
|
((ForeignScan *) plan)->operation != CMD_SELECT)
|
|
|
|
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;
|
|
|
|
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
if (plan->numCols > 0 || plan->groupingSets)
|
2013-12-12 17:24:38 +01:00
|
|
|
{
|
|
|
|
/* The key columns refer to the tlist of the child plan */
|
|
|
|
ancestors = lcons(astate, ancestors);
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
|
|
|
|
if (plan->groupingSets)
|
|
|
|
show_grouping_sets(outerPlanState(astate), plan, ancestors, es);
|
|
|
|
else
|
|
|
|
show_sort_group_keys(outerPlanState(astate), "Group Key",
|
|
|
|
plan->numCols, plan->grpColIdx,
|
|
|
|
NULL, NULL, NULL,
|
|
|
|
ancestors, es);
|
|
|
|
|
2013-12-12 17:24:38 +01:00
|
|
|
ancestors = list_delete_first(ancestors);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
static void
|
|
|
|
show_grouping_sets(PlanState *planstate, Agg *agg,
|
|
|
|
List *ancestors, ExplainState *es)
|
|
|
|
{
|
|
|
|
List *context;
|
|
|
|
bool useprefix;
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
/* Set up deparsing context */
|
|
|
|
context = set_deparse_context_planstate(es->deparse_cxt,
|
|
|
|
(Node *) planstate,
|
|
|
|
ancestors);
|
|
|
|
useprefix = (list_length(es->rtable) > 1 || es->verbose);
|
|
|
|
|
|
|
|
ExplainOpenGroup("Grouping Sets", "Grouping Sets", false, es);
|
|
|
|
|
|
|
|
show_grouping_set_keys(planstate, agg, NULL,
|
|
|
|
context, useprefix, ancestors, es);
|
|
|
|
|
|
|
|
foreach(lc, agg->chain)
|
|
|
|
{
|
2015-05-24 03:35:49 +02:00
|
|
|
Agg *aggnode = lfirst(lc);
|
|
|
|
Sort *sortnode = (Sort *) aggnode->plan.lefttree;
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
|
|
|
|
show_grouping_set_keys(planstate, aggnode, sortnode,
|
|
|
|
context, useprefix, ancestors, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
ExplainCloseGroup("Grouping Sets", "Grouping Sets", false, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
show_grouping_set_keys(PlanState *planstate,
|
|
|
|
Agg *aggnode, Sort *sortnode,
|
|
|
|
List *context, bool useprefix,
|
|
|
|
List *ancestors, ExplainState *es)
|
|
|
|
{
|
|
|
|
Plan *plan = planstate->plan;
|
|
|
|
char *exprstr;
|
|
|
|
ListCell *lc;
|
|
|
|
List *gsets = aggnode->groupingSets;
|
|
|
|
AttrNumber *keycols = aggnode->grpColIdx;
|
2017-03-27 05:20:54 +02:00
|
|
|
const char *keyname;
|
|
|
|
const char *keysetname;
|
|
|
|
|
|
|
|
if (aggnode->aggstrategy == AGG_HASHED || aggnode->aggstrategy == AGG_MIXED)
|
|
|
|
{
|
|
|
|
keyname = "Hash Key";
|
|
|
|
keysetname = "Hash Keys";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
keyname = "Group Key";
|
|
|
|
keysetname = "Group Keys";
|
|
|
|
}
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
|
|
|
|
ExplainOpenGroup("Grouping Set", NULL, true, es);
|
|
|
|
|
|
|
|
if (sortnode)
|
|
|
|
{
|
|
|
|
show_sort_group_keys(planstate, "Sort Key",
|
|
|
|
sortnode->numCols, sortnode->sortColIdx,
|
|
|
|
sortnode->sortOperators, sortnode->collations,
|
|
|
|
sortnode->nullsFirst,
|
|
|
|
ancestors, es);
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
es->indent++;
|
|
|
|
}
|
|
|
|
|
2017-03-27 05:20:54 +02:00
|
|
|
ExplainOpenGroup(keysetname, keysetname, false, es);
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
|
|
|
|
foreach(lc, gsets)
|
|
|
|
{
|
|
|
|
List *result = NIL;
|
|
|
|
ListCell *lc2;
|
|
|
|
|
|
|
|
foreach(lc2, (List *) lfirst(lc))
|
|
|
|
{
|
|
|
|
Index i = lfirst_int(lc2);
|
|
|
|
AttrNumber keyresno = keycols[i];
|
|
|
|
TargetEntry *target = get_tle_by_resno(plan->targetlist,
|
|
|
|
keyresno);
|
|
|
|
|
|
|
|
if (!target)
|
|
|
|
elog(ERROR, "no tlist entry for key %d", keyresno);
|
|
|
|
/* Deparse the expression, showing any top-level cast */
|
|
|
|
exprstr = deparse_expression((Node *) target->expr, context,
|
|
|
|
useprefix, true);
|
|
|
|
|
|
|
|
result = lappend(result, exprstr);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!result && es->format == EXPLAIN_FORMAT_TEXT)
|
2017-03-27 05:20:54 +02:00
|
|
|
ExplainPropertyText(keyname, "()", es);
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
else
|
2017-03-27 05:20:54 +02:00
|
|
|
ExplainPropertyListNested(keyname, result, es);
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
}
|
|
|
|
|
2017-03-27 05:20:54 +02:00
|
|
|
ExplainCloseGroup(keysetname, keysetname, false, es);
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
|
|
|
|
if (sortnode && es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
es->indent--;
|
|
|
|
|
|
|
|
ExplainCloseGroup("Grouping Set", NULL, true, es);
|
|
|
|
}
|
|
|
|
|
2013-12-12 17:24:38 +01:00
|
|
|
/*
|
|
|
|
* 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");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
/*
|
|
|
|
* Show TABLESAMPLE properties
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_tablesample(TableSampleClause *tsc, PlanState *planstate,
|
|
|
|
List *ancestors, ExplainState *es)
|
|
|
|
{
|
|
|
|
List *context;
|
|
|
|
bool useprefix;
|
|
|
|
char *method_name;
|
|
|
|
List *params = NIL;
|
|
|
|
char *repeatable;
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
/* Set up deparsing context */
|
|
|
|
context = set_deparse_context_planstate(es->deparse_cxt,
|
|
|
|
(Node *) planstate,
|
|
|
|
ancestors);
|
|
|
|
useprefix = list_length(es->rtable) > 1;
|
|
|
|
|
|
|
|
/* Get the tablesample method name */
|
|
|
|
method_name = get_func_name(tsc->tsmhandler);
|
|
|
|
|
|
|
|
/* Deparse parameter expressions */
|
|
|
|
foreach(lc, tsc->args)
|
|
|
|
{
|
|
|
|
Node *arg = (Node *) lfirst(lc);
|
|
|
|
|
|
|
|
params = lappend(params,
|
|
|
|
deparse_expression(arg, context,
|
|
|
|
useprefix, false));
|
|
|
|
}
|
|
|
|
if (tsc->repeatable)
|
|
|
|
repeatable = deparse_expression((Node *) tsc->repeatable, context,
|
|
|
|
useprefix, false);
|
|
|
|
else
|
|
|
|
repeatable = NULL;
|
|
|
|
|
|
|
|
/* Print results */
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfo(es->str, "Sampling: %s (", method_name);
|
|
|
|
foreach(lc, params)
|
|
|
|
{
|
|
|
|
if (!first)
|
|
|
|
appendStringInfoString(es->str, ", ");
|
|
|
|
appendStringInfoString(es->str, (const char *) lfirst(lc));
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
appendStringInfoChar(es->str, ')');
|
|
|
|
if (repeatable)
|
|
|
|
appendStringInfo(es->str, " REPEATABLE (%s)", repeatable);
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ExplainPropertyText("Sampling Method", method_name, es);
|
|
|
|
ExplainPropertyList("Sampling Parameters", params, es);
|
|
|
|
if (repeatable)
|
|
|
|
ExplainPropertyText("Repeatable Seed", repeatable, es);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2017-08-29 19:22:49 +02:00
|
|
|
if (!es->analyze)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (sortstate->sort_Done && sortstate->tuplesortstate != NULL)
|
2007-05-04 23:29:53 +02:00
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
Tuplesortstate *state = (Tuplesortstate *) sortstate->tuplesortstate;
|
2017-08-29 19:22:49 +02:00
|
|
|
TuplesortInstrumentation stats;
|
2009-08-10 07:46:50 +02:00
|
|
|
const char *sortMethod;
|
|
|
|
const char *spaceType;
|
|
|
|
long spaceUsed;
|
2007-05-04 23:29:53 +02:00
|
|
|
|
2017-08-29 19:22:49 +02:00
|
|
|
tuplesort_get_stats(state, &stats);
|
|
|
|
sortMethod = tuplesort_method_name(stats.sortMethod);
|
|
|
|
spaceType = tuplesort_space_type_name(stats.spaceType);
|
|
|
|
spaceUsed = stats.spaceUsed;
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
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);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
ExplainPropertyText("Sort Space Type", spaceType, es);
|
|
|
|
}
|
2007-05-04 23:29:53 +02:00
|
|
|
}
|
2017-08-29 19:22:49 +02:00
|
|
|
|
|
|
|
if (sortstate->shared_info != NULL)
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
bool opened_group = false;
|
|
|
|
|
|
|
|
for (n = 0; n < sortstate->shared_info->num_workers; n++)
|
|
|
|
{
|
|
|
|
TuplesortInstrumentation *sinstrument;
|
|
|
|
const char *sortMethod;
|
|
|
|
const char *spaceType;
|
|
|
|
long spaceUsed;
|
|
|
|
|
|
|
|
sinstrument = &sortstate->shared_info->sinstrument[n];
|
|
|
|
if (sinstrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)
|
|
|
|
continue; /* ignore any unfilled slots */
|
|
|
|
sortMethod = tuplesort_method_name(sinstrument->sortMethod);
|
|
|
|
spaceType = tuplesort_space_type_name(sinstrument->spaceType);
|
|
|
|
spaceUsed = sinstrument->spaceUsed;
|
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfo(es->str,
|
|
|
|
"Worker %d: Sort Method: %s %s: %ldkB\n",
|
|
|
|
n, sortMethod, spaceType, spaceUsed);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!opened_group)
|
|
|
|
{
|
|
|
|
ExplainOpenGroup("Workers", "Workers", false, es);
|
|
|
|
opened_group = true;
|
|
|
|
}
|
|
|
|
ExplainOpenGroup("Worker", NULL, true, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Worker Number", NULL, n, es);
|
2017-08-29 19:22:49 +02:00
|
|
|
ExplainPropertyText("Sort Method", sortMethod, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Sort Space Used", "kB", spaceUsed, es);
|
2017-08-29 19:22:49 +02:00
|
|
|
ExplainPropertyText("Sort Space Type", spaceType, es);
|
|
|
|
ExplainCloseGroup("Worker", NULL, true, es);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (opened_group)
|
|
|
|
ExplainCloseGroup("Workers", "Workers", false, 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)
|
|
|
|
{
|
2018-01-01 23:38:23 +01:00
|
|
|
HashInstrumentation hinstrument = {0};
|
2010-02-01 16:43:36 +01:00
|
|
|
|
2017-12-05 19:55:56 +01:00
|
|
|
/*
|
|
|
|
* In a parallel query, the leader process may or may not have run the
|
|
|
|
* hash join, and even if it did it may not have built a hash table due to
|
|
|
|
* timing (if it started late it might have seen no tuples in the outer
|
|
|
|
* relation and skipped building the hash table). Therefore we have to be
|
2018-01-01 23:38:23 +01:00
|
|
|
* prepared to get instrumentation data from all participants.
|
2017-12-05 19:55:56 +01:00
|
|
|
*/
|
|
|
|
if (hashstate->hashtable)
|
2018-01-01 23:38:23 +01:00
|
|
|
ExecHashGetInstrumentation(&hinstrument, hashstate->hashtable);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Merge results from workers. In the parallel-oblivious case, the
|
|
|
|
* results from all participants should be identical, except where
|
|
|
|
* participants didn't run the join at all so have no data. In the
|
|
|
|
* parallel-aware case, we need to consider all the results. Each worker
|
|
|
|
* may have seen a different subset of batches and we want to find the
|
|
|
|
* highest memory usage for any one batch across all batches.
|
|
|
|
*/
|
|
|
|
if (hashstate->shared_info)
|
2017-12-05 19:55:56 +01:00
|
|
|
{
|
|
|
|
SharedHashInfo *shared_info = hashstate->shared_info;
|
2018-01-01 23:38:23 +01:00
|
|
|
int i;
|
2017-12-05 19:55:56 +01:00
|
|
|
|
|
|
|
for (i = 0; i < shared_info->num_workers; ++i)
|
|
|
|
{
|
2018-01-01 23:38:23 +01:00
|
|
|
HashInstrumentation *worker_hi = &shared_info->hinstrument[i];
|
|
|
|
|
|
|
|
if (worker_hi->nbatch > 0)
|
2017-12-05 19:55:56 +01:00
|
|
|
{
|
2018-01-01 23:38:23 +01:00
|
|
|
/*
|
|
|
|
* Every participant should agree on the buckets, so to be
|
|
|
|
* sure we have a value we'll just overwrite each time.
|
|
|
|
*/
|
|
|
|
hinstrument.nbuckets = worker_hi->nbuckets;
|
|
|
|
hinstrument.nbuckets_original = worker_hi->nbuckets_original;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Normally every participant should agree on the number of
|
|
|
|
* batches too, but it's possible for a backend that started
|
|
|
|
* late and missed the whole join not to have the final nbatch
|
|
|
|
* number. So we'll take the largest number.
|
|
|
|
*/
|
|
|
|
hinstrument.nbatch = Max(hinstrument.nbatch, worker_hi->nbatch);
|
|
|
|
hinstrument.nbatch_original = worker_hi->nbatch_original;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In a parallel-aware hash join, for now we report the
|
|
|
|
* maximum peak memory reported by any worker.
|
|
|
|
*/
|
|
|
|
hinstrument.space_peak =
|
|
|
|
Max(hinstrument.space_peak, worker_hi->space_peak);
|
2017-12-05 19:55:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-02-01 16:43:36 +01:00
|
|
|
|
2018-01-01 23:38:23 +01:00
|
|
|
if (hinstrument.nbatch > 0)
|
2010-02-01 16:43:36 +01:00
|
|
|
{
|
2018-01-01 23:38:23 +01:00
|
|
|
long spacePeakKb = (hinstrument.space_peak + 1023) / 1024;
|
2010-02-26 03:01:40 +01:00
|
|
|
|
2010-02-01 16:43:36 +01:00
|
|
|
if (es->format != EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Hash Buckets", NULL,
|
|
|
|
hinstrument.nbuckets, es);
|
|
|
|
ExplainPropertyInteger("Original Hash Buckets", NULL,
|
|
|
|
hinstrument.nbuckets_original, es);
|
|
|
|
ExplainPropertyInteger("Hash Batches", NULL,
|
|
|
|
hinstrument.nbatch, es);
|
|
|
|
ExplainPropertyInteger("Original Hash Batches", NULL,
|
|
|
|
hinstrument.nbatch_original, es);
|
|
|
|
ExplainPropertyInteger("Peak Memory Usage", "kB",
|
|
|
|
spacePeakKb, es);
|
2010-02-01 16:43:36 +01:00
|
|
|
}
|
2018-01-01 23:38:23 +01:00
|
|
|
else if (hinstrument.nbatch_original != hinstrument.nbatch ||
|
|
|
|
hinstrument.nbuckets_original != hinstrument.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",
|
2018-01-01 23:38:23 +01:00
|
|
|
hinstrument.nbuckets,
|
|
|
|
hinstrument.nbuckets_original,
|
|
|
|
hinstrument.nbatch,
|
|
|
|
hinstrument.nbatch_original,
|
2014-11-20 18:32:19 +01:00
|
|
|
spacePeakKb);
|
2010-02-01 16:43:36 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfo(es->str,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
"Buckets: %d Batches: %d Memory Usage: %ldkB\n",
|
2018-01-01 23:38:23 +01:00
|
|
|
hinstrument.nbuckets, hinstrument.nbatch,
|
2010-02-01 16:43:36 +01:00
|
|
|
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)
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Exact Heap Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
planstate->exact_pages, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Lossy Heap Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
planstate->lossy_pages, es);
|
2014-01-13 20:42:16 +01:00
|
|
|
}
|
|
|
|
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)
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat(qlabel, NULL, nfiltered / nloops, 0, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
else
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat(qlabel, NULL, 0.0, 0, es);
|
2011-09-22 17:29:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 */
|
2016-03-18 18:48:58 +01:00
|
|
|
if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT)
|
|
|
|
{
|
|
|
|
if (fdwroutine->ExplainDirectModify != NULL)
|
|
|
|
fdwroutine->ExplainDirectModify(fsstate, es);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (fdwroutine->ExplainForeignScan != NULL)
|
|
|
|
fdwroutine->ExplainForeignScan(fsstate, es);
|
|
|
|
}
|
2011-02-20 06:17:18 +01:00
|
|
|
}
|
|
|
|
|
2017-11-16 18:06:14 +01:00
|
|
|
/*
|
|
|
|
* Show initplan params evaluated at Gather or Gather Merge node.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_eval_params(Bitmapset *bms_params, ExplainState *es)
|
|
|
|
{
|
|
|
|
int paramid = -1;
|
|
|
|
List *params = NIL;
|
|
|
|
|
|
|
|
Assert(bms_params);
|
|
|
|
|
|
|
|
while ((paramid = bms_next_member(bms_params, paramid)) >= 0)
|
|
|
|
{
|
|
|
|
char param[32];
|
|
|
|
|
|
|
|
snprintf(param, sizeof(param), "$%d", paramid);
|
|
|
|
params = lappend(params, pstrdup(param));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (params)
|
|
|
|
ExplainPropertyList("Params Evaluated", params, es);
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2015-12-09 19:18:09 +01:00
|
|
|
/*
|
|
|
|
* Show buffer usage details.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
show_buffer_usage(ExplainState *es, const BufferUsage *usage)
|
|
|
|
{
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
bool has_shared = (usage->shared_blks_hit > 0 ||
|
|
|
|
usage->shared_blks_read > 0 ||
|
|
|
|
usage->shared_blks_dirtied > 0 ||
|
|
|
|
usage->shared_blks_written > 0);
|
|
|
|
bool has_local = (usage->local_blks_hit > 0 ||
|
|
|
|
usage->local_blks_read > 0 ||
|
|
|
|
usage->local_blks_dirtied > 0 ||
|
|
|
|
usage->local_blks_written > 0);
|
|
|
|
bool has_temp = (usage->temp_blks_read > 0 ||
|
|
|
|
usage->temp_blks_written > 0);
|
|
|
|
bool has_timing = (!INSTR_TIME_IS_ZERO(usage->blk_read_time) ||
|
2016-04-06 17:34:02 +02:00
|
|
|
!INSTR_TIME_IS_ZERO(usage->blk_write_time));
|
2015-12-09 19:18:09 +01:00
|
|
|
|
|
|
|
/* Show only positive counter values. */
|
|
|
|
if (has_shared || has_local || has_temp)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfoString(es->str, "Buffers:");
|
|
|
|
|
|
|
|
if (has_shared)
|
|
|
|
{
|
|
|
|
appendStringInfoString(es->str, " shared");
|
|
|
|
if (usage->shared_blks_hit > 0)
|
|
|
|
appendStringInfo(es->str, " hit=%ld",
|
|
|
|
usage->shared_blks_hit);
|
|
|
|
if (usage->shared_blks_read > 0)
|
|
|
|
appendStringInfo(es->str, " read=%ld",
|
|
|
|
usage->shared_blks_read);
|
|
|
|
if (usage->shared_blks_dirtied > 0)
|
|
|
|
appendStringInfo(es->str, " dirtied=%ld",
|
|
|
|
usage->shared_blks_dirtied);
|
|
|
|
if (usage->shared_blks_written > 0)
|
|
|
|
appendStringInfo(es->str, " written=%ld",
|
|
|
|
usage->shared_blks_written);
|
|
|
|
if (has_local || has_temp)
|
|
|
|
appendStringInfoChar(es->str, ',');
|
|
|
|
}
|
|
|
|
if (has_local)
|
|
|
|
{
|
|
|
|
appendStringInfoString(es->str, " local");
|
|
|
|
if (usage->local_blks_hit > 0)
|
|
|
|
appendStringInfo(es->str, " hit=%ld",
|
|
|
|
usage->local_blks_hit);
|
|
|
|
if (usage->local_blks_read > 0)
|
|
|
|
appendStringInfo(es->str, " read=%ld",
|
|
|
|
usage->local_blks_read);
|
|
|
|
if (usage->local_blks_dirtied > 0)
|
|
|
|
appendStringInfo(es->str, " dirtied=%ld",
|
|
|
|
usage->local_blks_dirtied);
|
|
|
|
if (usage->local_blks_written > 0)
|
|
|
|
appendStringInfo(es->str, " written=%ld",
|
|
|
|
usage->local_blks_written);
|
|
|
|
if (has_temp)
|
|
|
|
appendStringInfoChar(es->str, ',');
|
|
|
|
}
|
|
|
|
if (has_temp)
|
|
|
|
{
|
|
|
|
appendStringInfoString(es->str, " temp");
|
|
|
|
if (usage->temp_blks_read > 0)
|
|
|
|
appendStringInfo(es->str, " read=%ld",
|
|
|
|
usage->temp_blks_read);
|
|
|
|
if (usage->temp_blks_written > 0)
|
|
|
|
appendStringInfo(es->str, " written=%ld",
|
|
|
|
usage->temp_blks_written);
|
|
|
|
}
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
/* As above, show only positive counter values. */
|
|
|
|
if (has_timing)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfoString(es->str, "I/O Timings:");
|
|
|
|
if (!INSTR_TIME_IS_ZERO(usage->blk_read_time))
|
|
|
|
appendStringInfo(es->str, " read=%0.3f",
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
INSTR_TIME_GET_MILLISEC(usage->blk_read_time));
|
2015-12-09 19:18:09 +01:00
|
|
|
if (!INSTR_TIME_IS_ZERO(usage->blk_write_time))
|
|
|
|
appendStringInfo(es->str, " write=%0.3f",
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
INSTR_TIME_GET_MILLISEC(usage->blk_write_time));
|
2015-12-09 19:18:09 +01:00
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Shared Hit Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->shared_blks_hit, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Shared Read Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->shared_blks_read, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Shared Dirtied Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->shared_blks_dirtied, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Shared Written Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->shared_blks_written, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Local Hit Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->local_blks_hit, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Local Read Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->local_blks_read, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Local Dirtied Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->local_blks_dirtied, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Local Written Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->local_blks_written, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Temp Read Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->temp_blks_read, es);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger("Temp Written Blocks", NULL,
|
2018-03-17 07:13:12 +01:00
|
|
|
usage->temp_blks_written, es);
|
2016-08-12 18:13:04 +02:00
|
|
|
if (track_io_timing)
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("I/O Read Time", "ms",
|
|
|
|
INSTR_TIME_GET_MILLISEC(usage->blk_read_time),
|
|
|
|
3, es);
|
|
|
|
ExplainPropertyFloat("I/O Write Time", "ms",
|
|
|
|
INSTR_TIME_GET_MILLISEC(usage->blk_write_time),
|
|
|
|
3, es);
|
2016-08-12 18:13:04 +02:00
|
|
|
}
|
2015-12-09 19:18:09 +01: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
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
*
|
|
|
|
* Here we show the nominal target (ie, the relation that was named in the
|
|
|
|
* original query). If the actual target(s) is/are different, we'll show them
|
|
|
|
* in show_modifytable_info().
|
2011-03-01 17:32:13 +01:00
|
|
|
*/
|
|
|
|
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:
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
case T_SampleScan:
|
2009-07-24 23:08:42 +02:00
|
|
|
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;
|
2017-03-08 16:39:37 +01:00
|
|
|
case T_TableFuncScan:
|
|
|
|
Assert(rte->rtekind == RTE_TABLEFUNC);
|
|
|
|
objectname = "xmltable";
|
|
|
|
objecttag = "Table Function Name";
|
|
|
|
break;
|
2009-07-24 23:08:42 +02:00
|
|
|
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;
|
2017-04-01 06:17:18 +02:00
|
|
|
case T_NamedTuplestoreScan:
|
|
|
|
Assert(rte->rtekind == RTE_NAMEDTUPLESTORE);
|
|
|
|
objectname = rte->enrname;
|
|
|
|
objecttag = "Tuplestore Name";
|
|
|
|
break;
|
2009-07-24 23:08:42 +02:00
|
|
|
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
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
*
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
* We have three objectives here. First, if there's more than one target
|
|
|
|
* table or it's different from the nominal target, identify the actual
|
|
|
|
* target(s). Second, give FDWs a chance to display extra info about foreign
|
|
|
|
* targets. Third, show information about ON CONFLICT.
|
2013-03-10 19:14:53 +01:00
|
|
|
*/
|
|
|
|
static void
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
|
|
|
|
ExplainState *es)
|
2013-03-10 19:14:53 +01:00
|
|
|
{
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
|
|
|
|
const char *operation;
|
|
|
|
const char *foperation;
|
|
|
|
bool labeltargets;
|
|
|
|
int j;
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
List *idxNames = NIL;
|
|
|
|
ListCell *lst;
|
2013-03-10 19:14:53 +01:00
|
|
|
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
switch (node->operation)
|
2013-03-10 19:14:53 +01:00
|
|
|
{
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
case CMD_INSERT:
|
|
|
|
operation = "Insert";
|
|
|
|
foperation = "Foreign Insert";
|
|
|
|
break;
|
|
|
|
case CMD_UPDATE:
|
|
|
|
operation = "Update";
|
|
|
|
foperation = "Foreign Update";
|
|
|
|
break;
|
|
|
|
case CMD_DELETE:
|
|
|
|
operation = "Delete";
|
|
|
|
foperation = "Foreign Delete";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
operation = "???";
|
|
|
|
foperation = "Foreign ???";
|
|
|
|
break;
|
2013-03-10 19:14:53 +01:00
|
|
|
}
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
|
|
|
|
/* Should we explicitly label target relations? */
|
|
|
|
labeltargets = (mtstate->mt_nplans > 1 ||
|
|
|
|
(mtstate->mt_nplans == 1 &&
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02:00
|
|
|
mtstate->resultRelInfo->ri_RangeTableIndex != node->nominalRelation));
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
|
|
|
|
if (labeltargets)
|
|
|
|
ExplainOpenGroup("Target Tables", "Target Tables", false, es);
|
|
|
|
|
|
|
|
for (j = 0; j < mtstate->mt_nplans; j++)
|
|
|
|
{
|
|
|
|
ResultRelInfo *resultRelInfo = mtstate->resultRelInfo + j;
|
|
|
|
FdwRoutine *fdwroutine = resultRelInfo->ri_FdwRoutine;
|
|
|
|
|
|
|
|
if (labeltargets)
|
|
|
|
{
|
|
|
|
/* Open a group for this target */
|
|
|
|
ExplainOpenGroup("Target Table", NULL, true, es);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* In text mode, decorate each target with operation type, so that
|
|
|
|
* ExplainTargetRel's output of " on foo" will read nicely.
|
|
|
|
*/
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfoString(es->str,
|
|
|
|
fdwroutine ? foperation : operation);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Identify target */
|
|
|
|
ExplainTargetRel((Plan *) node,
|
|
|
|
resultRelInfo->ri_RangeTableIndex,
|
|
|
|
es);
|
|
|
|
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
{
|
|
|
|
appendStringInfoChar(es->str, '\n');
|
|
|
|
es->indent++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-18 18:48:58 +01:00
|
|
|
/* Give FDW a chance if needed */
|
|
|
|
if (!resultRelInfo->ri_usesFdwDirectModify &&
|
|
|
|
fdwroutine != NULL &&
|
|
|
|
fdwroutine->ExplainForeignModify != NULL)
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
{
|
|
|
|
List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
|
|
|
|
|
|
|
|
fdwroutine->ExplainForeignModify(mtstate,
|
|
|
|
resultRelInfo,
|
|
|
|
fdw_private,
|
|
|
|
j,
|
|
|
|
es);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (labeltargets)
|
|
|
|
{
|
|
|
|
/* Undo the indentation we added in text format */
|
|
|
|
if (es->format == EXPLAIN_FORMAT_TEXT)
|
|
|
|
es->indent--;
|
|
|
|
|
|
|
|
/* Close the group */
|
|
|
|
ExplainCloseGroup("Target Table", NULL, true, es);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
/* Gather names of ON CONFLICT arbiter indexes */
|
|
|
|
foreach(lst, node->arbiterIndexes)
|
|
|
|
{
|
|
|
|
char *indexname = get_rel_name(lfirst_oid(lst));
|
|
|
|
|
|
|
|
idxNames = lappend(idxNames, indexname);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node->onConflictAction != ONCONFLICT_NONE)
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyText("Conflict Resolution",
|
|
|
|
node->onConflictAction == ONCONFLICT_NOTHING ?
|
|
|
|
"NOTHING" : "UPDATE",
|
|
|
|
es);
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't display arbiter indexes at all when DO NOTHING variant
|
|
|
|
* implicitly ignores all conflicts
|
|
|
|
*/
|
|
|
|
if (idxNames)
|
|
|
|
ExplainPropertyList("Conflict Arbiter Indexes", idxNames, es);
|
|
|
|
|
|
|
|
/* ON CONFLICT DO UPDATE WHERE qual is specially displayed */
|
|
|
|
if (node->onConflictWhere)
|
|
|
|
{
|
|
|
|
show_upper_qual((List *) node->onConflictWhere, "Conflict Filter",
|
|
|
|
&mtstate->ps, ancestors, es);
|
|
|
|
show_instrumentation_count("Rows Removed by Conflict Filter", 1, &mtstate->ps, es);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* EXPLAIN ANALYZE display of actual outcome for each tuple proposed */
|
|
|
|
if (es->analyze && mtstate->ps.instrument)
|
|
|
|
{
|
2015-05-24 03:35:49 +02:00
|
|
|
double total;
|
|
|
|
double insert_path;
|
|
|
|
double other_path;
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
|
|
|
|
InstrEndLoop(mtstate->mt_plans[0]->instrument);
|
|
|
|
|
|
|
|
/* count the number of source rows */
|
|
|
|
total = mtstate->mt_plans[0]->instrument->ntuples;
|
2018-04-10 20:56:15 +02:00
|
|
|
other_path = mtstate->ps.instrument->ntuples2;
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
insert_path = total - other_path;
|
|
|
|
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat("Tuples Inserted", NULL,
|
|
|
|
insert_path, 0, es);
|
|
|
|
ExplainPropertyFloat("Conflicting Tuples", NULL,
|
|
|
|
other_path, 0, es);
|
Add support for INSERT ... ON CONFLICT DO NOTHING/UPDATE.
The newly added ON CONFLICT clause allows to specify an alternative to
raising a unique or exclusion constraint violation error when inserting.
ON CONFLICT refers to constraints that can either be specified using a
inference clause (by specifying the columns of a unique constraint) or
by naming a unique or exclusion constraint. DO NOTHING avoids the
constraint violation, without touching the pre-existing row. DO UPDATE
SET ... [WHERE ...] updates the pre-existing tuple, and has access to
both the tuple proposed for insertion and the existing tuple; the
optional WHERE clause can be used to prevent an update from being
executed. The UPDATE SET and WHERE clauses have access to the tuple
proposed for insertion using the "magic" EXCLUDED alias, and to the
pre-existing tuple using the table name or its alias.
This feature is often referred to as upsert.
This is implemented using a new infrastructure called "speculative
insertion". It is an optimistic variant of regular insertion that first
does a pre-check for existing tuples and then attempts an insert. If a
violating tuple was inserted concurrently, the speculatively inserted
tuple is deleted and a new attempt is made. If the pre-check finds a
matching tuple the alternative DO NOTHING or DO UPDATE action is taken.
If the insertion succeeds without detecting a conflict, the tuple is
deemed inserted.
To handle the possible ambiguity between the excluded alias and a table
named excluded, and for convenience with long relation names, INSERT
INTO now can alias its target table.
Bumps catversion as stored rules change.
Author: Peter Geoghegan, with significant contributions from Heikki
Linnakangas and Andres Freund. Testing infrastructure by Jeff Janes.
Reviewed-By: Heikki Linnakangas, Andres Freund, Robert Haas, Simon Riggs,
Dean Rasheed, Stephen Frost and many others.
2015-05-08 05:31:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Allow foreign tables to participate in inheritance.
Foreign tables can now be inheritance children, or parents. Much of the
system was already ready for this, but we had to fix a few things of
course, mostly in the area of planner and executor handling of row locks.
As side effects of this, allow foreign tables to have NOT VALID CHECK
constraints (and hence to accept ALTER ... VALIDATE CONSTRAINT), and to
accept ALTER SET STORAGE and ALTER SET WITH/WITHOUT OIDS. Continuing to
disallow these things would've required bizarre and inconsistent special
cases in inheritance behavior. Since foreign tables don't enforce CHECK
constraints anyway, a NOT VALID one is a complete no-op, but that doesn't
mean we shouldn't allow it. And it's possible that some FDWs might have
use for SET STORAGE or SET WITH OIDS, though doubtless they will be no-ops
for most.
An additional change in support of this is that when a ModifyTable node
has multiple target tables, they will all now be explicitly identified
in EXPLAIN output, for example:
Update on pt1 (cost=0.00..321.05 rows=3541 width=46)
Update on pt1
Foreign Update on ft1
Foreign Update on ft2
Update on child3
-> Seq Scan on pt1 (cost=0.00..0.00 rows=1 width=46)
-> Foreign Scan on ft1 (cost=100.00..148.03 rows=1170 width=46)
-> Foreign Scan on ft2 (cost=100.00..148.03 rows=1170 width=46)
-> Seq Scan on child3 (cost=0.00..25.00 rows=1200 width=46)
This was done mainly to provide an unambiguous place to attach "Remote SQL"
fields, but it is useful for inherited updates even when no foreign tables
are involved.
Shigeru Hanada and Etsuro Fujita, reviewed by Ashutosh Bapat and Kyotaro
Horiguchi, some additional hacking by me
2015-03-22 18:53:11 +01:00
|
|
|
if (labeltargets)
|
|
|
|
ExplainCloseGroup("Target Tables", "Target Tables", false, es);
|
2013-03-10 19:14:53 +01:00
|
|
|
}
|
|
|
|
|
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.
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
*
|
|
|
|
* nsubnodes indicates the number of items in the planstates array.
|
|
|
|
* nplans indicates the original number of subnodes in the Plan, some of these
|
|
|
|
* may have been pruned by the run-time pruning code.
|
2009-07-24 23:08:42 +02:00
|
|
|
*/
|
|
|
|
static void
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
ExplainMemberNodes(PlanState **planstates, int nsubnodes, int nplans,
|
2010-07-13 22:57:19 +02:00
|
|
|
List *ancestors, ExplainState *es)
|
2009-07-24 23:08:42 +02:00
|
|
|
{
|
2010-07-13 22:57:19 +02:00
|
|
|
int j;
|
2009-07-24 23:08:42 +02:00
|
|
|
|
Support partition pruning at execution time
Existing partition pruning is only able to work at plan time, for query
quals that appear in the parsed query. This is good but limiting, as
there can be parameters that appear later that can be usefully used to
further prune partitions.
This commit adds support for pruning subnodes of Append which cannot
possibly contain any matching tuples, during execution, by evaluating
Params to determine the minimum set of subnodes that can possibly match.
We support more than just simple Params in WHERE clauses. Support
additionally includes:
1. Parameterized Nested Loop Joins: The parameter from the outer side of the
join can be used to determine the minimum set of inner side partitions to
scan.
2. Initplans: Once an initplan has been executed we can then determine which
partitions match the value from the initplan.
Partition pruning is performed in two ways. When Params external to the plan
are found to match the partition key we attempt to prune away unneeded Append
subplans during the initialization of the executor. This allows us to bypass
the initialization of non-matching subplans meaning they won't appear in the
EXPLAIN or EXPLAIN ANALYZE output.
For parameters whose value is only known during the actual execution
then the pruning of these subplans must wait. Subplans which are
eliminated during this stage of pruning are still visible in the EXPLAIN
output. In order to determine if pruning has actually taken place, the
EXPLAIN ANALYZE must be viewed. If a certain Append subplan was never
executed due to the elimination of the partition then the execution
timing area will state "(never executed)". Whereas, if, for example in
the case of parameterized nested loops, the number of loops stated in
the EXPLAIN ANALYZE output for certain subplans may appear lower than
others due to the subplan having been scanned fewer times. This is due
to the list of matching subnodes having to be evaluated whenever a
parameter which was found to match the partition key changes.
This commit required some additional infrastructure that permits the
building of a data structure which is able to perform the translation of
the matching partition IDs, as returned by get_matching_partitions, into
the list index of a subpaths list, as exist in node types such as
Append, MergeAppend and ModifyTable. This allows us to translate a list
of clauses into a Bitmapset of all the subpath indexes which must be
included to satisfy the clause list.
Author: David Rowley, based on an earlier effort by Beena Emerson
Reviewers: Amit Langote, Robert Haas, Amul Sul, Rajkumar Raghuwanshi,
Jesper Pedersen
Discussion: https://postgr.es/m/CAOG9ApE16ac-_VVZVvv0gePSgkg_BwYEV1NBqZFqDR2bBE0X0A@mail.gmail.com
2018-04-07 22:54:31 +02:00
|
|
|
/*
|
|
|
|
* The number of subnodes being lower than the number of subplans that was
|
|
|
|
* specified in the plan means that some subnodes have been ignored per
|
|
|
|
* instruction for the partition pruning code during the executor
|
|
|
|
* initialization. To make this a bit less mysterious, we'll indicate
|
|
|
|
* here that this has happened.
|
|
|
|
*/
|
|
|
|
if (nsubnodes < nplans)
|
|
|
|
ExplainPropertyInteger("Subplans Removed", NULL, nplans - nsubnodes, es);
|
|
|
|
|
|
|
|
for (j = 0; j < nsubnodes; j++)
|
2010-07-13 22:57:19 +02:00
|
|
|
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);
|
Faster expression evaluation and targetlist projection.
This replaces the old, recursive tree-walk based evaluation, with
non-recursive, opcode dispatch based, expression evaluation.
Projection is now implemented as part of expression evaluation.
This both leads to significant performance improvements, and makes
future just-in-time compilation of expressions easier.
The speed gains primarily come from:
- non-recursive implementation reduces stack usage / overhead
- simple sub-expressions are implemented with a single jump, without
function calls
- sharing some state between different sub-expressions
- reduced amount of indirect/hard to predict memory accesses by laying
out operation metadata sequentially; including the avoidance of
nearly all of the previously used linked lists
- more code has been moved to expression initialization, avoiding
constant re-checks at evaluation time
Future just-in-time compilation (JIT) has become easier, as
demonstrated by released patches intended to be merged in a later
release, for primarily two reasons: Firstly, due to a stricter split
between expression initialization and evaluation, less code has to be
handled by the JIT. Secondly, due to the non-recursive nature of the
generated "instructions", less performance-critical code-paths can
easily be shared between interpreted and compiled evaluation.
The new framework allows for significant future optimizations. E.g.:
- basic infrastructure for to later reduce the per executor-startup
overhead of expression evaluation, by caching state in prepared
statements. That'd be helpful in OLTPish scenarios where
initialization overhead is measurable.
- optimizing the generated "code". A number of proposals for potential
work has already been made.
- optimizing the interpreter. Similarly a number of proposals have
been made here too.
The move of logic into the expression initialization step leads to some
backward-incompatible changes:
- Function permission checks are now done during expression
initialization, whereas previously they were done during
execution. In edge cases this can lead to errors being raised that
previously wouldn't have been, e.g. a NULL array being coerced to a
different array type previously didn't perform checks.
- The set of domain constraints to be checked, is now evaluated once
during expression initialization, previously it was re-built
every time a domain check was evaluated. For normal queries this
doesn't change much, but e.g. for plpgsql functions, which caches
ExprStates, the old set could stick around longer. The behavior
around might still change.
Author: Andres Freund, with significant changes by Tom Lane,
changes by Heikki Linnakangas
Reviewed-By: Tom Lane, Heikki Linnakangas
Discussion: https://postgr.es/m/20161206034955.bh33paeralxbtluv@alap3.anarazel.de
2017-03-14 23:45:36 +01:00
|
|
|
SubPlan *sp = sps->subplan;
|
2009-07-24 23:08:42 +02:00
|
|
|
|
Print a given subplan only once in EXPLAIN.
We have, for a very long time, allowed the same subplan (same member of the
PlannedStmt.subplans list) to be referenced by more than one SubPlan node;
this avoids problems for cases such as subplans within an IndexScan's
indxqual and indxqualorig fields. However, EXPLAIN had not gotten the memo
and would print each reference as though it were an independent identical
subplan. To fix, track plan_ids of subplans we've printed and don't print
the same plan_id twice. Per report from Pavel Stehule.
BTW: the particular case of IndexScan didn't cause visible duplication
in a plain EXPLAIN, only EXPLAIN ANALYZE, because in the former case we
short-circuit executor startup before the indxqual field is processed by
ExecInitExpr. That seems like it could easily lead to other EXPLAIN
problems in future, but it's not clear how to avoid it without breaking
the "EXPLAIN a plan using hypothetical indexes" use-case. For now I've
left that issue alone.
Although this is a longstanding bug, it's purely cosmetic (no great harm
is done by the repeat printout) and we haven't had field complaints before.
So I'm hesitant to back-patch it, especially since there is some small risk
of ABI problems due to the need to add a new field to ExplainState.
In passing, rearrange order of fields in ExplainState to be less random,
and update some obsolete comments about when/where to initialize them.
Report: <CAFj8pRAimq+NK-menjt+3J4-LFoodDD8Or6=Lc_stcFD+eD4DA@mail.gmail.com>
2016-07-12 00:14:29 +02:00
|
|
|
/*
|
|
|
|
* There can be multiple SubPlan nodes referencing the same physical
|
|
|
|
* subplan (same plan_id, which is its index in PlannedStmt.subplans).
|
|
|
|
* We should print a subplan only once, so track which ones we already
|
|
|
|
* printed. This state must be global across the plan tree, since the
|
|
|
|
* duplicate nodes could be in different plan nodes, eg both a bitmap
|
|
|
|
* indexscan's indexqual and its parent heapscan's recheck qual. (We
|
|
|
|
* do not worry too much about which plan node we show the subplan as
|
|
|
|
* attached to in such cases.)
|
|
|
|
*/
|
|
|
|
if (bms_is_member(sp->plan_id, es->printed_subplans))
|
|
|
|
continue;
|
|
|
|
es->printed_subplans = bms_add_member(es->printed_subplans,
|
|
|
|
sp->plan_id);
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-26 15:40:47 +02:00
|
|
|
/*
|
|
|
|
* Explain a list of children of a CustomScan.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainCustomChildren(CustomScanState *css, List *ancestors, ExplainState *es)
|
|
|
|
{
|
|
|
|
ListCell *cell;
|
|
|
|
const char *label =
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
(list_length(css->custom_ps) != 1 ? "children" : "child");
|
2015-06-26 15:40:47 +02:00
|
|
|
|
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method
API on index access methods, which wasn't a good choice because, without
specialized DDL commands, there's no way to build an extension that can
implement a TSM. (Raw inserts into system catalogs are not an acceptable
thing to do, because we can't undo them during DROP EXTENSION, nor will
pg_upgrade behave sanely.) Instead adopt an API more like procedural
language handlers or foreign data wrappers, wherein the only SQL-level
support object needed is a single handler function identified by having
a special return type. This lets us get rid of the supporting catalog
altogether, so that no custom DDL support is needed for the feature.
Adjust the API so that it can support non-constant tablesample arguments
(the original coding assumed we could evaluate the argument expressions at
ExecInitSampleScan time, which is undesirable even if it weren't outright
unsafe), and discourage sampling methods from looking at invisible tuples.
Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable
within and across queries, as required by the SQL standard, and deal more
honestly with methods that can't support that requirement.
Make a full code-review pass over the tablesample additions, and fix
assorted bugs, omissions, infelicities, and cosmetic issues (such as
failure to put the added code stanzas in a consistent ordering).
Improve EXPLAIN's output of tablesample plans, too.
Back-patch to 9.5 so that we don't have to support the original API
in production.
2015-07-25 20:39:00 +02:00
|
|
|
foreach(cell, css->custom_ps)
|
2015-06-26 15:40:47 +02:00
|
|
|
ExplainNode((PlanState *) lfirst(cell), ancestors, label, NULL, 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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Support GROUPING SETS, CUBE and ROLLUP.
This SQL standard functionality allows to aggregate data by different
GROUP BY clauses at once. Each grouping set returns rows with columns
grouped by in other sets set to NULL.
This could previously be achieved by doing each grouping as a separate
query, conjoined by UNION ALLs. Besides being considerably more concise,
grouping sets will in many cases be faster, requiring only one scan over
the underlying data.
The current implementation of grouping sets only supports using sorting
for input. Individual sets that share a sort order are computed in one
pass. If there are sets that don't share a sort order, additional sort &
aggregation steps are performed. These additional passes are sourced by
the previous sort step; thus avoiding repeated scans of the source data.
The code is structured in a way that adding support for purely using
hash aggregation or a mix of hashing and sorting is possible. Sorting
was chosen to be supported first, as it is the most generic method of
implementation.
Instead of, as in an earlier versions of the patch, representing the
chain of sort and aggregation steps as full blown planner and executor
nodes, all but the first sort are performed inside the aggregation node
itself. This avoids the need to do some unusual gymnastics to handle
having to return aggregated and non-aggregated tuples from underlying
nodes, as well as having to shut down underlying nodes early to limit
memory usage. The optimizer still builds Sort/Agg node to describe each
phase, but they're not part of the plan tree, but instead additional
data for the aggregation node. They're a convenient and preexisting way
to describe aggregation and sorting. The first (and possibly only) sort
step is still performed as a separate execution step. That retains
similarity with existing group by plans, makes rescans fairly simple,
avoids very deep plans (leading to slow explains) and easily allows to
avoid the sorting step if the underlying data is sorted by other means.
A somewhat ugly side of this patch is having to deal with a grammar
ambiguity between the new CUBE keyword and the cube extension/functions
named cube (and rollup). To avoid breaking existing deployments of the
cube extension it has not been renamed, neither has cube been made a
reserved keyword. Instead precedence hacking is used to make GROUP BY
cube(..) refer to the CUBE grouping sets feature, and not the function
cube(). To actually group by a function cube(), unlikely as that might
be, the function name has to be quoted.
Needs a catversion bump because stored rules may change.
Author: Andrew Gierth and Atri Sharma, with contributions from Andres Freund
Reviewed-By: Andres Freund, Noah Misch, Tom Lane, Svenne Krap, Tomas
Vondra, Erik Rijkers, Marti Raudsepp, Pavel Stehule
Discussion: CAOeZVidmVRe2jU6aMk_5qkxnB7dfmPROzM7Ur8JPW5j8Y5X-Lw@mail.gmail.com
2015-05-16 03:40:59 +02:00
|
|
|
/*
|
|
|
|
* Explain a property that takes the form of a list of unlabeled items within
|
|
|
|
* another list. "data" is a list of C strings.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ExplainPropertyListNested(const char *qlabel, List *data, ExplainState *es)
|
|
|
|
{
|
|
|
|
ListCell *lc;
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
case EXPLAIN_FORMAT_XML:
|
|
|
|
ExplainPropertyList(qlabel, data, es);
|
|
|
|
return;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_JSON:
|
|
|
|
ExplainJSONLineEnding(es);
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
|
|
|
appendStringInfoChar(es->str, '[');
|
|
|
|
foreach(lc, data)
|
|
|
|
{
|
|
|
|
if (!first)
|
|
|
|
appendStringInfoString(es->str, ", ");
|
|
|
|
escape_json(es->str, (const char *) lfirst(lc));
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
appendStringInfoChar(es->str, ']');
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EXPLAIN_FORMAT_YAML:
|
|
|
|
ExplainYAMLLineStarting(es);
|
|
|
|
appendStringInfoString(es->str, "- [");
|
|
|
|
foreach(lc, data)
|
|
|
|
{
|
|
|
|
if (!first)
|
|
|
|
appendStringInfoString(es->str, ", ");
|
|
|
|
escape_yaml(es->str, (const char *) lfirst(lc));
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
appendStringInfoChar(es->str, ']');
|
|
|
|
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).
|
|
|
|
*
|
2018-03-17 07:13:12 +01:00
|
|
|
* If unit is is non-NULL the text format will display it after the value.
|
|
|
|
*
|
2009-08-10 07:46:50 +02:00
|
|
|
* This usually should not be invoked directly, but via one of the datatype
|
|
|
|
* specific routines ExplainPropertyText, ExplainPropertyInteger, etc.
|
|
|
|
*/
|
|
|
|
static void
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainProperty(const char *qlabel, const char *unit, const char *value,
|
|
|
|
bool numeric, ExplainState *es)
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
|
|
|
switch (es->format)
|
|
|
|
{
|
|
|
|
case EXPLAIN_FORMAT_TEXT:
|
|
|
|
appendStringInfoSpaces(es->str, es->indent * 2);
|
2018-03-17 07:13:12 +01:00
|
|
|
if (unit)
|
|
|
|
appendStringInfo(es->str, "%s: %s %s\n", qlabel, value, unit);
|
|
|
|
else
|
|
|
|
appendStringInfo(es->str, "%s: %s\n", qlabel, value);
|
2009-08-10 07:46:50 +02:00
|
|
|
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)
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainProperty(qlabel, NULL, value, false, es);
|
2011-02-20 06:17:18 +01:00
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/*
|
|
|
|
* Explain an integer-valued property.
|
|
|
|
*/
|
2011-02-20 06:17:18 +01:00
|
|
|
void
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyInteger(const char *qlabel, const char *unit, int64 value,
|
|
|
|
ExplainState *es)
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
2010-02-26 03:01:40 +01:00
|
|
|
char buf[32];
|
2009-08-10 07:46:50 +02:00
|
|
|
|
2018-03-17 07:13:12 +01:00
|
|
|
snprintf(buf, sizeof(buf), INT64_FORMAT, value);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainProperty(qlabel, unit, buf, true, es);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Explain a float-valued property, using the specified number of
|
|
|
|
* fractional digits.
|
|
|
|
*/
|
2011-02-20 06:17:18 +01:00
|
|
|
void
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainPropertyFloat(const char *qlabel, const char *unit, double value,
|
|
|
|
int ndigits, ExplainState *es)
|
2009-08-10 07:46:50 +02:00
|
|
|
{
|
2018-03-15 16:10:41 +01:00
|
|
|
char *buf;
|
2009-08-10 07:46:50 +02:00
|
|
|
|
2018-03-15 16:10:41 +01:00
|
|
|
buf = psprintf("%.*f", ndigits, value);
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainProperty(qlabel, unit, buf, true, es);
|
2018-03-15 16:10:41 +01:00
|
|
|
pfree(buf);
|
2009-08-10 07:46:50 +02:00
|
|
|
}
|
|
|
|
|
Fix some infelicities in EXPLAIN output for parallel query plans.
In non-text output formats, parallelized aggregates were reporting
"Partial" or "Finalize" as a field named "Operation", which might be all
right in the absence of any context --- but other plan node types use that
field to report SQL-visible semantics, such as Select/Insert/Update/Delete.
So that naming choice didn't seem good to me. I changed it to "Partial
Mode".
Also, the field did not appear at all for a non-parallelized Agg plan node,
which is contrary to expectation in non-text formats. We're notionally
producing objects that conform to a schema, so the set of fields for a
given node type and EXPLAIN mode should be well-defined. I set it up to
fill in "Simple" in such cases.
Other fields that were added for parallel query, namely "Parallel Aware"
and Gather's "Single Copy", had not gotten the word on that point either.
Make them appear always in non-text output.
Also, the latter two fields were nominally producing boolean output, but
were getting it wrong, because bool values shouldn't be quoted in JSON or
YAML. Somehow we'd not needed an ExplainPropertyBool formatting subroutine
before 9.6; but now we do, so invent it.
Discussion: <16002.1466972724@sss.pgh.pa.us>
2016-06-30 00:51:20 +02:00
|
|
|
/*
|
|
|
|
* Explain a bool-valued property.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
ExplainPropertyBool(const char *qlabel, bool value, ExplainState *es)
|
|
|
|
{
|
2018-03-17 07:13:12 +01:00
|
|
|
ExplainProperty(qlabel, NULL, value ? "true" : "false", true, es);
|
Fix some infelicities in EXPLAIN output for parallel query plans.
In non-text output formats, parallelized aggregates were reporting
"Partial" or "Finalize" as a field named "Operation", which might be all
right in the absence of any context --- but other plan node types use that
field to report SQL-visible semantics, such as Select/Insert/Update/Delete.
So that naming choice didn't seem good to me. I changed it to "Partial
Mode".
Also, the field did not appear at all for a non-parallelized Agg plan node,
which is contrary to expectation in non-text formats. We're notionally
producing objects that conform to a schema, so the set of fields for a
given node type and EXPLAIN mode should be well-defined. I set it up to
fill in "Simple" in such cases.
Other fields that were added for parallel query, namely "Parallel Aware"
and Gather's "Single Copy", had not gotten the word on that point either.
Make them appear always in non-text output.
Also, the latter two fields were nominally producing boolean output, but
were getting it wrong, because bool values shouldn't be quoted in JSON or
YAML. Somehow we'd not needed an ExplainPropertyBool formatting subroutine
before 9.6; but now we do, so invent it.
Discussion: <16002.1466972724@sss.pgh.pa.us>
2016-06-30 00:51:20 +02:00
|
|
|
}
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2017-09-18 22:01:16 +02:00
|
|
|
void
|
2009-08-10 07:46:50 +02:00
|
|
|
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.
|
|
|
|
*/
|
2017-09-18 22:01:16 +02:00
|
|
|
void
|
2009-08-10 07:46:50 +02:00
|
|
|
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,
|
Phase 3 of pgindent updates.
Don't move parenthesized lines to the left, even if that means they
flow past the right margin.
By default, BSD indent lines up statement continuation lines that are
within parentheses so that they start just to the right of the preceding
left parenthesis. However, traditionally, if that resulted in the
continuation line extending to the right of the desired right margin,
then indent would push it left just far enough to not overrun the margin,
if it could do so without making the continuation line start to the left of
the current statement indent. That makes for a weird mix of indentations
unless one has been completely rigid about never violating the 80-column
limit.
This behavior has been pretty universally panned by Postgres developers.
Hence, disable it with indent's new -lpl switch, so that parenthesized
lines are always lined up with the preceding left paren.
This patch is much less interesting than the first round of indent
changes, but also bulkier, so I thought it best to separate the effects.
Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org
Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:35:54 +02: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.
|
|
|
|
*
|
Fix EXPLAIN so that it doesn't emit invalid XML in corner cases.
With track_io_timing = on, EXPLAIN (ANALYZE, BUFFERS) will emit fields
named like "I/O Read Time". The slash makes that invalid as an XML
element name, so that adding FORMAT XML would produce invalid XML.
We already have code in there to translate spaces to dashes, so let's
generalize that to convert anything that isn't a valid XML name character,
viz letters, digits, hyphens, underscores, and periods. We could just
reject slashes, which would run a bit faster. But the fact that this went
unnoticed for so long doesn't give me a warm feeling that we'd notice the
next creative violation, so let's make it a permanent fix.
Reported by Markus Winand, though this isn't his initial patch proposal.
Back-patch to 9.2 where track_io_timing was added. The problem is only
latent in 9.1, so I don't feel a need to fix it there.
Discussion: <E0BF6A45-68E8-45E6-918F-741FB332C6BB@winand.at>
2016-10-20 23:17:50 +02:00
|
|
|
* XML restricts tag names more than our other output formats, eg they can't
|
|
|
|
* contain white space or slashes. Replace invalid characters with dashes,
|
|
|
|
* so that for example "I/O Read Time" becomes "I-O-Read-Time".
|
2009-08-10 07:46:50 +02:00
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ExplainXMLTag(const char *tagname, int flags, ExplainState *es)
|
|
|
|
{
|
|
|
|
const char *s;
|
Fix EXPLAIN so that it doesn't emit invalid XML in corner cases.
With track_io_timing = on, EXPLAIN (ANALYZE, BUFFERS) will emit fields
named like "I/O Read Time". The slash makes that invalid as an XML
element name, so that adding FORMAT XML would produce invalid XML.
We already have code in there to translate spaces to dashes, so let's
generalize that to convert anything that isn't a valid XML name character,
viz letters, digits, hyphens, underscores, and periods. We could just
reject slashes, which would run a bit faster. But the fact that this went
unnoticed for so long doesn't give me a warm feeling that we'd notice the
next creative violation, so let's make it a permanent fix.
Reported by Markus Winand, though this isn't his initial patch proposal.
Back-patch to 9.2 where track_io_timing was added. The problem is only
latent in 9.1, so I don't feel a need to fix it there.
Discussion: <E0BF6A45-68E8-45E6-918F-741FB332C6BB@winand.at>
2016-10-20 23:17:50 +02:00
|
|
|
const char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.";
|
2009-08-10 07:46:50 +02:00
|
|
|
|
|
|
|
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++)
|
Fix EXPLAIN so that it doesn't emit invalid XML in corner cases.
With track_io_timing = on, EXPLAIN (ANALYZE, BUFFERS) will emit fields
named like "I/O Read Time". The slash makes that invalid as an XML
element name, so that adding FORMAT XML would produce invalid XML.
We already have code in there to translate spaces to dashes, so let's
generalize that to convert anything that isn't a valid XML name character,
viz letters, digits, hyphens, underscores, and periods. We could just
reject slashes, which would run a bit faster. But the fact that this went
unnoticed for so long doesn't give me a warm feeling that we'd notice the
next creative violation, so let's make it a permanent fix.
Reported by Markus Winand, though this isn't his initial patch proposal.
Back-patch to 9.2 where track_io_timing was added. The problem is only
latent in 9.1, so I don't feel a need to fix it there.
Discussion: <E0BF6A45-68E8-45E6-918F-741FB332C6BB@winand.at>
2016-10-20 23:17:50 +02:00
|
|
|
appendStringInfoChar(es->str, strchr(valid, *s) ? *s : '-');
|
2009-08-10 07:46:50 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2017-02-06 10:33:58 +01:00
|
|
|
* YAML is a superset of JSON; unfortunately, the YAML quoting rules are
|
2010-06-10 03:26:30 +02:00
|
|
|
* 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
|
|
|
}
|