summaryrefslogtreecommitdiffstats
path: root/src/backend/optimizer
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r--src/backend/optimizer/prep/prepjointree.c9
-rw-r--r--src/backend/optimizer/util/clauses.c66
-rw-r--r--src/backend/optimizer/util/pathnode.c175
3 files changed, 248 insertions, 2 deletions
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 7f4bb7b..ea05763 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -2471,8 +2471,13 @@ pullup_replace_vars_callback(Var *var,
else if (newnode && IsA(newnode, PlaceHolderVar) &&
((PlaceHolderVar *) newnode)->phlevelsup == 0)
{
- /* No need to wrap a PlaceHolderVar with another one, either */
- wrap = false;
+ /* The same rules apply for a PlaceHolderVar */
+ if (rcon->target_rte->lateral &&
+ !bms_is_subset(((PlaceHolderVar *) newnode)->phrels,
+ rcon->relids))
+ wrap = true;
+ else
+ wrap = false;
}
else if (rcon->wrap_non_vars)
{
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index f2216f5..e1cedd9 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -357,6 +357,11 @@ contain_subplans_walker(Node *node, void *context)
* mistakenly think that something like "WHERE random() < 0.5" can be treated
* as a constant qualification.
*
+ * This will give the right answer only for clauses that have been put
+ * through expression preprocessing. Callers outside the planner typically
+ * should use contain_mutable_functions_after_planning() instead, for the
+ * reasons given there.
+ *
* We will recursively look into Query nodes (i.e., SubLink sub-selects)
* but not into SubPlans. See comments for contain_volatile_functions().
*/
@@ -416,6 +421,34 @@ contain_mutable_functions_walker(Node *node, void *context)
context);
}
+/*
+ * contain_mutable_functions_after_planning
+ * Test whether given expression contains mutable functions.
+ *
+ * This is a wrapper for contain_mutable_functions() that is safe to use from
+ * outside the planner. The difference is that it first runs the expression
+ * through expression_planner(). There are two key reasons why we need that:
+ *
+ * First, function default arguments will get inserted, which may affect
+ * volatility (consider "default now()").
+ *
+ * Second, inline-able functions will get inlined, which may allow us to
+ * conclude that the function is really less volatile than it's marked.
+ * As an example, polymorphic functions must be marked with the most volatile
+ * behavior that they have for any input type, but once we inline the
+ * function we may be able to conclude that it's not so volatile for the
+ * particular input type we're dealing with.
+ */
+bool
+contain_mutable_functions_after_planning(Expr *expr)
+{
+ /* We assume here that expression_planner() won't scribble on its input */
+ expr = expression_planner(expr);
+
+ /* Now we can search for non-immutable functions */
+ return contain_mutable_functions((Node *) expr);
+}
+
/*****************************************************************************
* Check clauses for volatile functions
@@ -429,6 +462,11 @@ contain_mutable_functions_walker(Node *node, void *context)
* volatile function) is found. This test prevents, for example,
* invalid conversions of volatile expressions into indexscan quals.
*
+ * This will give the right answer only for clauses that have been put
+ * through expression preprocessing. Callers outside the planner typically
+ * should use contain_volatile_functions_after_planning() instead, for the
+ * reasons given there.
+ *
* We will recursively look into Query nodes (i.e., SubLink sub-selects)
* but not into SubPlans. This is a bit odd, but intentional. If we are
* looking at a SubLink, we are probably deciding whether a query tree
@@ -553,6 +591,34 @@ contain_volatile_functions_walker(Node *node, void *context)
}
/*
+ * contain_volatile_functions_after_planning
+ * Test whether given expression contains volatile functions.
+ *
+ * This is a wrapper for contain_volatile_functions() that is safe to use from
+ * outside the planner. The difference is that it first runs the expression
+ * through expression_planner(). There are two key reasons why we need that:
+ *
+ * First, function default arguments will get inserted, which may affect
+ * volatility (consider "default random()").
+ *
+ * Second, inline-able functions will get inlined, which may allow us to
+ * conclude that the function is really less volatile than it's marked.
+ * As an example, polymorphic functions must be marked with the most volatile
+ * behavior that they have for any input type, but once we inline the
+ * function we may be able to conclude that it's not so volatile for the
+ * particular input type we're dealing with.
+ */
+bool
+contain_volatile_functions_after_planning(Expr *expr)
+{
+ /* We assume here that expression_planner() won't scribble on its input */
+ expr = expression_planner(expr);
+
+ /* Now we can search for volatile functions */
+ return contain_volatile_functions((Node *) expr);
+}
+
+/*
* Special purpose version of contain_volatile_functions() for use in COPY:
* ignore nextval(), but treat all other functions normally.
*/
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 33affaf..46fd29b 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -26,6 +26,7 @@
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
+#include "optimizer/placeholder.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
@@ -56,6 +57,10 @@ static int append_startup_cost_compare(const ListCell *a, const ListCell *b);
static List *reparameterize_pathlist_by_child(PlannerInfo *root,
List *pathlist,
RelOptInfo *child_rel);
+static bool contain_references_to(PlannerInfo *root, Node *clause,
+ Relids relids);
+static bool ris_contain_references_to(PlannerInfo *root, List *rinfos,
+ Relids relids);
/*****************************************************************************
@@ -4052,6 +4057,40 @@ do { \
switch (nodeTag(path))
{
case T_Path:
+
+ /*
+ * If the path's restriction clauses contain lateral references to
+ * the other relation, we can't reparameterize, because we must
+ * not change the RelOptInfo's contents here. (Doing so would
+ * break things if we end up using a non-partitionwise join.)
+ */
+ if (ris_contain_references_to(root,
+ path->parent->baserestrictinfo,
+ child_rel->top_parent_relids))
+ return NULL;
+
+ /*
+ * If it's a SampleScan with tablesample parameters referencing
+ * the other relation, we can't reparameterize, because we must
+ * not change the RTE's contents here. (Doing so would break
+ * things if we end up using a non-partitionwise join.)
+ */
+ if (path->pathtype == T_SampleScan)
+ {
+ Index scan_relid = path->parent->relid;
+ RangeTblEntry *rte;
+
+ /* it should be a base rel with a tablesample clause... */
+ Assert(scan_relid > 0);
+ rte = planner_rt_fetch(scan_relid, root);
+ Assert(rte->rtekind == RTE_RELATION);
+ Assert(rte->tablesample != NULL);
+
+ if (contain_references_to(root, (Node *) rte->tablesample,
+ child_rel->top_parent_relids))
+ return NULL;
+ }
+
FLAT_COPY_PATH(new_path, path, Path);
break;
@@ -4059,6 +4098,18 @@ do { \
{
IndexPath *ipath;
+ /*
+ * If the path's restriction clauses contain lateral
+ * references to the other relation, we can't reparameterize,
+ * because we must not change the IndexOptInfo's contents
+ * here. (Doing so would break things if we end up using a
+ * non-partitionwise join.)
+ */
+ if (ris_contain_references_to(root,
+ path->parent->baserestrictinfo,
+ child_rel->top_parent_relids))
+ return NULL;
+
FLAT_COPY_PATH(ipath, path, IndexPath);
ADJUST_CHILD_ATTRS(ipath->indexclauses);
new_path = (Path *) ipath;
@@ -4069,6 +4120,18 @@ do { \
{
BitmapHeapPath *bhpath;
+ /*
+ * If the path's restriction clauses contain lateral
+ * references to the other relation, we can't reparameterize,
+ * because we must not change the RelOptInfo's contents here.
+ * (Doing so would break things if we end up using a
+ * non-partitionwise join.)
+ */
+ if (ris_contain_references_to(root,
+ path->parent->baserestrictinfo,
+ child_rel->top_parent_relids))
+ return NULL;
+
FLAT_COPY_PATH(bhpath, path, BitmapHeapPath);
REPARAMETERIZE_CHILD_PATH(bhpath->bitmapqual);
new_path = (Path *) bhpath;
@@ -4100,6 +4163,18 @@ do { \
ForeignPath *fpath;
ReparameterizeForeignPathByChild_function rfpc_func;
+ /*
+ * If the path's restriction clauses contain lateral
+ * references to the other relation, we can't reparameterize,
+ * because we must not change the RelOptInfo's contents here.
+ * (Doing so would break things if we end up using a
+ * non-partitionwise join.)
+ */
+ if (ris_contain_references_to(root,
+ path->parent->baserestrictinfo,
+ child_rel->top_parent_relids))
+ return NULL;
+
FLAT_COPY_PATH(fpath, path, ForeignPath);
if (fpath->fdw_outerpath)
REPARAMETERIZE_CHILD_PATH(fpath->fdw_outerpath);
@@ -4118,6 +4193,18 @@ do { \
{
CustomPath *cpath;
+ /*
+ * If the path's restriction clauses contain lateral
+ * references to the other relation, we can't reparameterize,
+ * because we must not change the RelOptInfo's contents here.
+ * (Doing so would break things if we end up using a
+ * non-partitionwise join.)
+ */
+ if (ris_contain_references_to(root,
+ path->parent->baserestrictinfo,
+ child_rel->top_parent_relids))
+ return NULL;
+
FLAT_COPY_PATH(cpath, path, CustomPath);
REPARAMETERIZE_CHILD_PATH_LIST(cpath->custom_paths);
if (cpath->methods &&
@@ -4296,3 +4383,91 @@ reparameterize_pathlist_by_child(PlannerInfo *root,
return result;
}
+
+/*
+ * contain_references_to
+ * Detect whether any Vars or PlaceHolderVars in the given clause contain
+ * lateral references to the given 'relids'.
+ */
+static bool
+contain_references_to(PlannerInfo *root, Node *clause, Relids relids)
+{
+ bool ret = false;
+ List *vars;
+ ListCell *lc;
+
+ /*
+ * Examine all Vars and PlaceHolderVars used in the clause.
+ *
+ * By omitting the relevant flags, this also gives us a cheap sanity check
+ * that no aggregates or window functions appear in the clause. We don't
+ * expect any of those in scan-level restrictions or tablesamples.
+ */
+ vars = pull_var_clause(clause, PVC_INCLUDE_PLACEHOLDERS);
+ foreach(lc, vars)
+ {
+ Node *node = (Node *) lfirst(lc);
+
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+
+ if (bms_is_member(var->varno, relids))
+ {
+ ret = true;
+ break;
+ }
+ }
+ else if (IsA(node, PlaceHolderVar))
+ {
+ PlaceHolderVar *phv = (PlaceHolderVar *) node;
+ PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, false);
+
+ /*
+ * We should check both ph_eval_at (in case the PHV is to be
+ * computed at the other relation and then laterally referenced
+ * here) and ph_lateral (in case the PHV is to be evaluated here
+ * but contains lateral references to the other relation). The
+ * former case should not occur in baserestrictinfo clauses, but
+ * it can occur in tablesample clauses.
+ */
+ if (bms_overlap(phinfo->ph_eval_at, relids) ||
+ bms_overlap(phinfo->ph_lateral, relids))
+ {
+ ret = true;
+ break;
+ }
+ }
+ else
+ Assert(false);
+ }
+
+ list_free(vars);
+
+ return ret;
+}
+
+/*
+ * ris_contain_references_to
+ * Apply contain_references_to() to a list of RestrictInfos.
+ *
+ * We need extra code for this because pull_var_clause() can't descend
+ * through RestrictInfos.
+ */
+static bool
+ris_contain_references_to(PlannerInfo *root, List *rinfos, Relids relids)
+{
+ ListCell *lc;
+
+ foreach(lc, rinfos)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
+
+ /* Pseudoconstant clauses can't contain any Vars or PHVs */
+ if (rinfo->pseudoconstant)
+ continue;
+ if (contain_references_to(root, (Node *) rinfo->clause, relids))
+ return true;
+ }
+ return false;
+}