* this allows
* WHERE ctid IN (tid1, tid2, ...)
*
+ * As with indexscans, our definition of "pseudoconstant" is pretty liberal:
+ * we allow anything that doesn't involve a volatile function or a Var of
+ * the relation under consideration. Vars belonging to other relations of
+ * the query are allowed, giving rise to parameterized TID scans.
+ *
* We also support "WHERE CURRENT OF cursor" conditions (CurrentOfExpr),
* which amount to "CTID = run-time-determined-TID". These could in
* theory be translated to a simple comparison of CTID to the result of
* a function, but in practice it works better to keep the special node
* representation all the way through to execution.
*
- * There is currently no special support for joins involving CTID; in
- * particular nothing corresponding to best_inner_indexscan(). Since it's
- * not very useful to store TIDs of one table in another table, there
- * doesn't seem to be enough use-case to justify adding a lot of code
- * for that.
- *
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/restrictinfo.h"
+#include "optimizer/var.h"
-static bool IsTidEqualClause(OpExpr *node, int varno);
-static bool IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno);
-static List *TidQualFromExpr(Node *expr, int varno);
-static List *TidQualFromBaseRestrictinfo(RelOptInfo *rel);
-
+/*
+ * Does this Var represent the CTID column of the specified baserel?
+ */
+static inline bool
+IsCTIDVar(Var *var, RelOptInfo *rel)
+{
+ /* The vartype check is strictly paranoia */
+ if (var->varattno == SelfItemPointerAttributeNumber &&
+ var->vartype == TIDOID &&
+ var->varno == rel->relid &&
+ var->varlevelsup == 0)
+ return true;
+ return false;
+}
/*
- * Check to see if an opclause is of the form
+ * Check to see if a RestrictInfo is of the form
* CTID = pseudoconstant
* or
* pseudoconstant = CTID
- *
- * We check that the CTID Var belongs to relation "varno". That is probably
- * redundant considering this is only applied to restriction clauses, but
- * let's be safe.
+ * where the CTID Var belongs to relation "rel", and nothing on the
+ * other side of the clause does.
*/
static bool
-IsTidEqualClause(OpExpr *node, int varno)
+IsTidEqualClause(RestrictInfo *rinfo, RelOptInfo *rel)
{
+ OpExpr *node;
Node *arg1,
*arg2,
*other;
- Var *var;
+ Relids other_relids;
+
+ /* Must be an OpExpr */
+ if (!is_opclause(rinfo->clause))
+ return false;
+ node = (OpExpr *) rinfo->clause;
/* Operator must be tideq */
if (node->opno != TIDEqualOperator)
return false;
- if (list_length(node->args) != 2)
- return false;
+ Assert(list_length(node->args) == 2);
arg1 = linitial(node->args);
arg2 = lsecond(node->args);
/* Look for CTID as either argument */
other = NULL;
- if (arg1 && IsA(arg1, Var))
+ other_relids = NULL;
+ if (arg1 && IsA(arg1, Var) &&
+ IsCTIDVar((Var *) arg1, rel))
{
- var = (Var *) arg1;
- if (var->varattno == SelfItemPointerAttributeNumber &&
- var->vartype == TIDOID &&
- var->varno == varno &&
- var->varlevelsup == 0)
- other = arg2;
+ other = arg2;
+ other_relids = rinfo->right_relids;
}
- if (!other && arg2 && IsA(arg2, Var))
+ if (!other && arg2 && IsA(arg2, Var) &&
+ IsCTIDVar((Var *) arg2, rel))
{
- var = (Var *) arg2;
- if (var->varattno == SelfItemPointerAttributeNumber &&
- var->vartype == TIDOID &&
- var->varno == varno &&
- var->varlevelsup == 0)
- other = arg1;
+ other = arg1;
+ other_relids = rinfo->left_relids;
}
if (!other)
return false;
- if (exprType(other) != TIDOID)
- return false; /* probably can't happen */
/* The other argument must be a pseudoconstant */
- if (!is_pseudo_constant_clause(other))
+ if (bms_is_member(rel->relid, other_relids) ||
+ contain_volatile_functions(other))
return false;
return true; /* success */
}
/*
- * Check to see if a clause is of the form
+ * Check to see if a RestrictInfo is of the form
* CTID = ANY (pseudoconstant_array)
+ * where the CTID Var belongs to relation "rel", and nothing on the
+ * other side of the clause does.
*/
static bool
-IsTidEqualAnyClause(ScalarArrayOpExpr *node, int varno)
+IsTidEqualAnyClause(RestrictInfo *rinfo, RelOptInfo *rel)
{
+ ScalarArrayOpExpr *node;
Node *arg1,
*arg2;
+ /* Must be a ScalarArrayOpExpr */
+ if (!(rinfo->clause && IsA(rinfo->clause, ScalarArrayOpExpr)))
+ return false;
+ node = (ScalarArrayOpExpr *) rinfo->clause;
+
/* Operator must be tideq */
if (node->opno != TIDEqualOperator)
return false;
arg2 = lsecond(node->args);
/* CTID must be first argument */
- if (arg1 && IsA(arg1, Var))
+ if (arg1 && IsA(arg1, Var) &&
+ IsCTIDVar((Var *) arg1, rel))
{
- Var *var = (Var *) arg1;
+ /* The other argument must be a pseudoconstant */
+ if (bms_is_member(rel->relid, pull_varnos(arg2)) ||
+ contain_volatile_functions(arg2))
+ return false;
- if (var->varattno == SelfItemPointerAttributeNumber &&
- var->vartype == TIDOID &&
- var->varno == varno &&
- var->varlevelsup == 0)
- {
- /* The other argument must be a pseudoconstant */
- if (is_pseudo_constant_clause(arg2))
- return true; /* success */
- }
+ return true; /* success */
}
return false;
}
/*
- * Extract a set of CTID conditions from the given qual expression
+ * Check to see if a RestrictInfo is a CurrentOfExpr referencing "rel".
+ */
+static bool
+IsCurrentOfClause(RestrictInfo *rinfo, RelOptInfo *rel)
+{
+ CurrentOfExpr *node;
+
+ /* Must be a CurrentOfExpr */
+ if (!(rinfo->clause && IsA(rinfo->clause, CurrentOfExpr)))
+ return false;
+ node = (CurrentOfExpr *) rinfo->clause;
+
+ /* If it references this rel, we're good */
+ if (node->cvarno == rel->relid)
+ return true;
+
+ return false;
+}
+
+/*
+ * Extract a set of CTID conditions from the given RestrictInfo
+ *
+ * Returns a List of CTID qual RestrictInfos for the specified rel (with
+ * implicit OR semantics across the list), or NIL if there are no usable
+ * conditions.
*
- * Returns a List of CTID qual expressions (with implicit OR semantics
- * across the list), or NIL if there are no usable conditions.
+ * This function considers only base cases; AND/OR combination is handled
+ * below. Therefore the returned List never has more than one element.
+ * (Using a List may seem a bit weird, but it simplifies the caller.)
+ */
+static List *
+TidQualFromRestrictInfo(RestrictInfo *rinfo, RelOptInfo *rel)
+{
+ /*
+ * We may ignore pseudoconstant clauses (they can't contain Vars, so could
+ * not match anyway).
+ */
+ if (rinfo->pseudoconstant)
+ return NIL;
+
+ /*
+ * If clause must wait till after some lower-security-level restriction
+ * clause, reject it.
+ */
+ if (!restriction_is_securely_promotable(rinfo, rel))
+ return NIL;
+
+ /*
+ * Check all base cases. If we get a match, return the clause.
+ */
+ if (IsTidEqualClause(rinfo, rel) ||
+ IsTidEqualAnyClause(rinfo, rel) ||
+ IsCurrentOfClause(rinfo, rel))
+ return list_make1(rinfo);
+
+ return NIL;
+}
+
+/*
+ * Extract a set of CTID conditions from implicit-AND List of RestrictInfos
*
- * If the expression is an AND clause, we can use a CTID condition
- * from any sub-clause. If it is an OR clause, we must be able to
- * extract a CTID condition from every sub-clause, or we can't use it.
+ * Returns a List of CTID qual RestrictInfos for the specified rel (with
+ * implicit OR semantics across the list), or NIL if there are no usable
+ * conditions.
*
- * In theory, in the AND case we could get CTID conditions from different
- * sub-clauses, in which case we could try to pick the most efficient one.
- * In practice, such usage seems very unlikely, so we don't bother; we
- * just exit as soon as we find the first candidate.
+ * This function is just concerned with handling AND/OR recursion.
*/
static List *
-TidQualFromExpr(Node *expr, int varno)
+TidQualFromRestrictInfoList(List *rlist, RelOptInfo *rel)
{
List *rlst = NIL;
ListCell *l;
- if (is_opclause(expr))
- {
- /* base case: check for tideq opclause */
- if (IsTidEqualClause((OpExpr *) expr, varno))
- rlst = list_make1(expr);
- }
- else if (expr && IsA(expr, ScalarArrayOpExpr))
- {
- /* another base case: check for tid = ANY clause */
- if (IsTidEqualAnyClause((ScalarArrayOpExpr *) expr, varno))
- rlst = list_make1(expr);
- }
- else if (expr && IsA(expr, CurrentOfExpr))
- {
- /* another base case: check for CURRENT OF on this rel */
- if (((CurrentOfExpr *) expr)->cvarno == varno)
- rlst = list_make1(expr);
- }
- else if (and_clause(expr))
- {
- foreach(l, ((BoolExpr *) expr)->args)
- {
- rlst = TidQualFromExpr((Node *) lfirst(l), varno);
- if (rlst)
- break;
- }
- }
- else if (or_clause(expr))
+ foreach(l, rlist)
{
- foreach(l, ((BoolExpr *) expr)->args)
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+
+ if (restriction_is_or_clause(rinfo))
{
- List *frtn = TidQualFromExpr((Node *) lfirst(l), varno);
+ ListCell *j;
- if (frtn)
- rlst = list_concat(rlst, frtn);
- else
+ /*
+ * We must be able to extract a CTID condition from every
+ * sub-clause of an OR, or we can't use it.
+ */
+ foreach(j, ((BoolExpr *) rinfo->orclause)->args)
{
- if (rlst)
- list_free(rlst);
- rlst = NIL;
- break;
+ Node *orarg = (Node *) lfirst(j);
+ List *sublist;
+
+ /* OR arguments should be ANDs or sub-RestrictInfos */
+ if (and_clause(orarg))
+ {
+ List *andargs = ((BoolExpr *) orarg)->args;
+
+ /* Recurse in case there are sub-ORs */
+ sublist = TidQualFromRestrictInfoList(andargs, rel);
+ }
+ else
+ {
+ RestrictInfo *rinfo = castNode(RestrictInfo, orarg);
+
+ Assert(!restriction_is_or_clause(rinfo));
+ sublist = TidQualFromRestrictInfo(rinfo, rel);
+ }
+
+ /*
+ * If nothing found in this arm, we can't do anything with
+ * this OR clause.
+ */
+ if (sublist == NIL)
+ {
+ rlst = NIL; /* forget anything we had */
+ break; /* out of loop over OR args */
+ }
+
+ /*
+ * OK, continue constructing implicitly-OR'ed result list.
+ */
+ rlst = list_concat(rlst, sublist);
}
}
+ else
+ {
+ /* Not an OR clause, so handle base cases */
+ rlst = TidQualFromRestrictInfo(rinfo, rel);
+ }
+
+ /*
+ * Stop as soon as we find any usable CTID condition. In theory we
+ * could get CTID equality conditions from different AND'ed clauses,
+ * in which case we could try to pick the most efficient one. In
+ * practice, such usage seems very unlikely, so we don't bother; we
+ * just exit as soon as we find the first candidate.
+ */
+ if (rlst)
+ break;
}
+
return rlst;
}
/*
- * Extract a set of CTID conditions from the rel's baserestrictinfo list
+ * Given a list of join clauses involving our rel, create a parameterized
+ * TidPath for each one that is a suitable TidEqual clause.
+ *
+ * In principle we could combine clauses that reference the same outer rels,
+ * but it doesn't seem like such cases would arise often enough to be worth
+ * troubling over.
*/
-static List *
-TidQualFromBaseRestrictinfo(RelOptInfo *rel)
+static void
+BuildParameterizedTidPaths(PlannerInfo *root, RelOptInfo *rel, List *clauses)
{
- List *rlst = NIL;
ListCell *l;
- foreach(l, rel->baserestrictinfo)
+ foreach(l, clauses)
{
- RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+ List *tidquals;
+ Relids required_outer;
/*
- * If clause must wait till after some lower-security-level
- * restriction clause, reject it.
+ * Validate whether each clause is actually usable; we must check this
+ * even when examining clauses generated from an EquivalenceClass,
+ * since they might not satisfy the restriction on not having Vars of
+ * our rel on the other side, or somebody might've built an operator
+ * class that accepts type "tid" but has other operators in it.
+ *
+ * We currently consider only TidEqual join clauses. In principle we
+ * might find a suitable ScalarArrayOpExpr in the rel's joininfo list,
+ * but it seems unlikely to be worth checking for.
*/
- if (!restriction_is_securely_promotable(rinfo, rel))
+ if (!IsTidEqualClause(rinfo, rel))
continue;
- rlst = TidQualFromExpr((Node *) rinfo->clause, rel->relid);
- if (rlst)
- break;
+ /*
+ * Check if clause can be moved to this rel; this is probably
+ * redundant when considering EC-derived clauses, but we must check it
+ * for "loose" join clauses.
+ */
+ if (!join_clause_is_movable_to(rinfo, rel))
+ continue;
+
+ /* OK, make list of clauses for this path */
+ tidquals = list_make1(rinfo);
+
+ /* Compute required outer rels for this path */
+ required_outer = bms_union(rinfo->required_relids, rel->lateral_relids);
+ required_outer = bms_del_member(required_outer, rel->relid);
+
+ add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
+ required_outer));
}
- return rlst;
+}
+
+/*
+ * Test whether an EquivalenceClass member matches our rel's CTID Var.
+ *
+ * This is a callback for use by generate_implied_equalities_for_column.
+ */
+static bool
+ec_member_matches_ctid(PlannerInfo *root, RelOptInfo *rel,
+ EquivalenceClass *ec, EquivalenceMember *em,
+ void *arg)
+{
+ if (em->em_expr && IsA(em->em_expr, Var) &&
+ IsCTIDVar((Var *) em->em_expr, rel))
+ return true;
+ return false;
}
/*
void
create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
{
- Relids required_outer;
List *tidquals;
/*
- * We don't support pushing join clauses into the quals of a tidscan, but
- * it could still have required parameterization due to LATERAL refs in
- * its tlist.
+ * If any suitable quals exist in the rel's baserestrict list, generate a
+ * plain (unparameterized) TidPath with them.
*/
- required_outer = rel->lateral_relids;
-
- tidquals = TidQualFromBaseRestrictinfo(rel);
+ tidquals = TidQualFromRestrictInfoList(rel->baserestrictinfo, rel);
if (tidquals)
+ {
+ /*
+ * This path uses no join clauses, but it could still have required
+ * parameterization due to LATERAL refs in its tlist.
+ */
+ Relids required_outer = rel->lateral_relids;
+
add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
required_outer));
+ }
+
+ /*
+ * Try to generate parameterized TidPaths using equality clauses extracted
+ * from EquivalenceClasses. (This is important since simple "t1.ctid =
+ * t2.ctid" clauses will turn into ECs.)
+ */
+ if (rel->has_eclass_joins)
+ {
+ List *clauses;
+
+ /* Generate clauses, skipping any that join to lateral_referencers */
+ clauses = generate_implied_equalities_for_column(root,
+ rel,
+ ec_member_matches_ctid,
+ NULL,
+ rel->lateral_referencers);
+
+ /* Generate a path for each usable join clause */
+ BuildParameterizedTidPaths(root, rel, clauses);
+ }
+
+ /*
+ * Also consider parameterized TidPaths using "loose" join quals. Quals
+ * of the form "t1.ctid = t2.ctid" would turn into these if they are outer
+ * join quals, for example.
+ */
+ BuildParameterizedTidPaths(root, rel, rel->joininfo);
}
TidScan *scan_plan;
Index scan_relid = best_path->path.parent->relid;
List *tidquals = best_path->tidquals;
- List *ortidquals;
/* it should be a base rel... */
Assert(scan_relid > 0);
Assert(best_path->path.parent->rtekind == RTE_RELATION);
+ /*
+ * The qpqual list must contain all restrictions not enforced by the
+ * tidquals list. Since tidquals has OR semantics, we have to be careful
+ * about matching it up to scan_clauses. It's convenient to handle the
+ * single-tidqual case separately from the multiple-tidqual case. In the
+ * single-tidqual case, we look through the scan_clauses while they are
+ * still in RestrictInfo form, and drop any that are redundant with the
+ * tidqual.
+ *
+ * In normal cases simple pointer equality checks will be enough to spot
+ * duplicate RestrictInfos, so we try that first.
+ *
+ * Another common case is that a scan_clauses entry is generated from the
+ * same EquivalenceClass as some tidqual, and is therefore redundant with
+ * it, though not equal.
+ *
+ * Unlike indexpaths, we don't bother with predicate_implied_by(); the
+ * number of cases where it could win are pretty small.
+ */
+ if (list_length(tidquals) == 1)
+ {
+ List *qpqual = NIL;
+ ListCell *l;
+
+ foreach(l, scan_clauses)
+ {
+ RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
+
+ if (rinfo->pseudoconstant)
+ continue; /* we may drop pseudoconstants here */
+ if (list_member_ptr(tidquals, rinfo))
+ continue; /* simple duplicate */
+ if (is_redundant_derived_clause(rinfo, tidquals))
+ continue; /* derived from same EquivalenceClass */
+ qpqual = lappend(qpqual, rinfo);
+ }
+ scan_clauses = qpqual;
+ }
+
/* Sort clauses into best execution order */
scan_clauses = order_qual_clauses(root, scan_clauses);
- /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
+ /* Reduce RestrictInfo lists to bare expressions; ignore pseudoconstants */
+ tidquals = extract_actual_clauses(tidquals, false);
scan_clauses = extract_actual_clauses(scan_clauses, false);
+ /*
+ * If we have multiple tidquals, it's more convenient to remove duplicate
+ * scan_clauses after stripping the RestrictInfos. In this situation,
+ * because the tidquals represent OR sub-clauses, they could not have come
+ * from EquivalenceClasses so we don't have to worry about matching up
+ * non-identical clauses. On the other hand, because tidpath.c will have
+ * extracted those sub-clauses from some OR clause and built its own list,
+ * we will certainly not have pointer equality to any scan clause. So
+ * convert the tidquals list to an explicit OR clause and see if we can
+ * match it via equal() to any scan clause.
+ */
+ if (list_length(tidquals) > 1)
+ scan_clauses = list_difference(scan_clauses,
+ list_make1(make_orclause(tidquals)));
+
/* Replace any outer-relation variables with nestloop params */
if (best_path->path.param_info)
{
replace_nestloop_params(root, (Node *) scan_clauses);
}
- /*
- * Remove any clauses that are TID quals. This is a bit tricky since the
- * tidquals list has implicit OR semantics.
- */
- ortidquals = tidquals;
- if (list_length(ortidquals) > 1)
- ortidquals = list_make1(make_orclause(ortidquals));
- scan_clauses = list_difference(scan_clauses, ortidquals);
-
scan_plan = make_tidscan(tlist,
scan_clauses,
scan_relid,