|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* setrefs.c
|
|
|
|
* Post-processing of a completed plan tree: fix references to subplan
|
|
|
|
* vars, compute regproc values for operators, etc
|
|
|
|
*
|
|
|
|
* Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
|
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
|
|
|
* src/backend/optimizer/plan/setrefs.c
|
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
|
|
|
#include "access/transam.h"
|
|
|
|
#include "catalog/pg_type.h"
|
|
|
|
#include "nodes/makefuncs.h"
|
|
|
|
#include "nodes/nodeFuncs.h"
|
|
|
|
#include "optimizer/pathnode.h"
|
|
|
|
#include "optimizer/planmain.h"
|
|
|
|
#include "optimizer/planner.h"
|
|
|
|
#include "optimizer/tlist.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
14 years ago
|
|
|
#include "tcop/utility.h"
|
|
|
|
#include "utils/lsyscache.h"
|
|
|
|
#include "utils/syscache.h"
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
Index varno; /* RT index of Var */
|
|
|
|
AttrNumber varattno; /* attr number of Var */
|
|
|
|
AttrNumber resno; /* TLE position of Var */
|
|
|
|
} tlist_vinfo;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
List *tlist; /* underlying target list */
|
|
|
|
int num_vars; /* number of plain Var tlist entries */
|
|
|
|
bool has_ph_vars; /* are there PlaceHolderVar entries? */
|
|
|
|
bool has_non_vars; /* are there other entries? */
|
|
|
|
tlist_vinfo vars[FLEXIBLE_ARRAY_MEMBER]; /* has num_vars entries */
|
|
|
|
} indexed_tlist;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
PlannerInfo *root;
|
|
|
|
int rtoffset;
|
|
|
|
} fix_scan_expr_context;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
PlannerInfo *root;
|
|
|
|
indexed_tlist *outer_itlist;
|
|
|
|
indexed_tlist *inner_itlist;
|
|
|
|
Index acceptable_rel;
|
|
|
|
int rtoffset;
|
|
|
|
} fix_join_expr_context;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
PlannerInfo *root;
|
|
|
|
indexed_tlist *subplan_itlist;
|
|
|
|
Index newvarno;
|
|
|
|
int rtoffset;
|
|
|
|
} fix_upper_expr_context;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check if a Const node is a regclass value. We accept plain OID too,
|
|
|
|
* since a regclass Const will get folded to that type if it's an argument
|
|
|
|
* to oideq or similar operators. (This might result in some extraneous
|
|
|
|
* values in a plan's list of relation dependencies, but the worst result
|
|
|
|
* would be occasional useless replans.)
|
|
|
|
*/
|
|
|
|
#define ISREGCLASSCONST(con) \
|
|
|
|
(((con)->consttype == REGCLASSOID || (con)->consttype == OIDOID) && \
|
|
|
|
!(con)->constisnull)
|
|
|
|
|
|
|
|
#define fix_scan_list(root, lst, rtoffset) \
|
|
|
|
((List *) fix_scan_expr(root, (Node *) (lst), rtoffset))
|
|
|
|
|
|
|
|
static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
|
|
|
|
static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
|
|
|
|
static bool flatten_rtes_walker(Node *node, PlannerGlobal *glob);
|
|
|
|
static void add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte);
|
|
|
|
static Plan *set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset);
|
|
|
|
static Plan *set_indexonlyscan_references(PlannerInfo *root,
|
|
|
|
IndexOnlyScan *plan,
|
|
|
|
int rtoffset);
|
|
|
|
static Plan *set_subqueryscan_references(PlannerInfo *root,
|
|
|
|
SubqueryScan *plan,
|
|
|
|
int rtoffset);
|
|
|
|
static bool trivial_subqueryscan(SubqueryScan *plan);
|
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.
10 years ago
|
|
|
static void set_foreignscan_references(PlannerInfo *root,
|
|
|
|
ForeignScan *fscan,
|
|
|
|
int rtoffset);
|
|
|
|
static void set_customscan_references(PlannerInfo *root,
|
|
|
|
CustomScan *cscan,
|
|
|
|
int rtoffset);
|
|
|
|
static Node *fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset);
|
|
|
|
static Node *fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context);
|
|
|
|
static bool fix_scan_expr_walker(Node *node, fix_scan_expr_context *context);
|
|
|
|
static void set_join_references(PlannerInfo *root, Join *join, int rtoffset);
|
|
|
|
static void set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset);
|
|
|
|
static void set_dummy_tlist_references(Plan *plan, int rtoffset);
|
|
|
|
static indexed_tlist *build_tlist_index(List *tlist);
|
|
|
|
static Var *search_indexed_tlist_for_var(Var *var,
|
|
|
|
indexed_tlist *itlist,
|
|
|
|
Index newvarno,
|
|
|
|
int rtoffset);
|
|
|
|
static Var *search_indexed_tlist_for_non_var(Node *node,
|
|
|
|
indexed_tlist *itlist,
|
|
|
|
Index newvarno);
|
|
|
|
static Var *search_indexed_tlist_for_sortgroupref(Node *node,
|
|
|
|
Index sortgroupref,
|
|
|
|
indexed_tlist *itlist,
|
|
|
|
Index newvarno);
|
|
|
|
static List *fix_join_expr(PlannerInfo *root,
|
|
|
|
List *clauses,
|
|
|
|
indexed_tlist *outer_itlist,
|
|
|
|
indexed_tlist *inner_itlist,
|
|
|
|
Index acceptable_rel, int rtoffset);
|
|
|
|
static Node *fix_join_expr_mutator(Node *node,
|
|
|
|
fix_join_expr_context *context);
|
|
|
|
static Node *fix_upper_expr(PlannerInfo *root,
|
|
|
|
Node *node,
|
|
|
|
indexed_tlist *subplan_itlist,
|
|
|
|
Index newvarno,
|
|
|
|
int rtoffset);
|
|
|
|
static Node *fix_upper_expr_mutator(Node *node,
|
|
|
|
fix_upper_expr_context *context);
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
static List *set_returning_clause_references(PlannerInfo *root,
|
|
|
|
List *rlist,
|
|
|
|
Plan *topplan,
|
|
|
|
Index resultRelation,
|
|
|
|
int rtoffset);
|
|
|
|
static bool fix_opfuncids_walker(Node *node, void *context);
|
|
|
|
static bool extract_query_dependencies_walker(Node *node,
|
|
|
|
PlannerInfo *context);
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
*
|
|
|
|
* SUBPLAN REFERENCES
|
|
|
|
*
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_plan_references
|
|
|
|
*
|
|
|
|
* This is the final processing pass of the planner/optimizer. The plan
|
|
|
|
* tree is complete; we just have to adjust some representational details
|
|
|
|
* for the convenience of the executor:
|
|
|
|
*
|
|
|
|
* 1. We flatten the various subquery rangetables into a single list, and
|
|
|
|
* zero out RangeTblEntry fields that are not useful to the executor.
|
|
|
|
*
|
|
|
|
* 2. We adjust Vars in scan nodes to be consistent with the flat rangetable.
|
|
|
|
*
|
|
|
|
* 3. We adjust Vars in upper plan nodes to refer to the outputs of their
|
|
|
|
* subplans.
|
|
|
|
*
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
* 4. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
|
|
|
|
* now that we have finished planning all MULTIEXPR subplans.
|
|
|
|
*
|
|
|
|
* 5. We compute regproc OIDs for operators (ie, we look up the function
|
|
|
|
* that implements each op).
|
|
|
|
*
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
* 6. We create lists of specific objects that the plan depends on.
|
|
|
|
* This will be used by plancache.c to drive invalidation of cached plans.
|
|
|
|
* Relation dependencies are represented by OIDs, and everything else by
|
|
|
|
* PlanInvalItems (this distinction is motivated by the shared-inval APIs).
|
|
|
|
* Currently, relations and user-defined functions are the only types of
|
|
|
|
* objects that are explicitly tracked this way.
|
|
|
|
*
|
|
|
|
* We also perform one final optimization step, which is to delete
|
|
|
|
* SubqueryScan plan nodes that aren't doing anything useful (ie, have
|
|
|
|
* no qual and a no-op targetlist). The reason for doing this last is that
|
|
|
|
* it can't readily be done before set_plan_references, because it would
|
|
|
|
* break set_upper_references: the Vars in the subquery's top tlist
|
|
|
|
* wouldn't match up with the Vars in the outer plan tree. The SubqueryScan
|
|
|
|
* serves a necessary function as a buffer between outer query and subquery
|
|
|
|
* variable numbering ... but after we've flattened the rangetable this is
|
|
|
|
* no longer a problem, since then there's only one rtindex namespace.
|
|
|
|
*
|
|
|
|
* set_plan_references recursively traverses the whole plan tree.
|
|
|
|
*
|
|
|
|
* The return value is normally the same Plan node passed in, but can be
|
|
|
|
* different when the passed-in Plan is a SubqueryScan we decide isn't needed.
|
|
|
|
*
|
|
|
|
* The flattened rangetable entries are appended to root->glob->finalrtable.
|
|
|
|
* Also, rowmarks entries are appended to root->glob->finalrowmarks, and the
|
|
|
|
* RT indexes of ModifyTable result relations to root->glob->resultRelations.
|
|
|
|
* Plan dependencies are appended to root->glob->relationOids (for relations)
|
|
|
|
* and root->glob->invalItems (for everything else).
|
|
|
|
*
|
|
|
|
* Notice that we modify Plan nodes in-place, but use expression_tree_mutator
|
|
|
|
* to process targetlist and qual expressions. We can assume that the Plan
|
|
|
|
* nodes were just built by the planner and are not multiply referenced, but
|
|
|
|
* it's not so safe to assume that for expression tree nodes.
|
|
|
|
*/
|
|
|
|
Plan *
|
|
|
|
set_plan_references(PlannerInfo *root, Plan *plan)
|
|
|
|
{
|
|
|
|
PlannerGlobal *glob = root->glob;
|
|
|
|
int rtoffset = list_length(glob->finalrtable);
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add all the query's RTEs to the flattened rangetable. The live ones
|
|
|
|
* will have their rangetable indexes increased by rtoffset. (Additional
|
|
|
|
* RTEs, not referenced by the Plan tree, might get added after those.)
|
|
|
|
*/
|
|
|
|
add_rtes_to_flat_rtable(root, false);
|
|
|
|
|
|
|
|
/*
|
Re-implement EvalPlanQual processing to improve its performance and eliminate
a lot of strange behaviors that occurred in join cases. We now identify the
"current" row for every joined relation in UPDATE, DELETE, and SELECT FOR
UPDATE/SHARE queries. If an EvalPlanQual recheck is necessary, we jam the
appropriate row into each scan node in the rechecking plan, forcing it to emit
only that one row. The former behavior could rescan the whole of each joined
relation for each recheck, which was terrible for performance, and what's much
worse could result in duplicated output tuples.
Also, the original implementation of EvalPlanQual could not re-use the recheck
execution tree --- it had to go through a full executor init and shutdown for
every row to be tested. To avoid this overhead, I've associated a special
runtime Param with each LockRows or ModifyTable plan node, and arranged to
make every scan node below such a node depend on that Param. Thus, by
signaling a change in that Param, the EPQ machinery can just rescan the
already-built test plan.
This patch also adds a prohibition on set-returning functions in the
targetlist of SELECT FOR UPDATE/SHARE. This is needed to avoid the
duplicate-output-tuple problem. It seems fairly reasonable since the
other restrictions on SELECT FOR UPDATE are meant to ensure that there
is a unique correspondence between source tuples and result tuples,
which an output SRF destroys as much as anything else does.
16 years ago
|
|
|
* Adjust RT indexes of PlanRowMarks and add to final rowmarks list
|
|
|
|
*/
|
|
|
|
foreach(lc, root->rowMarks)
|
|
|
|
{
|
Re-implement EvalPlanQual processing to improve its performance and eliminate
a lot of strange behaviors that occurred in join cases. We now identify the
"current" row for every joined relation in UPDATE, DELETE, and SELECT FOR
UPDATE/SHARE queries. If an EvalPlanQual recheck is necessary, we jam the
appropriate row into each scan node in the rechecking plan, forcing it to emit
only that one row. The former behavior could rescan the whole of each joined
relation for each recheck, which was terrible for performance, and what's much
worse could result in duplicated output tuples.
Also, the original implementation of EvalPlanQual could not re-use the recheck
execution tree --- it had to go through a full executor init and shutdown for
every row to be tested. To avoid this overhead, I've associated a special
runtime Param with each LockRows or ModifyTable plan node, and arranged to
make every scan node below such a node depend on that Param. Thus, by
signaling a change in that Param, the EPQ machinery can just rescan the
already-built test plan.
This patch also adds a prohibition on set-returning functions in the
targetlist of SELECT FOR UPDATE/SHARE. This is needed to avoid the
duplicate-output-tuple problem. It seems fairly reasonable since the
other restrictions on SELECT FOR UPDATE are meant to ensure that there
is a unique correspondence between source tuples and result tuples,
which an output SRF destroys as much as anything else does.
16 years ago
|
|
|
PlanRowMark *rc = (PlanRowMark *) lfirst(lc);
|
|
|
|
PlanRowMark *newrc;
|
|
|
|
|
Re-implement EvalPlanQual processing to improve its performance and eliminate
a lot of strange behaviors that occurred in join cases. We now identify the
"current" row for every joined relation in UPDATE, DELETE, and SELECT FOR
UPDATE/SHARE queries. If an EvalPlanQual recheck is necessary, we jam the
appropriate row into each scan node in the rechecking plan, forcing it to emit
only that one row. The former behavior could rescan the whole of each joined
relation for each recheck, which was terrible for performance, and what's much
worse could result in duplicated output tuples.
Also, the original implementation of EvalPlanQual could not re-use the recheck
execution tree --- it had to go through a full executor init and shutdown for
every row to be tested. To avoid this overhead, I've associated a special
runtime Param with each LockRows or ModifyTable plan node, and arranged to
make every scan node below such a node depend on that Param. Thus, by
signaling a change in that Param, the EPQ machinery can just rescan the
already-built test plan.
This patch also adds a prohibition on set-returning functions in the
targetlist of SELECT FOR UPDATE/SHARE. This is needed to avoid the
duplicate-output-tuple problem. It seems fairly reasonable since the
other restrictions on SELECT FOR UPDATE are meant to ensure that there
is a unique correspondence between source tuples and result tuples,
which an output SRF destroys as much as anything else does.
16 years ago
|
|
|
Assert(IsA(rc, PlanRowMark));
|
|
|
|
|
|
|
|
/* flat copy is enough since all fields are scalars */
|
|
|
|
newrc = (PlanRowMark *) palloc(sizeof(PlanRowMark));
|
|
|
|
memcpy(newrc, rc, sizeof(PlanRowMark));
|
|
|
|
|
|
|
|
/* adjust indexes ... but *not* the rowmarkId */
|
|
|
|
newrc->rti += rtoffset;
|
|
|
|
newrc->prti += rtoffset;
|
|
|
|
|
|
|
|
glob->finalrowmarks = lappend(glob->finalrowmarks, newrc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now fix the Plan tree */
|
|
|
|
return set_plan_refs(root, plan, rtoffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extract RangeTblEntries from the plan's rangetable, and add to flat rtable
|
|
|
|
*
|
|
|
|
* This can recurse into subquery plans; "recursing" is true if so.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing)
|
|
|
|
{
|
|
|
|
PlannerGlobal *glob = root->glob;
|
|
|
|
Index rti;
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add the query's own RTEs to the flattened rangetable.
|
|
|
|
*
|
|
|
|
* At top level, we must add all RTEs so that their indexes in the
|
|
|
|
* flattened rangetable match up with their original indexes. When
|
|
|
|
* recursing, we only care about extracting relation RTEs.
|
|
|
|
*/
|
|
|
|
foreach(lc, root->parse->rtable)
|
|
|
|
{
|
|
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
|
|
|
|
|
|
|
if (!recursing || rte->rtekind == RTE_RELATION)
|
|
|
|
add_rte_to_flat_rtable(glob, rte);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there are any dead subqueries, they are not referenced in the Plan
|
|
|
|
* tree, so we must add RTEs contained in them to the flattened rtable
|
|
|
|
* separately. (If we failed to do this, the executor would not perform
|
|
|
|
* expected permission checks for tables mentioned in such subqueries.)
|
|
|
|
*
|
|
|
|
* Note: this pass over the rangetable can't be combined with the previous
|
|
|
|
* one, because that would mess up the numbering of the live RTEs in the
|
|
|
|
* flattened rangetable.
|
|
|
|
*/
|
|
|
|
rti = 1;
|
|
|
|
foreach(lc, root->parse->rtable)
|
|
|
|
{
|
|
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We should ignore inheritance-parent RTEs: their contents have been
|
|
|
|
* pulled up into our rangetable already. Also ignore any subquery
|
|
|
|
* RTEs without matching RelOptInfos, as they likewise have been
|
|
|
|
* pulled up.
|
|
|
|
*/
|
|
|
|
if (rte->rtekind == RTE_SUBQUERY && !rte->inh &&
|
|
|
|
rti < root->simple_rel_array_size)
|
|
|
|
{
|
|
|
|
RelOptInfo *rel = root->simple_rel_array[rti];
|
|
|
|
|
|
|
|
if (rel != NULL)
|
|
|
|
{
|
|
|
|
Assert(rel->relid == rti); /* sanity check on array */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The subquery might never have been planned at all, if it
|
|
|
|
* was excluded on the basis of self-contradictory constraints
|
|
|
|
* in our query level. In this case apply
|
|
|
|
* flatten_unplanned_rtes.
|
|
|
|
*
|
|
|
|
* If it was planned but the plan is dummy, we assume that it
|
|
|
|
* has been omitted from our plan tree (see
|
|
|
|
* set_subquery_pathlist), and recurse to pull up its RTEs.
|
|
|
|
*
|
|
|
|
* Otherwise, it should be represented by a SubqueryScan node
|
|
|
|
* somewhere in our plan tree, and we'll pull up its RTEs when
|
|
|
|
* we process that plan node.
|
|
|
|
*
|
|
|
|
* However, if we're recursing, then we should pull up RTEs
|
|
|
|
* whether the subplan is dummy or not, because we've found
|
|
|
|
* that some upper query level is treating this one as dummy,
|
|
|
|
* and so we won't scan this level's plan tree at all.
|
|
|
|
*/
|
|
|
|
if (rel->subplan == NULL)
|
|
|
|
flatten_unplanned_rtes(glob, rte);
|
|
|
|
else if (recursing || is_dummy_plan(rel->subplan))
|
|
|
|
{
|
|
|
|
Assert(rel->subroot != NULL);
|
|
|
|
add_rtes_to_flat_rtable(rel->subroot, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rti++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extract RangeTblEntries from a subquery that was never planned at all
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte)
|
|
|
|
{
|
|
|
|
/* Use query_tree_walker to find all RTEs in the parse tree */
|
|
|
|
(void) query_tree_walker(rte->subquery,
|
|
|
|
flatten_rtes_walker,
|
|
|
|
(void *) glob,
|
|
|
|
QTW_EXAMINE_RTES);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
flatten_rtes_walker(Node *node, PlannerGlobal *glob)
|
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return false;
|
|
|
|
if (IsA(node, RangeTblEntry))
|
|
|
|
{
|
|
|
|
RangeTblEntry *rte = (RangeTblEntry *) node;
|
|
|
|
|
|
|
|
/* As above, we need only save relation RTEs */
|
|
|
|
if (rte->rtekind == RTE_RELATION)
|
|
|
|
add_rte_to_flat_rtable(glob, rte);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (IsA(node, Query))
|
|
|
|
{
|
|
|
|
/* Recurse into subselects */
|
|
|
|
return query_tree_walker((Query *) node,
|
|
|
|
flatten_rtes_walker,
|
|
|
|
(void *) glob,
|
|
|
|
QTW_EXAMINE_RTES);
|
|
|
|
}
|
|
|
|
return expression_tree_walker(node, flatten_rtes_walker,
|
|
|
|
(void *) glob);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Add (a copy of) the given RTE to the final rangetable
|
|
|
|
*
|
|
|
|
* In the flat rangetable, we zero out substructure pointers that are not
|
|
|
|
* needed by the executor; this reduces the storage space and copying cost
|
|
|
|
* for cached plans. We keep only the alias and eref Alias fields, which are
|
|
|
|
* needed by EXPLAIN, and the selectedCols, insertedCols and updatedCols
|
|
|
|
* bitmaps, which are needed for executor-startup permissions checking and for
|
|
|
|
* trigger event checking.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
|
|
|
|
{
|
|
|
|
RangeTblEntry *newrte;
|
|
|
|
|
|
|
|
/* flat copy to duplicate all the scalar fields */
|
|
|
|
newrte = (RangeTblEntry *) palloc(sizeof(RangeTblEntry));
|
|
|
|
memcpy(newrte, rte, sizeof(RangeTblEntry));
|
|
|
|
|
|
|
|
/* zap unneeded sub-structure */
|
|
|
|
newrte->subquery = NULL;
|
|
|
|
newrte->joinaliasvars = NIL;
|
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
12 years ago
|
|
|
newrte->functions = NIL;
|
|
|
|
newrte->values_lists = NIL;
|
|
|
|
newrte->values_collations = NIL;
|
|
|
|
newrte->ctecoltypes = NIL;
|
|
|
|
newrte->ctecoltypmods = NIL;
|
|
|
|
newrte->ctecolcollations = NIL;
|
|
|
|
|
|
|
|
glob->finalrtable = lappend(glob->finalrtable, newrte);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check for RT index overflow; it's very unlikely, but if it did happen,
|
|
|
|
* the executor would get confused by varnos that match the special varno
|
|
|
|
* values.
|
|
|
|
*/
|
|
|
|
if (IS_SPECIAL_VARNO(list_length(glob->finalrtable)))
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
|
|
|
|
errmsg("too many range table entries")));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If it's a plain relation RTE, add the table to relationOids.
|
|
|
|
*
|
|
|
|
* We do this even though the RTE might be unreferenced in the plan tree;
|
|
|
|
* this would correspond to cases such as views that were expanded, child
|
|
|
|
* tables that were eliminated by constraint exclusion, etc. Schema
|
|
|
|
* invalidation on such a rel must still force rebuilding of the plan.
|
|
|
|
*
|
|
|
|
* Note we don't bother to avoid making duplicate list entries. We could,
|
|
|
|
* but it would probably cost more cycles than it would save.
|
|
|
|
*/
|
|
|
|
if (newrte->rtekind == RTE_RELATION)
|
|
|
|
glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_plan_refs: recurse through the Plan nodes of a single subquery level
|
|
|
|
*/
|
|
|
|
static Plan *
|
|
|
|
set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
|
|
|
|
{
|
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
if (plan == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Plan-type-specific fixes
|
|
|
|
*/
|
|
|
|
switch (nodeTag(plan))
|
|
|
|
{
|
|
|
|
case T_SeqScan:
|
|
|
|
{
|
|
|
|
SeqScan *splan = (SeqScan *) plan;
|
|
|
|
|
|
|
|
splan->scanrelid += rtoffset;
|
|
|
|
splan->plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->plan.targetlist, rtoffset);
|
|
|
|
splan->plan.qual =
|
|
|
|
fix_scan_list(root, splan->plan.qual, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_SampleScan:
|
|
|
|
{
|
|
|
|
SampleScan *splan = (SampleScan *) plan;
|
|
|
|
|
|
|
|
splan->scanrelid += rtoffset;
|
|
|
|
splan->plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->plan.targetlist, rtoffset);
|
|
|
|
splan->plan.qual =
|
|
|
|
fix_scan_list(root, splan->plan.qual, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_IndexScan:
|
|
|
|
{
|
|
|
|
IndexScan *splan = (IndexScan *) plan;
|
|
|
|
|
|
|
|
splan->scan.scanrelid += rtoffset;
|
|
|
|
splan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
|
|
|
|
splan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
|
|
|
splan->indexqual =
|
|
|
|
fix_scan_list(root, splan->indexqual, rtoffset);
|
|
|
|
splan->indexqualorig =
|
|
|
|
fix_scan_list(root, splan->indexqualorig, rtoffset);
|
|
|
|
splan->indexorderby =
|
|
|
|
fix_scan_list(root, splan->indexorderby, rtoffset);
|
|
|
|
splan->indexorderbyorig =
|
|
|
|
fix_scan_list(root, splan->indexorderbyorig, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_IndexOnlyScan:
|
|
|
|
{
|
|
|
|
IndexOnlyScan *splan = (IndexOnlyScan *) plan;
|
|
|
|
|
|
|
|
return set_indexonlyscan_references(root, splan, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_BitmapIndexScan:
|
|
|
|
{
|
|
|
|
BitmapIndexScan *splan = (BitmapIndexScan *) plan;
|
|
|
|
|
|
|
|
splan->scan.scanrelid += rtoffset;
|
|
|
|
/* no need to fix targetlist and qual */
|
|
|
|
Assert(splan->scan.plan.targetlist == NIL);
|
|
|
|
Assert(splan->scan.plan.qual == NIL);
|
|
|
|
splan->indexqual =
|
|
|
|
fix_scan_list(root, splan->indexqual, rtoffset);
|
|
|
|
splan->indexqualorig =
|
|
|
|
fix_scan_list(root, splan->indexqualorig, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_BitmapHeapScan:
|
|
|
|
{
|
|
|
|
BitmapHeapScan *splan = (BitmapHeapScan *) plan;
|
|
|
|
|
|
|
|
splan->scan.scanrelid += rtoffset;
|
|
|
|
splan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
|
|
|
|
splan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
|
|
|
splan->bitmapqualorig =
|
|
|
|
fix_scan_list(root, splan->bitmapqualorig, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_TidScan:
|
|
|
|
{
|
|
|
|
TidScan *splan = (TidScan *) plan;
|
|
|
|
|
|
|
|
splan->scan.scanrelid += rtoffset;
|
|
|
|
splan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
|
|
|
|
splan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
|
|
|
splan->tidquals =
|
|
|
|
fix_scan_list(root, splan->tidquals, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_SubqueryScan:
|
|
|
|
/* Needs special treatment, see comments below */
|
|
|
|
return set_subqueryscan_references(root,
|
|
|
|
(SubqueryScan *) plan,
|
|
|
|
rtoffset);
|
|
|
|
case T_FunctionScan:
|
|
|
|
{
|
|
|
|
FunctionScan *splan = (FunctionScan *) plan;
|
|
|
|
|
|
|
|
splan->scan.scanrelid += rtoffset;
|
|
|
|
splan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
|
|
|
|
splan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
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
12 years ago
|
|
|
splan->functions =
|
|
|
|
fix_scan_list(root, splan->functions, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_ValuesScan:
|
|
|
|
{
|
|
|
|
ValuesScan *splan = (ValuesScan *) plan;
|
|
|
|
|
|
|
|
splan->scan.scanrelid += rtoffset;
|
|
|
|
splan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
|
|
|
|
splan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
|
|
|
splan->values_lists =
|
|
|
|
fix_scan_list(root, splan->values_lists, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_CteScan:
|
|
|
|
{
|
|
|
|
CteScan *splan = (CteScan *) plan;
|
|
|
|
|
|
|
|
splan->scan.scanrelid += rtoffset;
|
|
|
|
splan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
|
|
|
|
splan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_WorkTableScan:
|
|
|
|
{
|
|
|
|
WorkTableScan *splan = (WorkTableScan *) plan;
|
|
|
|
|
|
|
|
splan->scan.scanrelid += rtoffset;
|
|
|
|
splan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->scan.plan.targetlist, rtoffset);
|
|
|
|
splan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, splan->scan.plan.qual, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_ForeignScan:
|
|
|
|
set_foreignscan_references(root, (ForeignScan *) plan, rtoffset);
|
|
|
|
break;
|
|
|
|
case T_CustomScan:
|
|
|
|
set_customscan_references(root, (CustomScan *) plan, rtoffset);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case T_NestLoop:
|
|
|
|
case T_MergeJoin:
|
|
|
|
case T_HashJoin:
|
|
|
|
set_join_references(root, (Join *) plan, rtoffset);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case T_Hash:
|
|
|
|
case T_Material:
|
|
|
|
case T_Sort:
|
|
|
|
case T_Unique:
|
|
|
|
case T_SetOp:
|
|
|
|
|
|
|
|
/*
|
|
|
|
* These plan types don't actually bother to evaluate their
|
|
|
|
* targetlists, because they just return their unmodified input
|
|
|
|
* tuples. Even though the targetlist won't be used by the
|
|
|
|
* executor, we fix it up for possible use by EXPLAIN (not to
|
|
|
|
* mention ease of debugging --- wrong varnos are very confusing).
|
|
|
|
*/
|
|
|
|
set_dummy_tlist_references(plan, rtoffset);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Since these plan types don't check quals either, we should not
|
|
|
|
* find any qual expression attached to them.
|
|
|
|
*/
|
|
|
|
Assert(plan->qual == NIL);
|
|
|
|
break;
|
|
|
|
case T_LockRows:
|
|
|
|
{
|
|
|
|
LockRows *splan = (LockRows *) plan;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Like the plan types above, LockRows doesn't evaluate its
|
|
|
|
* tlist or quals. But we have to fix up the RT indexes in
|
|
|
|
* its rowmarks.
|
|
|
|
*/
|
|
|
|
set_dummy_tlist_references(plan, rtoffset);
|
|
|
|
Assert(splan->plan.qual == NIL);
|
|
|
|
|
|
|
|
foreach(l, splan->rowMarks)
|
|
|
|
{
|
Re-implement EvalPlanQual processing to improve its performance and eliminate
a lot of strange behaviors that occurred in join cases. We now identify the
"current" row for every joined relation in UPDATE, DELETE, and SELECT FOR
UPDATE/SHARE queries. If an EvalPlanQual recheck is necessary, we jam the
appropriate row into each scan node in the rechecking plan, forcing it to emit
only that one row. The former behavior could rescan the whole of each joined
relation for each recheck, which was terrible for performance, and what's much
worse could result in duplicated output tuples.
Also, the original implementation of EvalPlanQual could not re-use the recheck
execution tree --- it had to go through a full executor init and shutdown for
every row to be tested. To avoid this overhead, I've associated a special
runtime Param with each LockRows or ModifyTable plan node, and arranged to
make every scan node below such a node depend on that Param. Thus, by
signaling a change in that Param, the EPQ machinery can just rescan the
already-built test plan.
This patch also adds a prohibition on set-returning functions in the
targetlist of SELECT FOR UPDATE/SHARE. This is needed to avoid the
duplicate-output-tuple problem. It seems fairly reasonable since the
other restrictions on SELECT FOR UPDATE are meant to ensure that there
is a unique correspondence between source tuples and result tuples,
which an output SRF destroys as much as anything else does.
16 years ago
|
|
|
PlanRowMark *rc = (PlanRowMark *) lfirst(l);
|
|
|
|
|
|
|
|
rc->rti += rtoffset;
|
|
|
|
rc->prti += rtoffset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_Limit:
|
|
|
|
{
|
|
|
|
Limit *splan = (Limit *) plan;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Like the plan types above, Limit doesn't evaluate its tlist
|
|
|
|
* or quals. It does have live expressions for limit/offset,
|
|
|
|
* however; and those cannot contain subplan variable refs, so
|
|
|
|
* fix_scan_expr works for them.
|
|
|
|
*/
|
|
|
|
set_dummy_tlist_references(plan, rtoffset);
|
|
|
|
Assert(splan->plan.qual == NIL);
|
|
|
|
|
|
|
|
splan->limitOffset =
|
|
|
|
fix_scan_expr(root, splan->limitOffset, rtoffset);
|
|
|
|
splan->limitCount =
|
|
|
|
fix_scan_expr(root, splan->limitCount, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_Agg:
|
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
10 years ago
|
|
|
set_upper_references(root, plan, rtoffset);
|
|
|
|
break;
|
|
|
|
case T_Group:
|
|
|
|
set_upper_references(root, plan, rtoffset);
|
|
|
|
break;
|
|
|
|
case T_WindowAgg:
|
|
|
|
{
|
|
|
|
WindowAgg *wplan = (WindowAgg *) plan;
|
|
|
|
|
|
|
|
set_upper_references(root, plan, rtoffset);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Like Limit node limit/offset expressions, WindowAgg has
|
|
|
|
* frame offset expressions, which cannot contain subplan
|
|
|
|
* variable refs, so fix_scan_expr works for them.
|
|
|
|
*/
|
|
|
|
wplan->startOffset =
|
|
|
|
fix_scan_expr(root, wplan->startOffset, rtoffset);
|
|
|
|
wplan->endOffset =
|
|
|
|
fix_scan_expr(root, wplan->endOffset, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_Result:
|
|
|
|
{
|
|
|
|
Result *splan = (Result *) plan;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Result may or may not have a subplan; if not, it's more
|
|
|
|
* like a scan node than an upper node.
|
|
|
|
*/
|
|
|
|
if (splan->plan.lefttree != NULL)
|
|
|
|
set_upper_references(root, plan, rtoffset);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
splan->plan.targetlist =
|
|
|
|
fix_scan_list(root, splan->plan.targetlist, rtoffset);
|
|
|
|
splan->plan.qual =
|
|
|
|
fix_scan_list(root, splan->plan.qual, rtoffset);
|
|
|
|
}
|
|
|
|
/* resconstantqual can't contain any subplan variable refs */
|
|
|
|
splan->resconstantqual =
|
|
|
|
fix_scan_expr(root, splan->resconstantqual, rtoffset);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_ModifyTable:
|
|
|
|
{
|
|
|
|
ModifyTable *splan = (ModifyTable *) plan;
|
|
|
|
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
Assert(splan->plan.targetlist == NIL);
|
|
|
|
Assert(splan->plan.qual == NIL);
|
|
|
|
|
|
|
|
splan->withCheckOptionLists =
|
|
|
|
fix_scan_list(root, splan->withCheckOptionLists, rtoffset);
|
|
|
|
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
if (splan->returningLists)
|
|
|
|
{
|
|
|
|
List *newRL = NIL;
|
|
|
|
ListCell *lcrl,
|
|
|
|
*lcrr,
|
|
|
|
*lcp;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pass each per-subplan returningList through
|
|
|
|
* set_returning_clause_references().
|
|
|
|
*/
|
|
|
|
Assert(list_length(splan->returningLists) == list_length(splan->resultRelations));
|
|
|
|
Assert(list_length(splan->returningLists) == list_length(splan->plans));
|
|
|
|
forthree(lcrl, splan->returningLists,
|
|
|
|
lcrr, splan->resultRelations,
|
|
|
|
lcp, splan->plans)
|
|
|
|
{
|
|
|
|
List *rlist = (List *) lfirst(lcrl);
|
|
|
|
Index resultrel = lfirst_int(lcrr);
|
|
|
|
Plan *subplan = (Plan *) lfirst(lcp);
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
|
|
|
|
rlist = set_returning_clause_references(root,
|
|
|
|
rlist,
|
|
|
|
subplan,
|
|
|
|
resultrel,
|
|
|
|
rtoffset);
|
|
|
|
newRL = lappend(newRL, rlist);
|
|
|
|
}
|
|
|
|
splan->returningLists = newRL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up the visible plan targetlist as being the same as
|
|
|
|
* the first RETURNING list. This is for the use of
|
|
|
|
* EXPLAIN; the executor won't pay any attention to the
|
|
|
|
* targetlist. We postpone this step until here so that
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
* we don't have to do set_returning_clause_references()
|
|
|
|
* twice on identical targetlists.
|
|
|
|
*/
|
|
|
|
splan->plan.targetlist = copyObject(linitial(newRL));
|
|
|
|
}
|
|
|
|
|
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.
11 years ago
|
|
|
/*
|
|
|
|
* We treat ModifyTable with ON CONFLICT as a form of 'pseudo
|
|
|
|
* join', where the inner side is the EXCLUDED tuple.
|
|
|
|
* Therefore use fix_join_expr to setup the relevant variables
|
|
|
|
* to INNER_VAR. We explicitly don't create any OUTER_VARs as
|
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.
11 years ago
|
|
|
* those are already used by RETURNING and it seems better to
|
|
|
|
* be non-conflicting.
|
|
|
|
*/
|
|
|
|
if (splan->onConflictSet)
|
|
|
|
{
|
|
|
|
indexed_tlist *itlist;
|
|
|
|
|
|
|
|
itlist = build_tlist_index(splan->exclRelTlist);
|
|
|
|
|
|
|
|
splan->onConflictSet =
|
|
|
|
fix_join_expr(root, splan->onConflictSet,
|
|
|
|
NULL, itlist,
|
|
|
|
linitial_int(splan->resultRelations),
|
|
|
|
rtoffset);
|
|
|
|
|
|
|
|
splan->onConflictWhere = (Node *)
|
|
|
|
fix_join_expr(root, (List *) splan->onConflictWhere,
|
|
|
|
NULL, itlist,
|
|
|
|
linitial_int(splan->resultRelations),
|
|
|
|
rtoffset);
|
|
|
|
|
|
|
|
splan->exclRelTlist =
|
|
|
|
fix_scan_list(root, splan->exclRelTlist, rtoffset);
|
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.
11 years ago
|
|
|
}
|
|
|
|
|
|
|
|
splan->nominalRelation += rtoffset;
|
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.
11 years ago
|
|
|
splan->exclRelRTI += rtoffset;
|
|
|
|
|
|
|
|
foreach(l, splan->resultRelations)
|
|
|
|
{
|
|
|
|
lfirst_int(l) += rtoffset;
|
|
|
|
}
|
Re-implement EvalPlanQual processing to improve its performance and eliminate
a lot of strange behaviors that occurred in join cases. We now identify the
"current" row for every joined relation in UPDATE, DELETE, and SELECT FOR
UPDATE/SHARE queries. If an EvalPlanQual recheck is necessary, we jam the
appropriate row into each scan node in the rechecking plan, forcing it to emit
only that one row. The former behavior could rescan the whole of each joined
relation for each recheck, which was terrible for performance, and what's much
worse could result in duplicated output tuples.
Also, the original implementation of EvalPlanQual could not re-use the recheck
execution tree --- it had to go through a full executor init and shutdown for
every row to be tested. To avoid this overhead, I've associated a special
runtime Param with each LockRows or ModifyTable plan node, and arranged to
make every scan node below such a node depend on that Param. Thus, by
signaling a change in that Param, the EPQ machinery can just rescan the
already-built test plan.
This patch also adds a prohibition on set-returning functions in the
targetlist of SELECT FOR UPDATE/SHARE. This is needed to avoid the
duplicate-output-tuple problem. It seems fairly reasonable since the
other restrictions on SELECT FOR UPDATE are meant to ensure that there
is a unique correspondence between source tuples and result tuples,
which an output SRF destroys as much as anything else does.
16 years ago
|
|
|
foreach(l, splan->rowMarks)
|
|
|
|
{
|
|
|
|
PlanRowMark *rc = (PlanRowMark *) lfirst(l);
|
|
|
|
|
|
|
|
rc->rti += rtoffset;
|
|
|
|
rc->prti += rtoffset;
|
|
|
|
}
|
|
|
|
foreach(l, splan->plans)
|
|
|
|
{
|
|
|
|
lfirst(l) = set_plan_refs(root,
|
|
|
|
(Plan *) lfirst(l),
|
|
|
|
rtoffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Append this ModifyTable node's final result relation RT
|
|
|
|
* index(es) to the global list for the plan, and set its
|
|
|
|
* resultRelIndex to reflect their starting position in the
|
|
|
|
* global list.
|
|
|
|
*/
|
|
|
|
splan->resultRelIndex = list_length(root->glob->resultRelations);
|
|
|
|
root->glob->resultRelations =
|
|
|
|
list_concat(root->glob->resultRelations,
|
|
|
|
list_copy(splan->resultRelations));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_Append:
|
|
|
|
{
|
|
|
|
Append *splan = (Append *) plan;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Append, like Sort et al, doesn't actually evaluate its
|
|
|
|
* targetlist or check quals.
|
|
|
|
*/
|
|
|
|
set_dummy_tlist_references(plan, rtoffset);
|
|
|
|
Assert(splan->plan.qual == NIL);
|
|
|
|
foreach(l, splan->appendplans)
|
|
|
|
{
|
|
|
|
lfirst(l) = set_plan_refs(root,
|
|
|
|
(Plan *) lfirst(l),
|
|
|
|
rtoffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_MergeAppend:
|
|
|
|
{
|
|
|
|
MergeAppend *splan = (MergeAppend *) plan;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MergeAppend, like Sort et al, doesn't actually evaluate its
|
|
|
|
* targetlist or check quals.
|
|
|
|
*/
|
|
|
|
set_dummy_tlist_references(plan, rtoffset);
|
|
|
|
Assert(splan->plan.qual == NIL);
|
|
|
|
foreach(l, splan->mergeplans)
|
|
|
|
{
|
|
|
|
lfirst(l) = set_plan_refs(root,
|
|
|
|
(Plan *) lfirst(l),
|
|
|
|
rtoffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_RecursiveUnion:
|
|
|
|
/* This doesn't evaluate targetlist or check quals either */
|
|
|
|
set_dummy_tlist_references(plan, rtoffset);
|
|
|
|
Assert(plan->qual == NIL);
|
|
|
|
break;
|
|
|
|
case T_BitmapAnd:
|
|
|
|
{
|
|
|
|
BitmapAnd *splan = (BitmapAnd *) plan;
|
|
|
|
|
|
|
|
/* BitmapAnd works like Append, but has no tlist */
|
|
|
|
Assert(splan->plan.targetlist == NIL);
|
|
|
|
Assert(splan->plan.qual == NIL);
|
|
|
|
foreach(l, splan->bitmapplans)
|
|
|
|
{
|
|
|
|
lfirst(l) = set_plan_refs(root,
|
|
|
|
(Plan *) lfirst(l),
|
|
|
|
rtoffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case T_BitmapOr:
|
|
|
|
{
|
|
|
|
BitmapOr *splan = (BitmapOr *) plan;
|
|
|
|
|
|
|
|
/* BitmapOr works like Append, but has no tlist */
|
|
|
|
Assert(splan->plan.targetlist == NIL);
|
|
|
|
Assert(splan->plan.qual == NIL);
|
|
|
|
foreach(l, splan->bitmapplans)
|
|
|
|
{
|
|
|
|
lfirst(l) = set_plan_refs(root,
|
|
|
|
(Plan *) lfirst(l),
|
|
|
|
rtoffset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
elog(ERROR, "unrecognized node type: %d",
|
|
|
|
(int) nodeTag(plan));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now recurse into child plans, if any
|
|
|
|
*
|
|
|
|
* NOTE: it is essential that we recurse into child plans AFTER we set
|
|
|
|
* subplan references in this plan's tlist and quals. If we did the
|
|
|
|
* reference-adjustments bottom-up, then we would fail to match this
|
|
|
|
* plan's var nodes against the already-modified nodes of the children.
|
|
|
|
*/
|
|
|
|
plan->lefttree = set_plan_refs(root, plan->lefttree, rtoffset);
|
|
|
|
plan->righttree = set_plan_refs(root, plan->righttree, rtoffset);
|
|
|
|
|
|
|
|
return plan;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_indexonlyscan_references
|
|
|
|
* Do set_plan_references processing on an IndexOnlyScan
|
|
|
|
*
|
|
|
|
* This is unlike the handling of a plain IndexScan because we have to
|
|
|
|
* convert Vars referencing the heap into Vars referencing the index.
|
|
|
|
* We can use the fix_upper_expr machinery for that, by working from a
|
|
|
|
* targetlist describing the index columns.
|
|
|
|
*/
|
|
|
|
static Plan *
|
|
|
|
set_indexonlyscan_references(PlannerInfo *root,
|
|
|
|
IndexOnlyScan *plan,
|
|
|
|
int rtoffset)
|
|
|
|
{
|
|
|
|
indexed_tlist *index_itlist;
|
|
|
|
|
|
|
|
index_itlist = build_tlist_index(plan->indextlist);
|
|
|
|
|
|
|
|
plan->scan.scanrelid += rtoffset;
|
|
|
|
plan->scan.plan.targetlist = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) plan->scan.plan.targetlist,
|
|
|
|
index_itlist,
|
|
|
|
INDEX_VAR,
|
|
|
|
rtoffset);
|
|
|
|
plan->scan.plan.qual = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) plan->scan.plan.qual,
|
|
|
|
index_itlist,
|
|
|
|
INDEX_VAR,
|
|
|
|
rtoffset);
|
|
|
|
/* indexqual is already transformed to reference index columns */
|
|
|
|
plan->indexqual = fix_scan_list(root, plan->indexqual, rtoffset);
|
|
|
|
/* indexorderby is already transformed to reference index columns */
|
|
|
|
plan->indexorderby = fix_scan_list(root, plan->indexorderby, rtoffset);
|
|
|
|
/* indextlist must NOT be transformed to reference index columns */
|
|
|
|
plan->indextlist = fix_scan_list(root, plan->indextlist, rtoffset);
|
|
|
|
|
|
|
|
pfree(index_itlist);
|
|
|
|
|
|
|
|
return (Plan *) plan;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_subqueryscan_references
|
|
|
|
* Do set_plan_references processing on a SubqueryScan
|
|
|
|
*
|
|
|
|
* We try to strip out the SubqueryScan entirely; if we can't, we have
|
|
|
|
* to do the normal processing on it.
|
|
|
|
*/
|
|
|
|
static Plan *
|
|
|
|
set_subqueryscan_references(PlannerInfo *root,
|
|
|
|
SubqueryScan *plan,
|
|
|
|
int rtoffset)
|
|
|
|
{
|
|
|
|
RelOptInfo *rel;
|
|
|
|
Plan *result;
|
|
|
|
|
|
|
|
/* Need to look up the subquery's RelOptInfo, since we need its subroot */
|
|
|
|
rel = find_base_rel(root, plan->scan.scanrelid);
|
|
|
|
Assert(rel->subplan == plan->subplan);
|
|
|
|
|
|
|
|
/* Recursively process the subplan */
|
|
|
|
plan->subplan = set_plan_references(rel->subroot, plan->subplan);
|
|
|
|
|
|
|
|
if (trivial_subqueryscan(plan))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* We can omit the SubqueryScan node and just pull up the subplan.
|
|
|
|
*/
|
|
|
|
ListCell *lp,
|
|
|
|
*lc;
|
|
|
|
|
|
|
|
result = plan->subplan;
|
|
|
|
|
|
|
|
/* We have to be sure we don't lose any initplans */
|
|
|
|
result->initPlan = list_concat(plan->scan.plan.initPlan,
|
|
|
|
result->initPlan);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We also have to transfer the SubqueryScan's result-column names
|
|
|
|
* into the subplan, else columns sent to client will be improperly
|
|
|
|
* labeled if this is the topmost plan level. Copy the "source
|
|
|
|
* column" information too.
|
|
|
|
*/
|
|
|
|
forboth(lp, plan->scan.plan.targetlist, lc, result->targetlist)
|
|
|
|
{
|
|
|
|
TargetEntry *ptle = (TargetEntry *) lfirst(lp);
|
|
|
|
TargetEntry *ctle = (TargetEntry *) lfirst(lc);
|
|
|
|
|
|
|
|
ctle->resname = ptle->resname;
|
|
|
|
ctle->resorigtbl = ptle->resorigtbl;
|
|
|
|
ctle->resorigcol = ptle->resorigcol;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Keep the SubqueryScan node. We have to do the processing that
|
|
|
|
* set_plan_references would otherwise have done on it. Notice we do
|
|
|
|
* not do set_upper_references() here, because a SubqueryScan will
|
|
|
|
* always have been created with correct references to its subplan's
|
|
|
|
* outputs to begin with.
|
|
|
|
*/
|
|
|
|
plan->scan.scanrelid += rtoffset;
|
|
|
|
plan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, plan->scan.plan.targetlist, rtoffset);
|
|
|
|
plan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, plan->scan.plan.qual, rtoffset);
|
|
|
|
|
|
|
|
result = (Plan *) plan;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* trivial_subqueryscan
|
|
|
|
* Detect whether a SubqueryScan can be deleted from the plan tree.
|
|
|
|
*
|
|
|
|
* We can delete it if it has no qual to check and the targetlist just
|
|
|
|
* regurgitates the output of the child plan.
|
|
|
|
*/
|
|
|
|
static bool
|
|
|
|
trivial_subqueryscan(SubqueryScan *plan)
|
|
|
|
{
|
|
|
|
int attrno;
|
|
|
|
ListCell *lp,
|
|
|
|
*lc;
|
|
|
|
|
|
|
|
if (plan->scan.plan.qual != NIL)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (list_length(plan->scan.plan.targetlist) !=
|
|
|
|
list_length(plan->subplan->targetlist))
|
|
|
|
return false; /* tlists not same length */
|
|
|
|
|
|
|
|
attrno = 1;
|
|
|
|
forboth(lp, plan->scan.plan.targetlist, lc, plan->subplan->targetlist)
|
|
|
|
{
|
|
|
|
TargetEntry *ptle = (TargetEntry *) lfirst(lp);
|
|
|
|
TargetEntry *ctle = (TargetEntry *) lfirst(lc);
|
|
|
|
|
|
|
|
if (ptle->resjunk != ctle->resjunk)
|
|
|
|
return false; /* tlist doesn't match junk status */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We accept either a Var referencing the corresponding element of the
|
|
|
|
* subplan tlist, or a Const equaling the subplan element. See
|
|
|
|
* generate_setop_tlist() for motivation.
|
|
|
|
*/
|
|
|
|
if (ptle->expr && IsA(ptle->expr, Var))
|
|
|
|
{
|
|
|
|
Var *var = (Var *) ptle->expr;
|
|
|
|
|
|
|
|
Assert(var->varno == plan->scan.scanrelid);
|
|
|
|
Assert(var->varlevelsup == 0);
|
|
|
|
if (var->varattno != attrno)
|
|
|
|
return false; /* out of order */
|
|
|
|
}
|
|
|
|
else if (ptle->expr && IsA(ptle->expr, Const))
|
|
|
|
{
|
|
|
|
if (!equal(ptle->expr, ctle->expr))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return false;
|
|
|
|
|
|
|
|
attrno++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
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.
10 years ago
|
|
|
/*
|
|
|
|
* set_foreignscan_references
|
|
|
|
* Do set_plan_references processing on a ForeignScan
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
set_foreignscan_references(PlannerInfo *root,
|
|
|
|
ForeignScan *fscan,
|
|
|
|
int rtoffset)
|
|
|
|
{
|
|
|
|
/* Adjust scanrelid if it's valid */
|
|
|
|
if (fscan->scan.scanrelid > 0)
|
|
|
|
fscan->scan.scanrelid += rtoffset;
|
|
|
|
|
|
|
|
if (fscan->fdw_scan_tlist != NIL || fscan->scan.scanrelid == 0)
|
|
|
|
{
|
|
|
|
/* Adjust tlist, qual, fdw_exprs to reference custom scan tuple */
|
|
|
|
indexed_tlist *itlist = build_tlist_index(fscan->fdw_scan_tlist);
|
|
|
|
|
|
|
|
fscan->scan.plan.targetlist = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) fscan->scan.plan.targetlist,
|
|
|
|
itlist,
|
|
|
|
INDEX_VAR,
|
|
|
|
rtoffset);
|
|
|
|
fscan->scan.plan.qual = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) fscan->scan.plan.qual,
|
|
|
|
itlist,
|
|
|
|
INDEX_VAR,
|
|
|
|
rtoffset);
|
|
|
|
fscan->fdw_exprs = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) fscan->fdw_exprs,
|
|
|
|
itlist,
|
|
|
|
INDEX_VAR,
|
|
|
|
rtoffset);
|
|
|
|
pfree(itlist);
|
|
|
|
/* fdw_scan_tlist itself just needs fix_scan_list() adjustments */
|
|
|
|
fscan->fdw_scan_tlist =
|
|
|
|
fix_scan_list(root, fscan->fdw_scan_tlist, rtoffset);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Adjust tlist, qual, fdw_exprs in the standard way */
|
|
|
|
fscan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, fscan->scan.plan.targetlist, rtoffset);
|
|
|
|
fscan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, fscan->scan.plan.qual, rtoffset);
|
|
|
|
fscan->fdw_exprs =
|
|
|
|
fix_scan_list(root, fscan->fdw_exprs, rtoffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Adjust fs_relids if needed */
|
|
|
|
if (rtoffset > 0)
|
|
|
|
{
|
|
|
|
Bitmapset *tempset = NULL;
|
|
|
|
int x = -1;
|
|
|
|
|
|
|
|
while ((x = bms_next_member(fscan->fs_relids, x)) >= 0)
|
|
|
|
tempset = bms_add_member(tempset, x + rtoffset);
|
|
|
|
fscan->fs_relids = tempset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_customscan_references
|
|
|
|
* Do set_plan_references processing on a CustomScan
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
set_customscan_references(PlannerInfo *root,
|
|
|
|
CustomScan *cscan,
|
|
|
|
int rtoffset)
|
|
|
|
{
|
|
|
|
/* Adjust scanrelid if it's valid */
|
|
|
|
if (cscan->scan.scanrelid > 0)
|
|
|
|
cscan->scan.scanrelid += rtoffset;
|
|
|
|
|
|
|
|
if (cscan->custom_scan_tlist != NIL || cscan->scan.scanrelid == 0)
|
|
|
|
{
|
|
|
|
/* Adjust tlist, qual, custom_exprs to reference custom scan tuple */
|
|
|
|
indexed_tlist *itlist = build_tlist_index(cscan->custom_scan_tlist);
|
|
|
|
|
|
|
|
cscan->scan.plan.targetlist = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) cscan->scan.plan.targetlist,
|
|
|
|
itlist,
|
|
|
|
INDEX_VAR,
|
|
|
|
rtoffset);
|
|
|
|
cscan->scan.plan.qual = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) cscan->scan.plan.qual,
|
|
|
|
itlist,
|
|
|
|
INDEX_VAR,
|
|
|
|
rtoffset);
|
|
|
|
cscan->custom_exprs = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) cscan->custom_exprs,
|
|
|
|
itlist,
|
|
|
|
INDEX_VAR,
|
|
|
|
rtoffset);
|
|
|
|
pfree(itlist);
|
|
|
|
/* custom_scan_tlist itself just needs fix_scan_list() adjustments */
|
|
|
|
cscan->custom_scan_tlist =
|
|
|
|
fix_scan_list(root, cscan->custom_scan_tlist, rtoffset);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Adjust tlist, qual, custom_exprs in the standard way */
|
|
|
|
cscan->scan.plan.targetlist =
|
|
|
|
fix_scan_list(root, cscan->scan.plan.targetlist, rtoffset);
|
|
|
|
cscan->scan.plan.qual =
|
|
|
|
fix_scan_list(root, cscan->scan.plan.qual, rtoffset);
|
|
|
|
cscan->custom_exprs =
|
|
|
|
fix_scan_list(root, cscan->custom_exprs, rtoffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Adjust custom_relids if needed */
|
|
|
|
if (rtoffset > 0)
|
|
|
|
{
|
|
|
|
Bitmapset *tempset = NULL;
|
|
|
|
int x = -1;
|
|
|
|
|
|
|
|
while ((x = bms_next_member(cscan->custom_relids, x)) >= 0)
|
|
|
|
tempset = bms_add_member(tempset, x + rtoffset);
|
|
|
|
cscan->custom_relids = tempset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* copyVar
|
|
|
|
* Copy a Var node.
|
|
|
|
*
|
|
|
|
* fix_scan_expr and friends do this enough times that it's worth having
|
|
|
|
* a bespoke routine instead of using the generic copyObject() function.
|
|
|
|
*/
|
|
|
|
static inline Var *
|
|
|
|
copyVar(Var *var)
|
|
|
|
{
|
|
|
|
Var *newvar = (Var *) palloc(sizeof(Var));
|
|
|
|
|
|
|
|
*newvar = *var;
|
|
|
|
return newvar;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* fix_expr_common
|
|
|
|
* Do generic set_plan_references processing on an expression node
|
|
|
|
*
|
|
|
|
* This is code that is common to all variants of expression-fixing.
|
|
|
|
* We must look up operator opcode info for OpExpr and related nodes,
|
|
|
|
* add OIDs from regclass Const nodes into root->glob->relationOids, and
|
|
|
|
* add catalog TIDs for user-defined functions into root->glob->invalItems.
|
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
10 years ago
|
|
|
* We also fill in column index lists for GROUPING() expressions.
|
|
|
|
*
|
|
|
|
* We assume it's okay to update opcode info in-place. So this could possibly
|
|
|
|
* scribble on the planner's input data structures, but it's OK.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
fix_expr_common(PlannerInfo *root, Node *node)
|
|
|
|
{
|
|
|
|
/* We assume callers won't call us on a NULL pointer */
|
|
|
|
if (IsA(node, Aggref))
|
|
|
|
{
|
|
|
|
record_plan_function_dependency(root,
|
|
|
|
((Aggref *) node)->aggfnoid);
|
|
|
|
}
|
|
|
|
else if (IsA(node, WindowFunc))
|
|
|
|
{
|
|
|
|
record_plan_function_dependency(root,
|
|
|
|
((WindowFunc *) node)->winfnoid);
|
|
|
|
}
|
|
|
|
else if (IsA(node, FuncExpr))
|
|
|
|
{
|
|
|
|
record_plan_function_dependency(root,
|
|
|
|
((FuncExpr *) node)->funcid);
|
|
|
|
}
|
|
|
|
else if (IsA(node, OpExpr))
|
|
|
|
{
|
|
|
|
set_opfuncid((OpExpr *) node);
|
|
|
|
record_plan_function_dependency(root,
|
|
|
|
((OpExpr *) node)->opfuncid);
|
|
|
|
}
|
|
|
|
else if (IsA(node, DistinctExpr))
|
|
|
|
{
|
|
|
|
set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
|
|
|
|
record_plan_function_dependency(root,
|
|
|
|
((DistinctExpr *) node)->opfuncid);
|
|
|
|
}
|
|
|
|
else if (IsA(node, NullIfExpr))
|
|
|
|
{
|
|
|
|
set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
|
|
|
|
record_plan_function_dependency(root,
|
|
|
|
((NullIfExpr *) node)->opfuncid);
|
|
|
|
}
|
|
|
|
else if (IsA(node, ScalarArrayOpExpr))
|
|
|
|
{
|
|
|
|
set_sa_opfuncid((ScalarArrayOpExpr *) node);
|
|
|
|
record_plan_function_dependency(root,
|
|
|
|
((ScalarArrayOpExpr *) node)->opfuncid);
|
|
|
|
}
|
|
|
|
else if (IsA(node, ArrayCoerceExpr))
|
|
|
|
{
|
|
|
|
if (OidIsValid(((ArrayCoerceExpr *) node)->elemfuncid))
|
|
|
|
record_plan_function_dependency(root,
|
|
|
|
((ArrayCoerceExpr *) node)->elemfuncid);
|
|
|
|
}
|
|
|
|
else if (IsA(node, Const))
|
|
|
|
{
|
|
|
|
Const *con = (Const *) node;
|
|
|
|
|
|
|
|
/* Check for regclass reference */
|
|
|
|
if (ISREGCLASSCONST(con))
|
|
|
|
root->glob->relationOids =
|
|
|
|
lappend_oid(root->glob->relationOids,
|
|
|
|
DatumGetObjectId(con->constvalue));
|
|
|
|
}
|
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
10 years ago
|
|
|
else if (IsA(node, GroupingFunc))
|
|
|
|
{
|
|
|
|
GroupingFunc *g = (GroupingFunc *) node;
|
|
|
|
AttrNumber *grouping_map = root->grouping_map;
|
|
|
|
|
|
|
|
/* If there are no grouping sets, we don't need this. */
|
|
|
|
|
|
|
|
Assert(grouping_map || g->cols == NIL);
|
|
|
|
|
|
|
|
if (grouping_map)
|
|
|
|
{
|
|
|
|
ListCell *lc;
|
|
|
|
List *cols = NIL;
|
|
|
|
|
|
|
|
foreach(lc, g->refs)
|
|
|
|
{
|
|
|
|
cols = lappend_int(cols, grouping_map[lfirst_int(lc)]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Assert(!g->cols || equal(cols, g->cols));
|
|
|
|
|
|
|
|
if (!g->cols)
|
|
|
|
g->cols = cols;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
/*
|
|
|
|
* fix_param_node
|
|
|
|
* Do set_plan_references processing on a Param
|
|
|
|
*
|
|
|
|
* If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from
|
|
|
|
* root->multiexpr_params; otherwise no change is needed.
|
|
|
|
* Just for paranoia's sake, we make a copy of the node in either case.
|
|
|
|
*/
|
|
|
|
static Node *
|
|
|
|
fix_param_node(PlannerInfo *root, Param *p)
|
|
|
|
{
|
|
|
|
if (p->paramkind == PARAM_MULTIEXPR)
|
|
|
|
{
|
|
|
|
int subqueryid = p->paramid >> 16;
|
|
|
|
int colno = p->paramid & 0xFFFF;
|
|
|
|
List *params;
|
|
|
|
|
|
|
|
if (subqueryid <= 0 ||
|
|
|
|
subqueryid > list_length(root->multiexpr_params))
|
|
|
|
elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
|
|
|
|
params = (List *) list_nth(root->multiexpr_params, subqueryid - 1);
|
|
|
|
if (colno <= 0 || colno > list_length(params))
|
|
|
|
elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid);
|
|
|
|
return copyObject(list_nth(params, colno - 1));
|
|
|
|
}
|
|
|
|
return copyObject(p);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* fix_scan_expr
|
|
|
|
* Do set_plan_references processing on a scan-level expression
|
|
|
|
*
|
|
|
|
* This consists of incrementing all Vars' varnos by rtoffset,
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
* replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars,
|
|
|
|
* looking up operator opcode info for OpExpr and related nodes,
|
|
|
|
* and adding OIDs from regclass Const nodes into root->glob->relationOids.
|
|
|
|
*/
|
|
|
|
static Node *
|
|
|
|
fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
|
|
|
|
{
|
|
|
|
fix_scan_expr_context context;
|
|
|
|
|
|
|
|
context.root = root;
|
|
|
|
context.rtoffset = rtoffset;
|
|
|
|
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
if (rtoffset != 0 ||
|
|
|
|
root->multiexpr_params != NIL ||
|
|
|
|
root->glob->lastPHId != 0)
|
|
|
|
{
|
|
|
|
return fix_scan_expr_mutator(node, &context);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If rtoffset == 0, we don't need to change any Vars, and if there
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
* are no MULTIEXPR subqueries then we don't need to replace
|
|
|
|
* PARAM_MULTIEXPR Params, and if there are no placeholders anywhere
|
|
|
|
* we won't need to remove them. Then it's OK to just scribble on the
|
|
|
|
* input node tree instead of copying (since the only change, filling
|
|
|
|
* in any unset opfuncid fields, is harmless). This saves just enough
|
|
|
|
* cycles to be noticeable on trivial queries.
|
|
|
|
*/
|
|
|
|
(void) fix_scan_expr_walker(node, &context);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static Node *
|
|
|
|
fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
|
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return NULL;
|
|
|
|
if (IsA(node, Var))
|
|
|
|
{
|
|
|
|
Var *var = copyVar((Var *) node);
|
|
|
|
|
|
|
|
Assert(var->varlevelsup == 0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We should not see any Vars marked INNER_VAR or OUTER_VAR. But an
|
|
|
|
* indexqual expression could contain INDEX_VAR Vars.
|
|
|
|
*/
|
|
|
|
Assert(var->varno != INNER_VAR);
|
|
|
|
Assert(var->varno != OUTER_VAR);
|
|
|
|
if (!IS_SPECIAL_VARNO(var->varno))
|
|
|
|
var->varno += context->rtoffset;
|
|
|
|
if (var->varnoold > 0)
|
|
|
|
var->varnoold += context->rtoffset;
|
|
|
|
return (Node *) var;
|
|
|
|
}
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
if (IsA(node, Param))
|
|
|
|
return fix_param_node(context->root, (Param *) node);
|
|
|
|
if (IsA(node, CurrentOfExpr))
|
|
|
|
{
|
|
|
|
CurrentOfExpr *cexpr = (CurrentOfExpr *) copyObject(node);
|
|
|
|
|
|
|
|
Assert(cexpr->cvarno != INNER_VAR);
|
|
|
|
Assert(cexpr->cvarno != OUTER_VAR);
|
|
|
|
if (!IS_SPECIAL_VARNO(cexpr->cvarno))
|
|
|
|
cexpr->cvarno += context->rtoffset;
|
|
|
|
return (Node *) cexpr;
|
|
|
|
}
|
|
|
|
if (IsA(node, PlaceHolderVar))
|
|
|
|
{
|
|
|
|
/* At scan level, we should always just evaluate the contained expr */
|
|
|
|
PlaceHolderVar *phv = (PlaceHolderVar *) node;
|
|
|
|
|
|
|
|
return fix_scan_expr_mutator((Node *) phv->phexpr, context);
|
|
|
|
}
|
|
|
|
fix_expr_common(context->root, node);
|
|
|
|
return expression_tree_mutator(node, fix_scan_expr_mutator,
|
|
|
|
(void *) context);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
fix_scan_expr_walker(Node *node, fix_scan_expr_context *context)
|
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return false;
|
|
|
|
Assert(!IsA(node, PlaceHolderVar));
|
|
|
|
fix_expr_common(context->root, node);
|
|
|
|
return expression_tree_walker(node, fix_scan_expr_walker,
|
|
|
|
(void *) context);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_join_references
|
|
|
|
* Modify the target list and quals of a join node to reference its
|
|
|
|
* subplans, by setting the varnos to OUTER_VAR or INNER_VAR and setting
|
|
|
|
* attno values to the result domain number of either the corresponding
|
|
|
|
* outer or inner join tuple item. Also perform opcode lookup for these
|
|
|
|
* expressions. and add regclass OIDs to root->glob->relationOids.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
set_join_references(PlannerInfo *root, Join *join, int rtoffset)
|
|
|
|
{
|
|
|
|
Plan *outer_plan = join->plan.lefttree;
|
|
|
|
Plan *inner_plan = join->plan.righttree;
|
|
|
|
indexed_tlist *outer_itlist;
|
|
|
|
indexed_tlist *inner_itlist;
|
|
|
|
|
|
|
|
outer_itlist = build_tlist_index(outer_plan->targetlist);
|
|
|
|
inner_itlist = build_tlist_index(inner_plan->targetlist);
|
|
|
|
|
Fix incorrect matching of subexpressions in outer-join plan nodes.
Previously we would re-use input subexpressions in all expression trees
attached to a Join plan node. However, if it's an outer join and the
subexpression appears in the nullable-side input, this is potentially
incorrect for apparently-matching subexpressions that came from above
the outer join (ie, targetlist and qpqual expressions), because the
executor will treat the subexpression value as NULL when maybe it should
not be.
The case is fairly hard to hit because (a) you need a non-strict
subexpression (else NULL is correct), and (b) we don't usually compute
expressions in the outputs of non-toplevel plan nodes. But we might do
so if the expressions are sort keys for a mergejoin, for example.
Probably in the long run we should make a more explicit distinction between
Vars appearing above and below an outer join, but that will be a major
planner redesign and not at all back-patchable. For the moment, just hack
set_join_references so that it will not match any non-Var expressions
coming from nullable inputs to expressions that came from above the join.
(This is somewhat overkill, in that a strict expression could still be
matched, but it doesn't seem worth the effort to check that.)
Per report from Qingqing Zhou. The added regression test case is based
on his example.
This has been broken for a very long time, so back-patch to all active
branches.
11 years ago
|
|
|
/*
|
|
|
|
* First process the joinquals (including merge or hash clauses). These
|
|
|
|
* are logically below the join so they can always use all values
|
|
|
|
* available from the input tlists. It's okay to also handle
|
|
|
|
* NestLoopParams now, because those couldn't refer to nullable
|
|
|
|
* subexpressions.
|
|
|
|
*/
|
|
|
|
join->joinqual = fix_join_expr(root,
|
|
|
|
join->joinqual,
|
|
|
|
outer_itlist,
|
|
|
|
inner_itlist,
|
|
|
|
(Index) 0,
|
|
|
|
rtoffset);
|
|
|
|
|
|
|
|
/* Now do join-type-specific stuff */
|
|
|
|
if (IsA(join, NestLoop))
|
|
|
|
{
|
|
|
|
NestLoop *nl = (NestLoop *) join;
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
foreach(lc, nl->nestParams)
|
|
|
|
{
|
|
|
|
NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
|
|
|
|
|
|
|
|
nlp->paramval = (Var *) fix_upper_expr(root,
|
|
|
|
(Node *) nlp->paramval,
|
|
|
|
outer_itlist,
|
|
|
|
OUTER_VAR,
|
|
|
|
rtoffset);
|
|
|
|
/* Check we replaced any PlaceHolderVar with simple Var */
|
|
|
|
if (!(IsA(nlp->paramval, Var) &&
|
|
|
|
nlp->paramval->varno == OUTER_VAR))
|
|
|
|
elog(ERROR, "NestLoopParam was not reduced to a simple Var");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (IsA(join, MergeJoin))
|
|
|
|
{
|
|
|
|
MergeJoin *mj = (MergeJoin *) join;
|
|
|
|
|
|
|
|
mj->mergeclauses = fix_join_expr(root,
|
|
|
|
mj->mergeclauses,
|
|
|
|
outer_itlist,
|
|
|
|
inner_itlist,
|
|
|
|
(Index) 0,
|
|
|
|
rtoffset);
|
|
|
|
}
|
|
|
|
else if (IsA(join, HashJoin))
|
|
|
|
{
|
|
|
|
HashJoin *hj = (HashJoin *) join;
|
|
|
|
|
|
|
|
hj->hashclauses = fix_join_expr(root,
|
|
|
|
hj->hashclauses,
|
|
|
|
outer_itlist,
|
|
|
|
inner_itlist,
|
|
|
|
(Index) 0,
|
|
|
|
rtoffset);
|
|
|
|
}
|
|
|
|
|
Fix incorrect matching of subexpressions in outer-join plan nodes.
Previously we would re-use input subexpressions in all expression trees
attached to a Join plan node. However, if it's an outer join and the
subexpression appears in the nullable-side input, this is potentially
incorrect for apparently-matching subexpressions that came from above
the outer join (ie, targetlist and qpqual expressions), because the
executor will treat the subexpression value as NULL when maybe it should
not be.
The case is fairly hard to hit because (a) you need a non-strict
subexpression (else NULL is correct), and (b) we don't usually compute
expressions in the outputs of non-toplevel plan nodes. But we might do
so if the expressions are sort keys for a mergejoin, for example.
Probably in the long run we should make a more explicit distinction between
Vars appearing above and below an outer join, but that will be a major
planner redesign and not at all back-patchable. For the moment, just hack
set_join_references so that it will not match any non-Var expressions
coming from nullable inputs to expressions that came from above the join.
(This is somewhat overkill, in that a strict expression could still be
matched, but it doesn't seem worth the effort to check that.)
Per report from Qingqing Zhou. The added regression test case is based
on his example.
This has been broken for a very long time, so back-patch to all active
branches.
11 years ago
|
|
|
/*
|
|
|
|
* Now we need to fix up the targetlist and qpqual, which are logically
|
|
|
|
* above the join. This means they should not re-use any input expression
|
|
|
|
* that was computed in the nullable side of an outer join. Vars and
|
|
|
|
* PlaceHolderVars are fine, so we can implement this restriction just by
|
|
|
|
* clearing has_non_vars in the indexed_tlist structs.
|
|
|
|
*
|
|
|
|
* XXX This is a grotty workaround for the fact that we don't clearly
|
|
|
|
* distinguish between a Var appearing below an outer join and the "same"
|
|
|
|
* Var appearing above it. If we did, we'd not need to hack the matching
|
|
|
|
* rules this way.
|
|
|
|
*/
|
|
|
|
switch (join->jointype)
|
|
|
|
{
|
|
|
|
case JOIN_LEFT:
|
|
|
|
case JOIN_SEMI:
|
|
|
|
case JOIN_ANTI:
|
|
|
|
inner_itlist->has_non_vars = false;
|
|
|
|
break;
|
|
|
|
case JOIN_RIGHT:
|
|
|
|
outer_itlist->has_non_vars = false;
|
|
|
|
break;
|
|
|
|
case JOIN_FULL:
|
|
|
|
outer_itlist->has_non_vars = false;
|
|
|
|
inner_itlist->has_non_vars = false;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
join->plan.targetlist = fix_join_expr(root,
|
|
|
|
join->plan.targetlist,
|
|
|
|
outer_itlist,
|
|
|
|
inner_itlist,
|
|
|
|
(Index) 0,
|
|
|
|
rtoffset);
|
|
|
|
join->plan.qual = fix_join_expr(root,
|
|
|
|
join->plan.qual,
|
|
|
|
outer_itlist,
|
|
|
|
inner_itlist,
|
|
|
|
(Index) 0,
|
|
|
|
rtoffset);
|
|
|
|
|
|
|
|
pfree(outer_itlist);
|
|
|
|
pfree(inner_itlist);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_upper_references
|
|
|
|
* Update the targetlist and quals of an upper-level plan node
|
|
|
|
* to refer to the tuples returned by its lefttree subplan.
|
|
|
|
* Also perform opcode lookup for these expressions, and
|
|
|
|
* add regclass OIDs to root->glob->relationOids.
|
|
|
|
*
|
|
|
|
* This is used for single-input plan types like Agg, Group, Result.
|
|
|
|
*
|
|
|
|
* In most cases, we have to match up individual Vars in the tlist and
|
|
|
|
* qual expressions with elements of the subplan's tlist (which was
|
|
|
|
* generated by flatten_tlist() from these selfsame expressions, so it
|
|
|
|
* should have all the required variables). There is an important exception,
|
|
|
|
* however: GROUP BY and ORDER BY expressions will have been pushed into the
|
|
|
|
* subplan tlist unflattened. If these values are also needed in the output
|
|
|
|
* then we want to reference the subplan tlist element rather than recomputing
|
|
|
|
* the expression.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
set_upper_references(PlannerInfo *root, Plan *plan, int rtoffset)
|
|
|
|
{
|
|
|
|
Plan *subplan = plan->lefttree;
|
|
|
|
indexed_tlist *subplan_itlist;
|
|
|
|
List *output_targetlist;
|
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
subplan_itlist = build_tlist_index(subplan->targetlist);
|
|
|
|
|
|
|
|
output_targetlist = NIL;
|
|
|
|
foreach(l, plan->targetlist)
|
|
|
|
{
|
|
|
|
TargetEntry *tle = (TargetEntry *) lfirst(l);
|
|
|
|
Node *newexpr;
|
|
|
|
|
|
|
|
/* If it's a non-Var sort/group item, first try to match by sortref */
|
|
|
|
if (tle->ressortgroupref != 0 && !IsA(tle->expr, Var))
|
|
|
|
{
|
|
|
|
newexpr = (Node *)
|
|
|
|
search_indexed_tlist_for_sortgroupref((Node *) tle->expr,
|
|
|
|
tle->ressortgroupref,
|
|
|
|
subplan_itlist,
|
|
|
|
OUTER_VAR);
|
|
|
|
if (!newexpr)
|
|
|
|
newexpr = fix_upper_expr(root,
|
|
|
|
(Node *) tle->expr,
|
|
|
|
subplan_itlist,
|
|
|
|
OUTER_VAR,
|
|
|
|
rtoffset);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
newexpr = fix_upper_expr(root,
|
|
|
|
(Node *) tle->expr,
|
|
|
|
subplan_itlist,
|
|
|
|
OUTER_VAR,
|
|
|
|
rtoffset);
|
|
|
|
tle = flatCopyTargetEntry(tle);
|
|
|
|
tle->expr = (Expr *) newexpr;
|
|
|
|
output_targetlist = lappend(output_targetlist, tle);
|
|
|
|
}
|
|
|
|
plan->targetlist = output_targetlist;
|
|
|
|
|
|
|
|
plan->qual = (List *)
|
|
|
|
fix_upper_expr(root,
|
|
|
|
(Node *) plan->qual,
|
|
|
|
subplan_itlist,
|
|
|
|
OUTER_VAR,
|
|
|
|
rtoffset);
|
|
|
|
|
|
|
|
pfree(subplan_itlist);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_dummy_tlist_references
|
|
|
|
* Replace the targetlist of an upper-level plan node with a simple
|
|
|
|
* list of OUTER_VAR references to its child.
|
|
|
|
*
|
|
|
|
* This is used for plan types like Sort and Append that don't evaluate
|
|
|
|
* their targetlists. Although the executor doesn't care at all what's in
|
|
|
|
* the tlist, EXPLAIN needs it to be realistic.
|
|
|
|
*
|
|
|
|
* Note: we could almost use set_upper_references() here, but it fails for
|
|
|
|
* Append for lack of a lefttree subplan. Single-purpose code is faster
|
|
|
|
* anyway.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
set_dummy_tlist_references(Plan *plan, int rtoffset)
|
|
|
|
{
|
|
|
|
List *output_targetlist;
|
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
output_targetlist = NIL;
|
|
|
|
foreach(l, plan->targetlist)
|
|
|
|
{
|
|
|
|
TargetEntry *tle = (TargetEntry *) lfirst(l);
|
|
|
|
Var *oldvar = (Var *) tle->expr;
|
|
|
|
Var *newvar;
|
|
|
|
|
|
|
|
newvar = makeVar(OUTER_VAR,
|
|
|
|
tle->resno,
|
|
|
|
exprType((Node *) oldvar),
|
|
|
|
exprTypmod((Node *) oldvar),
|
|
|
|
exprCollation((Node *) oldvar),
|
|
|
|
0);
|
|
|
|
if (IsA(oldvar, Var))
|
|
|
|
{
|
|
|
|
newvar->varnoold = oldvar->varno + rtoffset;
|
|
|
|
newvar->varoattno = oldvar->varattno;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
newvar->varnoold = 0; /* wasn't ever a plain Var */
|
|
|
|
newvar->varoattno = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
tle = flatCopyTargetEntry(tle);
|
|
|
|
tle->expr = (Expr *) newvar;
|
|
|
|
output_targetlist = lappend(output_targetlist, tle);
|
|
|
|
}
|
|
|
|
plan->targetlist = output_targetlist;
|
|
|
|
|
|
|
|
/* We don't touch plan->qual here */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* build_tlist_index --- build an index data structure for a child tlist
|
|
|
|
*
|
|
|
|
* In most cases, subplan tlists will be "flat" tlists with only Vars,
|
|
|
|
* so we try to optimize that case by extracting information about Vars
|
|
|
|
* in advance. Matching a parent tlist to a child is still an O(N^2)
|
|
|
|
* operation, but at least with a much smaller constant factor than plain
|
|
|
|
* tlist_member() searches.
|
|
|
|
*
|
|
|
|
* The result of this function is an indexed_tlist struct to pass to
|
|
|
|
* search_indexed_tlist_for_var() or search_indexed_tlist_for_non_var().
|
|
|
|
* When done, the indexed_tlist may be freed with a single pfree().
|
|
|
|
*/
|
|
|
|
static indexed_tlist *
|
|
|
|
build_tlist_index(List *tlist)
|
|
|
|
{
|
|
|
|
indexed_tlist *itlist;
|
|
|
|
tlist_vinfo *vinfo;
|
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
/* Create data structure with enough slots for all tlist entries */
|
|
|
|
itlist = (indexed_tlist *)
|
|
|
|
palloc(offsetof(indexed_tlist, vars) +
|
|
|
|
list_length(tlist) * sizeof(tlist_vinfo));
|
|
|
|
|
|
|
|
itlist->tlist = tlist;
|
|
|
|
itlist->has_ph_vars = false;
|
|
|
|
itlist->has_non_vars = false;
|
|
|
|
|
|
|
|
/* Find the Vars and fill in the index array */
|
|
|
|
vinfo = itlist->vars;
|
|
|
|
foreach(l, tlist)
|
|
|
|
{
|
|
|
|
TargetEntry *tle = (TargetEntry *) lfirst(l);
|
|
|
|
|
|
|
|
if (tle->expr && IsA(tle->expr, Var))
|
|
|
|
{
|
|
|
|
Var *var = (Var *) tle->expr;
|
|
|
|
|
|
|
|
vinfo->varno = var->varno;
|
|
|
|
vinfo->varattno = var->varattno;
|
|
|
|
vinfo->resno = tle->resno;
|
|
|
|
vinfo++;
|
|
|
|
}
|
|
|
|
else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
|
|
|
|
itlist->has_ph_vars = true;
|
|
|
|
else
|
|
|
|
itlist->has_non_vars = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
itlist->num_vars = (vinfo - itlist->vars);
|
|
|
|
|
|
|
|
return itlist;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* build_tlist_index_other_vars --- build a restricted tlist index
|
|
|
|
*
|
|
|
|
* This is like build_tlist_index, but we only index tlist entries that
|
|
|
|
* are Vars belonging to some rel other than the one specified. We will set
|
|
|
|
* has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars
|
|
|
|
* (so nothing other than Vars and PlaceHolderVars can be matched).
|
|
|
|
*/
|
|
|
|
static indexed_tlist *
|
|
|
|
build_tlist_index_other_vars(List *tlist, Index ignore_rel)
|
|
|
|
{
|
|
|
|
indexed_tlist *itlist;
|
|
|
|
tlist_vinfo *vinfo;
|
|
|
|
ListCell *l;
|
|
|
|
|
|
|
|
/* Create data structure with enough slots for all tlist entries */
|
|
|
|
itlist = (indexed_tlist *)
|
|
|
|
palloc(offsetof(indexed_tlist, vars) +
|
|
|
|
list_length(tlist) * sizeof(tlist_vinfo));
|
|
|
|
|
|
|
|
itlist->tlist = tlist;
|
|
|
|
itlist->has_ph_vars = false;
|
|
|
|
itlist->has_non_vars = false;
|
|
|
|
|
|
|
|
/* Find the desired Vars and fill in the index array */
|
|
|
|
vinfo = itlist->vars;
|
|
|
|
foreach(l, tlist)
|
|
|
|
{
|
|
|
|
TargetEntry *tle = (TargetEntry *) lfirst(l);
|
|
|
|
|
|
|
|
if (tle->expr && IsA(tle->expr, Var))
|
|
|
|
{
|
|
|
|
Var *var = (Var *) tle->expr;
|
|
|
|
|
|
|
|
if (var->varno != ignore_rel)
|
|
|
|
{
|
|
|
|
vinfo->varno = var->varno;
|
|
|
|
vinfo->varattno = var->varattno;
|
|
|
|
vinfo->resno = tle->resno;
|
|
|
|
vinfo++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
|
|
|
|
itlist->has_ph_vars = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
itlist->num_vars = (vinfo - itlist->vars);
|
|
|
|
|
|
|
|
return itlist;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* search_indexed_tlist_for_var --- find a Var in an indexed tlist
|
|
|
|
*
|
|
|
|
* If a match is found, return a copy of the given Var with suitably
|
|
|
|
* modified varno/varattno (to wit, newvarno and the resno of the TLE entry).
|
|
|
|
* Also ensure that varnoold is incremented by rtoffset.
|
|
|
|
* If no match, return NULL.
|
|
|
|
*/
|
|
|
|
static Var *
|
|
|
|
search_indexed_tlist_for_var(Var *var, indexed_tlist *itlist,
|
|
|
|
Index newvarno, int rtoffset)
|
|
|
|
{
|
|
|
|
Index varno = var->varno;
|
|
|
|
AttrNumber varattno = var->varattno;
|
|
|
|
tlist_vinfo *vinfo;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
vinfo = itlist->vars;
|
|
|
|
i = itlist->num_vars;
|
|
|
|
while (i-- > 0)
|
|
|
|
{
|
|
|
|
if (vinfo->varno == varno && vinfo->varattno == varattno)
|
|
|
|
{
|
|
|
|
/* Found a match */
|
|
|
|
Var *newvar = copyVar(var);
|
|
|
|
|
|
|
|
newvar->varno = newvarno;
|
|
|
|
newvar->varattno = vinfo->resno;
|
|
|
|
if (newvar->varnoold > 0)
|
|
|
|
newvar->varnoold += rtoffset;
|
|
|
|
return newvar;
|
|
|
|
}
|
|
|
|
vinfo++;
|
|
|
|
}
|
|
|
|
return NULL; /* no match */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* search_indexed_tlist_for_non_var --- find a non-Var in an indexed tlist
|
|
|
|
*
|
|
|
|
* If a match is found, return a Var constructed to reference the tlist item.
|
|
|
|
* If no match, return NULL.
|
|
|
|
*
|
|
|
|
* NOTE: it is a waste of time to call this unless itlist->has_ph_vars or
|
Fix incorrect matching of subexpressions in outer-join plan nodes.
Previously we would re-use input subexpressions in all expression trees
attached to a Join plan node. However, if it's an outer join and the
subexpression appears in the nullable-side input, this is potentially
incorrect for apparently-matching subexpressions that came from above
the outer join (ie, targetlist and qpqual expressions), because the
executor will treat the subexpression value as NULL when maybe it should
not be.
The case is fairly hard to hit because (a) you need a non-strict
subexpression (else NULL is correct), and (b) we don't usually compute
expressions in the outputs of non-toplevel plan nodes. But we might do
so if the expressions are sort keys for a mergejoin, for example.
Probably in the long run we should make a more explicit distinction between
Vars appearing above and below an outer join, but that will be a major
planner redesign and not at all back-patchable. For the moment, just hack
set_join_references so that it will not match any non-Var expressions
coming from nullable inputs to expressions that came from above the join.
(This is somewhat overkill, in that a strict expression could still be
matched, but it doesn't seem worth the effort to check that.)
Per report from Qingqing Zhou. The added regression test case is based
on his example.
This has been broken for a very long time, so back-patch to all active
branches.
11 years ago
|
|
|
* itlist->has_non_vars. Furthermore, set_join_references() relies on being
|
|
|
|
* able to prevent matching of non-Vars by clearing itlist->has_non_vars,
|
|
|
|
* so there's a correctness reason not to call it unless that's set.
|
|
|
|
*/
|
|
|
|
static Var *
|
|
|
|
search_indexed_tlist_for_non_var(Node *node,
|
|
|
|
indexed_tlist *itlist, Index newvarno)
|
|
|
|
{
|
|
|
|
TargetEntry *tle;
|
|
|
|
|
|
|
|
tle = tlist_member(node, itlist->tlist);
|
|
|
|
if (tle)
|
|
|
|
{
|
|
|
|
/* Found a matching subplan output expression */
|
|
|
|
Var *newvar;
|
|
|
|
|
|
|
|
newvar = makeVarFromTargetEntry(newvarno, tle);
|
|
|
|
newvar->varnoold = 0; /* wasn't ever a plain Var */
|
|
|
|
newvar->varoattno = 0;
|
|
|
|
return newvar;
|
|
|
|
}
|
|
|
|
return NULL; /* no match */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* search_indexed_tlist_for_sortgroupref --- find a sort/group expression
|
|
|
|
* (which is assumed not to be just a Var)
|
|
|
|
*
|
|
|
|
* If a match is found, return a Var constructed to reference the tlist item.
|
|
|
|
* If no match, return NULL.
|
|
|
|
*
|
|
|
|
* This is needed to ensure that we select the right subplan TLE in cases
|
|
|
|
* where there are multiple textually-equal()-but-volatile sort expressions.
|
|
|
|
* And it's also faster than search_indexed_tlist_for_non_var.
|
|
|
|
*/
|
|
|
|
static Var *
|
|
|
|
search_indexed_tlist_for_sortgroupref(Node *node,
|
|
|
|
Index sortgroupref,
|
|
|
|
indexed_tlist *itlist,
|
|
|
|
Index newvarno)
|
|
|
|
{
|
|
|
|
ListCell *lc;
|
|
|
|
|
|
|
|
foreach(lc, itlist->tlist)
|
|
|
|
{
|
|
|
|
TargetEntry *tle = (TargetEntry *) lfirst(lc);
|
|
|
|
|
|
|
|
/* The equal() check should be redundant, but let's be paranoid */
|
|
|
|
if (tle->ressortgroupref == sortgroupref &&
|
|
|
|
equal(node, tle->expr))
|
|
|
|
{
|
|
|
|
/* Found a matching subplan output expression */
|
|
|
|
Var *newvar;
|
|
|
|
|
|
|
|
newvar = makeVarFromTargetEntry(newvarno, tle);
|
|
|
|
newvar->varnoold = 0; /* wasn't ever a plain Var */
|
|
|
|
newvar->varoattno = 0;
|
|
|
|
return newvar;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL; /* no match */
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* fix_join_expr
|
|
|
|
* Create a new set of targetlist entries or join qual clauses by
|
|
|
|
* changing the varno/varattno values of variables in the clauses
|
|
|
|
* to reference target list values from the outer and inner join
|
|
|
|
* relation target lists. Also perform opcode lookup and add
|
|
|
|
* regclass OIDs to root->glob->relationOids.
|
|
|
|
*
|
|
|
|
* This is used in two different scenarios: a normal join clause, where all
|
|
|
|
* the Vars in the clause *must* be replaced by OUTER_VAR or INNER_VAR
|
|
|
|
* references; and a RETURNING clause, which may contain both Vars of the
|
|
|
|
* target relation and Vars of other relations. In the latter case we want
|
|
|
|
* to replace the other-relation Vars by OUTER_VAR references, while leaving
|
|
|
|
* target Vars alone.
|
|
|
|
*
|
|
|
|
* For a normal join, acceptable_rel should be zero so that any failure to
|
|
|
|
* match a Var will be reported as an error. For the RETURNING case, pass
|
|
|
|
* inner_itlist = NULL and acceptable_rel = the ID of the target relation.
|
|
|
|
*
|
|
|
|
* 'clauses' is the targetlist or list of join clauses
|
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.
11 years ago
|
|
|
* 'outer_itlist' is the indexed target list of the outer join relation,
|
|
|
|
* or NULL
|
|
|
|
* 'inner_itlist' is the indexed target list of the inner join relation,
|
|
|
|
* or NULL
|
|
|
|
* 'acceptable_rel' is either zero or the rangetable index of a relation
|
|
|
|
* whose Vars may appear in the clause without provoking an error
|
|
|
|
* 'rtoffset': how much to increment varnoold by
|
|
|
|
*
|
|
|
|
* Returns the new expression tree. The original clause structure is
|
|
|
|
* not modified.
|
|
|
|
*/
|
|
|
|
static List *
|
|
|
|
fix_join_expr(PlannerInfo *root,
|
|
|
|
List *clauses,
|
|
|
|
indexed_tlist *outer_itlist,
|
|
|
|
indexed_tlist *inner_itlist,
|
|
|
|
Index acceptable_rel,
|
|
|
|
int rtoffset)
|
|
|
|
{
|
|
|
|
fix_join_expr_context context;
|
|
|
|
|
|
|
|
context.root = root;
|
|
|
|
context.outer_itlist = outer_itlist;
|
|
|
|
context.inner_itlist = inner_itlist;
|
|
|
|
context.acceptable_rel = acceptable_rel;
|
|
|
|
context.rtoffset = rtoffset;
|
|
|
|
return (List *) fix_join_expr_mutator((Node *) clauses, &context);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Node *
|
|
|
|
fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
|
|
|
|
{
|
|
|
|
Var *newvar;
|
|
|
|
|
|
|
|
if (node == NULL)
|
|
|
|
return NULL;
|
|
|
|
if (IsA(node, Var))
|
|
|
|
{
|
|
|
|
Var *var = (Var *) node;
|
|
|
|
|
|
|
|
/* First look for the var in the input tlists */
|
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.
11 years ago
|
|
|
if (context->outer_itlist)
|
|
|
|
{
|
|
|
|
newvar = search_indexed_tlist_for_var(var,
|
|
|
|
context->outer_itlist,
|
|
|
|
OUTER_VAR,
|
|
|
|
context->rtoffset);
|
|
|
|
if (newvar)
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Then in the outer */
|
|
|
|
if (context->inner_itlist)
|
|
|
|
{
|
|
|
|
newvar = search_indexed_tlist_for_var(var,
|
|
|
|
context->inner_itlist,
|
|
|
|
INNER_VAR,
|
|
|
|
context->rtoffset);
|
|
|
|
if (newvar)
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If it's for acceptable_rel, adjust and return it */
|
|
|
|
if (var->varno == context->acceptable_rel)
|
|
|
|
{
|
|
|
|
var = copyVar(var);
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
var->varno += context->rtoffset;
|
|
|
|
if (var->varnoold > 0)
|
|
|
|
var->varnoold += context->rtoffset;
|
|
|
|
return (Node *) var;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No referent found for Var */
|
|
|
|
elog(ERROR, "variable not found in subplan target lists");
|
|
|
|
}
|
|
|
|
if (IsA(node, PlaceHolderVar))
|
|
|
|
{
|
|
|
|
PlaceHolderVar *phv = (PlaceHolderVar *) node;
|
|
|
|
|
|
|
|
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
|
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.
11 years ago
|
|
|
if (context->outer_itlist && context->outer_itlist->has_ph_vars)
|
|
|
|
{
|
|
|
|
newvar = search_indexed_tlist_for_non_var((Node *) phv,
|
|
|
|
context->outer_itlist,
|
|
|
|
OUTER_VAR);
|
|
|
|
if (newvar)
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
if (context->inner_itlist && context->inner_itlist->has_ph_vars)
|
|
|
|
{
|
|
|
|
newvar = search_indexed_tlist_for_non_var((Node *) phv,
|
|
|
|
context->inner_itlist,
|
|
|
|
INNER_VAR);
|
|
|
|
if (newvar)
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If not supplied by input plans, evaluate the contained expr */
|
|
|
|
return fix_join_expr_mutator((Node *) phv->phexpr, context);
|
|
|
|
}
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
if (IsA(node, Param))
|
|
|
|
return fix_param_node(context->root, (Param *) node);
|
|
|
|
/* Try matching more complex expressions too, if tlists have any */
|
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.
11 years ago
|
|
|
if (context->outer_itlist && context->outer_itlist->has_non_vars)
|
|
|
|
{
|
|
|
|
newvar = search_indexed_tlist_for_non_var(node,
|
|
|
|
context->outer_itlist,
|
|
|
|
OUTER_VAR);
|
|
|
|
if (newvar)
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
if (context->inner_itlist && context->inner_itlist->has_non_vars)
|
|
|
|
{
|
|
|
|
newvar = search_indexed_tlist_for_non_var(node,
|
|
|
|
context->inner_itlist,
|
|
|
|
INNER_VAR);
|
|
|
|
if (newvar)
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
fix_expr_common(context->root, node);
|
|
|
|
return expression_tree_mutator(node,
|
|
|
|
fix_join_expr_mutator,
|
|
|
|
(void *) context);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* fix_upper_expr
|
|
|
|
* Modifies an expression tree so that all Var nodes reference outputs
|
|
|
|
* of a subplan. Also performs opcode lookup, and adds regclass OIDs to
|
|
|
|
* root->glob->relationOids.
|
|
|
|
*
|
|
|
|
* This is used to fix up target and qual expressions of non-join upper-level
|
|
|
|
* plan nodes, as well as index-only scan nodes.
|
|
|
|
*
|
|
|
|
* An error is raised if no matching var can be found in the subplan tlist
|
|
|
|
* --- so this routine should only be applied to nodes whose subplans'
|
|
|
|
* targetlists were generated via flatten_tlist() or some such method.
|
|
|
|
*
|
|
|
|
* If itlist->has_non_vars is true, then we try to match whole subexpressions
|
|
|
|
* against elements of the subplan tlist, so that we can avoid recomputing
|
|
|
|
* expressions that were already computed by the subplan. (This is relatively
|
|
|
|
* expensive, so we don't want to try it in the common case where the
|
|
|
|
* subplan tlist is just a flattened list of Vars.)
|
|
|
|
*
|
|
|
|
* 'node': the tree to be fixed (a target item or qual)
|
|
|
|
* 'subplan_itlist': indexed target list for subplan (or index)
|
|
|
|
* 'newvarno': varno to use for Vars referencing tlist elements
|
|
|
|
* 'rtoffset': how much to increment varnoold by
|
|
|
|
*
|
|
|
|
* The resulting tree is a copy of the original in which all Var nodes have
|
|
|
|
* varno = newvarno, varattno = resno of corresponding targetlist element.
|
|
|
|
* The original tree is not modified.
|
|
|
|
*/
|
|
|
|
static Node *
|
|
|
|
fix_upper_expr(PlannerInfo *root,
|
|
|
|
Node *node,
|
|
|
|
indexed_tlist *subplan_itlist,
|
|
|
|
Index newvarno,
|
|
|
|
int rtoffset)
|
|
|
|
{
|
|
|
|
fix_upper_expr_context context;
|
|
|
|
|
|
|
|
context.root = root;
|
|
|
|
context.subplan_itlist = subplan_itlist;
|
|
|
|
context.newvarno = newvarno;
|
|
|
|
context.rtoffset = rtoffset;
|
|
|
|
return fix_upper_expr_mutator(node, &context);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Node *
|
|
|
|
fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
|
|
|
|
{
|
|
|
|
Var *newvar;
|
|
|
|
|
|
|
|
if (node == NULL)
|
|
|
|
return NULL;
|
|
|
|
if (IsA(node, Var))
|
|
|
|
{
|
|
|
|
Var *var = (Var *) node;
|
|
|
|
|
|
|
|
newvar = search_indexed_tlist_for_var(var,
|
|
|
|
context->subplan_itlist,
|
|
|
|
context->newvarno,
|
|
|
|
context->rtoffset);
|
|
|
|
if (!newvar)
|
|
|
|
elog(ERROR, "variable not found in subplan target list");
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
if (IsA(node, PlaceHolderVar))
|
|
|
|
{
|
|
|
|
PlaceHolderVar *phv = (PlaceHolderVar *) node;
|
|
|
|
|
|
|
|
/* See if the PlaceHolderVar has bubbled up from a lower plan node */
|
|
|
|
if (context->subplan_itlist->has_ph_vars)
|
|
|
|
{
|
|
|
|
newvar = search_indexed_tlist_for_non_var((Node *) phv,
|
|
|
|
context->subplan_itlist,
|
|
|
|
context->newvarno);
|
|
|
|
if (newvar)
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
/* If not supplied by input plan, evaluate the contained expr */
|
|
|
|
return fix_upper_expr_mutator((Node *) phv->phexpr, context);
|
|
|
|
}
|
Implement UPDATE tab SET (col1,col2,...) = (SELECT ...), ...
This SQL-standard feature allows a sub-SELECT yielding multiple columns
(but only one row) to be used to compute the new values of several columns
to be updated. While the same results can be had with an independent
sub-SELECT per column, such a workaround can require a great deal of
duplicated computation.
The standard actually says that the source for a multi-column assignment
could be any row-valued expression. The implementation used here is
tightly tied to our existing sub-SELECT support and can't handle other
cases; the Bison grammar would have some issues with them too. However,
I don't feel too bad about this since other cases can be converted into
sub-SELECTs. For instance, "SET (a,b,c) = row_valued_function(x)" could
be written "SET (a,b,c) = (SELECT * FROM row_valued_function(x))".
11 years ago
|
|
|
if (IsA(node, Param))
|
|
|
|
return fix_param_node(context->root, (Param *) node);
|
|
|
|
/* Try matching more complex expressions too, if tlist has any */
|
|
|
|
if (context->subplan_itlist->has_non_vars)
|
|
|
|
{
|
|
|
|
newvar = search_indexed_tlist_for_non_var(node,
|
|
|
|
context->subplan_itlist,
|
|
|
|
context->newvarno);
|
|
|
|
if (newvar)
|
|
|
|
return (Node *) newvar;
|
|
|
|
}
|
|
|
|
fix_expr_common(context->root, node);
|
|
|
|
return expression_tree_mutator(node,
|
|
|
|
fix_upper_expr_mutator,
|
|
|
|
(void *) context);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_returning_clause_references
|
|
|
|
* Perform setrefs.c's work on a RETURNING targetlist
|
|
|
|
*
|
|
|
|
* If the query involves more than just the result table, we have to
|
|
|
|
* adjust any Vars that refer to other tables to reference junk tlist
|
|
|
|
* entries in the top subplan's targetlist. Vars referencing the result
|
|
|
|
* table should be left alone, however (the executor will evaluate them
|
|
|
|
* using the actual heap tuple, after firing triggers if any). In the
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
* adjusted RETURNING list, result-table Vars will have their original
|
|
|
|
* varno (plus rtoffset), but Vars for other rels will have varno OUTER_VAR.
|
|
|
|
*
|
|
|
|
* We also must perform opcode lookup and add regclass OIDs to
|
|
|
|
* root->glob->relationOids.
|
|
|
|
*
|
|
|
|
* 'rlist': the RETURNING targetlist to be fixed
|
|
|
|
* 'topplan': the top subplan node that will be just below the ModifyTable
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
* node (note it's not yet passed through set_plan_refs)
|
|
|
|
* 'resultRelation': RT index of the associated result relation
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
* 'rtoffset': how much to increment varnos by
|
|
|
|
*
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
* Note: the given 'root' is for the parent query level, not the 'topplan'.
|
|
|
|
* This does not matter currently since we only access the dependency-item
|
|
|
|
* lists in root->glob, but it would need some hacking if we wanted a root
|
|
|
|
* that actually matches the subplan.
|
|
|
|
*
|
|
|
|
* Note: resultRelation is not yet adjusted by rtoffset.
|
|
|
|
*/
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
static List *
|
|
|
|
set_returning_clause_references(PlannerInfo *root,
|
|
|
|
List *rlist,
|
|
|
|
Plan *topplan,
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
Index resultRelation,
|
|
|
|
int rtoffset)
|
|
|
|
{
|
|
|
|
indexed_tlist *itlist;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We can perform the desired Var fixup by abusing the fix_join_expr
|
|
|
|
* machinery that formerly handled inner indexscan fixup. We search the
|
|
|
|
* top plan's targetlist for Vars of non-result relations, and use
|
|
|
|
* fix_join_expr to convert RETURNING Vars into references to those tlist
|
|
|
|
* entries, while leaving result-rel Vars as-is.
|
|
|
|
*
|
|
|
|
* PlaceHolderVars will also be sought in the targetlist, but no
|
|
|
|
* more-complex expressions will be. Note that it is not possible for a
|
|
|
|
* PlaceHolderVar to refer to the result relation, since the result is
|
|
|
|
* never below an outer join. If that case could happen, we'd have to be
|
|
|
|
* prepared to pick apart the PlaceHolderVar and evaluate its contained
|
|
|
|
* expression instead.
|
|
|
|
*/
|
|
|
|
itlist = build_tlist_index_other_vars(topplan->targetlist, resultRelation);
|
|
|
|
|
|
|
|
rlist = fix_join_expr(root,
|
|
|
|
rlist,
|
|
|
|
itlist,
|
|
|
|
NULL,
|
|
|
|
resultRelation,
|
Fix planner's handling of RETURNING lists in writable CTEs.
setrefs.c failed to do "rtoffset" adjustment of Vars in RETURNING lists,
which meant they were left with the wrong varnos when the RETURNING list
was in a subquery. That was never possible before writable CTEs, of
course, but now it's broken. The executor fails to notice any problem
because ExecEvalVar just references the ecxt_scantuple for any normal
varno; but EXPLAIN breaks when the varno is wrong, as illustrated in a
recent complaint from Bartosz Dmytrak.
Since the eventual rtoffset of the subquery is not known at the time
we are preparing its plan node, the previous scheme of executing
set_returning_clause_references() at that time cannot handle this
adjustment. Fortunately, it turns out that we don't really need to do it
that way, because all the needed information is available during normal
setrefs.c execution; we just have to dig it out of the ModifyTable node.
So, do that, and get rid of the kluge of early setrefs processing of
RETURNING lists. (This is a little bit of a cheat in the case of inherited
UPDATE/DELETE, because we are not passing a "root" struct that corresponds
exactly to what the subplan was built with. But that doesn't matter, and
anyway this is less ugly than early setrefs processing was.)
Back-patch to 9.1, where the problem became possible to hit.
14 years ago
|
|
|
rtoffset);
|
|
|
|
|
|
|
|
pfree(itlist);
|
|
|
|
|
|
|
|
return rlist;
|
|
|
|
}
|
|
|
|
|
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
10 years ago
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
* OPERATOR REGPROC LOOKUP
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* fix_opfuncids
|
|
|
|
* Calculate opfuncid field from opno for each OpExpr node in given tree.
|
|
|
|
* The given tree can be anything expression_tree_walker handles.
|
|
|
|
*
|
|
|
|
* The argument is modified in-place. (This is OK since we'd want the
|
|
|
|
* same change for any node, even if it gets visited more than once due to
|
|
|
|
* shared structure.)
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
fix_opfuncids(Node *node)
|
|
|
|
{
|
|
|
|
/* This tree walk requires no special setup, so away we go... */
|
|
|
|
fix_opfuncids_walker(node, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
fix_opfuncids_walker(Node *node, void *context)
|
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return false;
|
|
|
|
if (IsA(node, OpExpr))
|
|
|
|
set_opfuncid((OpExpr *) node);
|
|
|
|
else if (IsA(node, DistinctExpr))
|
|
|
|
set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
|
|
|
|
else if (IsA(node, NullIfExpr))
|
|
|
|
set_opfuncid((OpExpr *) node); /* rely on struct equivalence */
|
|
|
|
else if (IsA(node, ScalarArrayOpExpr))
|
|
|
|
set_sa_opfuncid((ScalarArrayOpExpr *) node);
|
|
|
|
return expression_tree_walker(node, fix_opfuncids_walker, context);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_opfuncid
|
|
|
|
* Set the opfuncid (procedure OID) in an OpExpr node,
|
|
|
|
* if it hasn't been set already.
|
|
|
|
*
|
|
|
|
* Because of struct equivalence, this can also be used for
|
|
|
|
* DistinctExpr and NullIfExpr nodes.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
set_opfuncid(OpExpr *opexpr)
|
|
|
|
{
|
|
|
|
if (opexpr->opfuncid == InvalidOid)
|
|
|
|
opexpr->opfuncid = get_opcode(opexpr->opno);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* set_sa_opfuncid
|
|
|
|
* As above, for ScalarArrayOpExpr nodes.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
set_sa_opfuncid(ScalarArrayOpExpr *opexpr)
|
|
|
|
{
|
|
|
|
if (opexpr->opfuncid == InvalidOid)
|
|
|
|
opexpr->opfuncid = get_opcode(opexpr->opno);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************
|
|
|
|
* QUERY DEPENDENCY MANAGEMENT
|
|
|
|
*****************************************************************************/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* record_plan_function_dependency
|
|
|
|
* Mark the current plan as depending on a particular function.
|
|
|
|
*
|
|
|
|
* This is exported so that the function-inlining code can record a
|
|
|
|
* dependency on a function that it's removed from the plan tree.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
record_plan_function_dependency(PlannerInfo *root, Oid funcid)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* For performance reasons, we don't bother to track built-in functions;
|
|
|
|
* we just assume they'll never change (or at least not in ways that'd
|
|
|
|
* invalidate plans using them). For this purpose we can consider a
|
|
|
|
* built-in function to be one with OID less than FirstBootstrapObjectId.
|
|
|
|
* Note that the OID generator guarantees never to generate such an OID
|
|
|
|
* after startup, even at OID wraparound.
|
|
|
|
*/
|
|
|
|
if (funcid >= (Oid) FirstBootstrapObjectId)
|
|
|
|
{
|
|
|
|
PlanInvalItem *inval_item = makeNode(PlanInvalItem);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It would work to use any syscache on pg_proc, but the easiest is
|
|
|
|
* PROCOID since we already have the function's OID at hand. Note
|
|
|
|
* that plancache.c knows we use PROCOID.
|
|
|
|
*/
|
|
|
|
inval_item->cacheId = PROCOID;
|
|
|
|
inval_item->hashValue = GetSysCacheHashValue1(PROCOID,
|
|
|
|
ObjectIdGetDatum(funcid));
|
|
|
|
|
|
|
|
root->glob->invalItems = lappend(root->glob->invalItems, inval_item);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* extract_query_dependencies
|
|
|
|
* Given a not-yet-planned query or queries (i.e. a Query node or list
|
|
|
|
* of Query nodes), extract dependencies just as set_plan_references
|
|
|
|
* would do.
|
|
|
|
*
|
|
|
|
* This is needed by plancache.c to handle invalidation of cached unplanned
|
|
|
|
* queries.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
extract_query_dependencies(Node *query,
|
|
|
|
List **relationOids,
|
Row-Level Security Policies (RLS)
Building on the updatable security-barrier views work, add the
ability to define policies on tables to limit the set of rows
which are returned from a query and which are allowed to be added
to a table. Expressions defined by the policy for filtering are
added to the security barrier quals of the query, while expressions
defined to check records being added to a table are added to the
with-check options of the query.
New top-level commands are CREATE/ALTER/DROP POLICY and are
controlled by the table owner. Row Security is able to be enabled
and disabled by the owner on a per-table basis using
ALTER TABLE .. ENABLE/DISABLE ROW SECURITY.
Per discussion, ROW SECURITY is disabled on tables by default and
must be enabled for policies on the table to be used. If no
policies exist on a table with ROW SECURITY enabled, a default-deny
policy is used and no records will be visible.
By default, row security is applied at all times except for the
table owner and the superuser. A new GUC, row_security, is added
which can be set to ON, OFF, or FORCE. When set to FORCE, row
security will be applied even for the table owner and superusers.
When set to OFF, row security will be disabled when allowed and an
error will be thrown if the user does not have rights to bypass row
security.
Per discussion, pg_dump sets row_security = OFF by default to ensure
that exports and backups will have all data in the table or will
error if there are insufficient privileges to bypass row security.
A new option has been added to pg_dump, --enable-row-security, to
ask pg_dump to export with row security enabled.
A new role capability, BYPASSRLS, which can only be set by the
superuser, is added to allow other users to be able to bypass row
security using row_security = OFF.
Many thanks to the various individuals who have helped with the
design, particularly Robert Haas for his feedback.
Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean
Rasheed, with additional changes and rework by me.
Reviewers have included all of the above, Greg Smith,
Jeff McCormick, and Robert Haas.
11 years ago
|
|
|
List **invalItems,
|
|
|
|
bool *hasRowSecurity)
|
|
|
|
{
|
|
|
|
PlannerGlobal glob;
|
|
|
|
PlannerInfo root;
|
|
|
|
|
|
|
|
/* Make up dummy planner state so we can use this module's machinery */
|
|
|
|
MemSet(&glob, 0, sizeof(glob));
|
|
|
|
glob.type = T_PlannerGlobal;
|
|
|
|
glob.relationOids = NIL;
|
|
|
|
glob.invalItems = NIL;
|
Rename pg_rowsecurity -> pg_policy and other fixes
As pointed out by Robert, we should really have named pg_rowsecurity
pg_policy, as the objects stored in that catalog are policies. This
patch fixes that and updates the column names to start with 'pol' to
match the new catalog name.
The security consideration for COPY with row level security, also
pointed out by Robert, has also been addressed by remembering and
re-checking the OID of the relation initially referenced during COPY
processing, to make sure it hasn't changed under us by the time we
finish planning out the query which has been built.
Robert and Alvaro also commented on missing OCLASS and OBJECT entries
for POLICY (formerly ROWSECURITY or POLICY, depending) in various
places. This patch fixes that too, which also happens to add the
ability to COMMENT on policies.
In passing, attempt to improve the consistency of messages, comments,
and documentation as well. This removes various incarnations of
'row-security', 'row-level security', 'Row-security', etc, in favor
of 'policy', 'row level security' or 'row_security' as appropriate.
Happy Thanksgiving!
11 years ago
|
|
|
glob.hasRowSecurity = false;
|
|
|
|
|
|
|
|
MemSet(&root, 0, sizeof(root));
|
|
|
|
root.type = T_PlannerInfo;
|
|
|
|
root.glob = &glob;
|
|
|
|
|
|
|
|
(void) extract_query_dependencies_walker(query, &root);
|
|
|
|
|
|
|
|
*relationOids = glob.relationOids;
|
|
|
|
*invalItems = glob.invalItems;
|
Rename pg_rowsecurity -> pg_policy and other fixes
As pointed out by Robert, we should really have named pg_rowsecurity
pg_policy, as the objects stored in that catalog are policies. This
patch fixes that and updates the column names to start with 'pol' to
match the new catalog name.
The security consideration for COPY with row level security, also
pointed out by Robert, has also been addressed by remembering and
re-checking the OID of the relation initially referenced during COPY
processing, to make sure it hasn't changed under us by the time we
finish planning out the query which has been built.
Robert and Alvaro also commented on missing OCLASS and OBJECT entries
for POLICY (formerly ROWSECURITY or POLICY, depending) in various
places. This patch fixes that too, which also happens to add the
ability to COMMENT on policies.
In passing, attempt to improve the consistency of messages, comments,
and documentation as well. This removes various incarnations of
'row-security', 'row-level security', 'Row-security', etc, in favor
of 'policy', 'row level security' or 'row_security' as appropriate.
Happy Thanksgiving!
11 years ago
|
|
|
*hasRowSecurity = glob.hasRowSecurity;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
extract_query_dependencies_walker(Node *node, PlannerInfo *context)
|
|
|
|
{
|
|
|
|
if (node == NULL)
|
|
|
|
return false;
|
|
|
|
Assert(!IsA(node, PlaceHolderVar));
|
|
|
|
/* Extract function dependencies and check for regclass Consts */
|
|
|
|
fix_expr_common(context, node);
|
|
|
|
if (IsA(node, Query))
|
|
|
|
{
|
|
|
|
Query *query = (Query *) node;
|
|
|
|
ListCell *lc;
|
|
|
|
|
Rename pg_rowsecurity -> pg_policy and other fixes
As pointed out by Robert, we should really have named pg_rowsecurity
pg_policy, as the objects stored in that catalog are policies. This
patch fixes that and updates the column names to start with 'pol' to
match the new catalog name.
The security consideration for COPY with row level security, also
pointed out by Robert, has also been addressed by remembering and
re-checking the OID of the relation initially referenced during COPY
processing, to make sure it hasn't changed under us by the time we
finish planning out the query which has been built.
Robert and Alvaro also commented on missing OCLASS and OBJECT entries
for POLICY (formerly ROWSECURITY or POLICY, depending) in various
places. This patch fixes that too, which also happens to add the
ability to COMMENT on policies.
In passing, attempt to improve the consistency of messages, comments,
and documentation as well. This removes various incarnations of
'row-security', 'row-level security', 'Row-security', etc, in favor
of 'policy', 'row level security' or 'row_security' as appropriate.
Happy Thanksgiving!
11 years ago
|
|
|
/* Collect row security information */
|
|
|
|
context->glob->hasRowSecurity = query->hasRowSecurity;
|
Row-Level Security Policies (RLS)
Building on the updatable security-barrier views work, add the
ability to define policies on tables to limit the set of rows
which are returned from a query and which are allowed to be added
to a table. Expressions defined by the policy for filtering are
added to the security barrier quals of the query, while expressions
defined to check records being added to a table are added to the
with-check options of the query.
New top-level commands are CREATE/ALTER/DROP POLICY and are
controlled by the table owner. Row Security is able to be enabled
and disabled by the owner on a per-table basis using
ALTER TABLE .. ENABLE/DISABLE ROW SECURITY.
Per discussion, ROW SECURITY is disabled on tables by default and
must be enabled for policies on the table to be used. If no
policies exist on a table with ROW SECURITY enabled, a default-deny
policy is used and no records will be visible.
By default, row security is applied at all times except for the
table owner and the superuser. A new GUC, row_security, is added
which can be set to ON, OFF, or FORCE. When set to FORCE, row
security will be applied even for the table owner and superusers.
When set to OFF, row security will be disabled when allowed and an
error will be thrown if the user does not have rights to bypass row
security.
Per discussion, pg_dump sets row_security = OFF by default to ensure
that exports and backups will have all data in the table or will
error if there are insufficient privileges to bypass row security.
A new option has been added to pg_dump, --enable-row-security, to
ask pg_dump to export with row security enabled.
A new role capability, BYPASSRLS, which can only be set by the
superuser, is added to allow other users to be able to bypass row
security using row_security = OFF.
Many thanks to the various individuals who have helped with the
design, particularly Robert Haas for his feedback.
Authors include Craig Ringer, KaiGai Kohei, Adam Brightwell, Dean
Rasheed, with additional changes and rework by me.
Reviewers have included all of the above, Greg Smith,
Jeff McCormick, and Robert Haas.
11 years ago
|
|
|
|
|
|
|
if (query->commandType == CMD_UTILITY)
|
|
|
|
{
|
Restructure SELECT INTO's parsetree representation into CreateTableAsStmt.
Making this operation look like a utility statement seems generally a good
idea, and particularly so in light of the desire to provide command
triggers for utility statements. The original choice of representing it as
SELECT with an IntoClause appendage had metastasized into rather a lot of
places, unfortunately, so that this patch is a great deal more complicated
than one might at first expect.
In particular, keeping EXPLAIN working for SELECT INTO and CREATE TABLE AS
subcommands required restructuring some EXPLAIN-related APIs. Add-on code
that calls ExplainOnePlan or ExplainOneUtility, or uses
ExplainOneQuery_hook, will need adjustment.
Also, the cases PREPARE ... SELECT INTO and CREATE RULE ... SELECT INTO,
which formerly were accepted though undocumented, are no longer accepted.
The PREPARE case can be replaced with use of CREATE TABLE AS EXECUTE.
The CREATE RULE case doesn't seem to have much real-world use (since the
rule would work only once before failing with "table already exists"),
so we'll not bother with that one.
Both SELECT INTO and CREATE TABLE AS still return a command tag of
"SELECT nnnn". There was some discussion of returning "CREATE TABLE nnnn",
but for the moment backwards compatibility wins the day.
Andres Freund and Tom Lane
14 years ago
|
|
|
/*
|
|
|
|
* Ignore utility statements, except those (such as EXPLAIN) that
|
|
|
|
* contain a parsed-but-not-planned query.
|
|
|
|
*/
|
|
|
|
query = UtilityContainsQuery(query->utilityStmt);
|
|
|
|
if (query == NULL)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Collect relation OIDs in this Query's rtable */
|
|
|
|
foreach(lc, query->rtable)
|
|
|
|
{
|
|
|
|
RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
|
|
|
|
|
|
|
|
if (rte->rtekind == RTE_RELATION)
|
|
|
|
context->glob->relationOids =
|
|
|
|
lappend_oid(context->glob->relationOids, rte->relid);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* And recurse into the query's subexpressions */
|
|
|
|
return query_tree_walker(query, extract_query_dependencies_walker,
|
|
|
|
(void *) context, 0);
|
|
|
|
}
|
|
|
|
return expression_tree_walker(node, extract_query_dependencies_walker,
|
|
|
|
(void *) context);
|
|
|
|
}
|