mirror of https://github.com/postgres/postgres
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
587 lines
16 KiB
587 lines
16 KiB
/*-------------------------------------------------------------------------
|
|
*
|
|
* tidpath.c
|
|
* Routines to determine which TID conditions are usable for scanning
|
|
* a given relation, and create TidPaths and TidRangePaths accordingly.
|
|
*
|
|
* For TidPaths, we look for WHERE conditions of the form
|
|
* "CTID = pseudoconstant", which can be implemented by just fetching
|
|
* the tuple directly via heap_fetch(). We can also handle OR'd conditions
|
|
* such as (CTID = const1) OR (CTID = const2), as well as ScalarArrayOpExpr
|
|
* conditions of the form CTID = ANY(pseudoconstant_array). In particular
|
|
* 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.
|
|
*
|
|
* Additionally, TidRangePaths may be created for conditions of the form
|
|
* "CTID relop pseudoconstant", where relop is one of >,>=,<,<=, and
|
|
* AND-clauses composed of such conditions.
|
|
*
|
|
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/optimizer/path/tidpath.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/sysattr.h"
|
|
#include "catalog/pg_operator.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "optimizer/cost.h"
|
|
#include "optimizer/optimizer.h"
|
|
#include "optimizer/pathnode.h"
|
|
#include "optimizer/paths.h"
|
|
#include "optimizer/restrictinfo.h"
|
|
|
|
|
|
/*
|
|
* 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->varnullingrels == NULL &&
|
|
var->varlevelsup == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Check to see if a RestrictInfo is of the form
|
|
* CTID OP pseudoconstant
|
|
* or
|
|
* pseudoconstant OP CTID
|
|
* where OP is a binary operation, the CTID Var belongs to relation "rel",
|
|
* and nothing on the other side of the clause does.
|
|
*/
|
|
static bool
|
|
IsBinaryTidClause(RestrictInfo *rinfo, RelOptInfo *rel)
|
|
{
|
|
OpExpr *node;
|
|
Node *arg1,
|
|
*arg2,
|
|
*other;
|
|
Relids other_relids;
|
|
|
|
/* Must be an OpExpr */
|
|
if (!is_opclause(rinfo->clause))
|
|
return false;
|
|
node = (OpExpr *) rinfo->clause;
|
|
|
|
/* OpExpr must have two arguments */
|
|
if (list_length(node->args) != 2)
|
|
return false;
|
|
arg1 = linitial(node->args);
|
|
arg2 = lsecond(node->args);
|
|
|
|
/* Look for CTID as either argument */
|
|
other = NULL;
|
|
other_relids = NULL;
|
|
if (arg1 && IsA(arg1, Var) &&
|
|
IsCTIDVar((Var *) arg1, rel))
|
|
{
|
|
other = arg2;
|
|
other_relids = rinfo->right_relids;
|
|
}
|
|
if (!other && arg2 && IsA(arg2, Var) &&
|
|
IsCTIDVar((Var *) arg2, rel))
|
|
{
|
|
other = arg1;
|
|
other_relids = rinfo->left_relids;
|
|
}
|
|
if (!other)
|
|
return false;
|
|
|
|
/* The other argument must be a pseudoconstant */
|
|
if (bms_is_member(rel->relid, other_relids) ||
|
|
contain_volatile_functions(other))
|
|
return false;
|
|
|
|
return true; /* success */
|
|
}
|
|
|
|
/*
|
|
* Check to see if a RestrictInfo is of the form
|
|
* CTID = pseudoconstant
|
|
* or
|
|
* pseudoconstant = CTID
|
|
* where the CTID Var belongs to relation "rel", and nothing on the
|
|
* other side of the clause does.
|
|
*/
|
|
static bool
|
|
IsTidEqualClause(RestrictInfo *rinfo, RelOptInfo *rel)
|
|
{
|
|
if (!IsBinaryTidClause(rinfo, rel))
|
|
return false;
|
|
|
|
if (((OpExpr *) rinfo->clause)->opno == TIDEqualOperator)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Check to see if a RestrictInfo is of the form
|
|
* CTID OP pseudoconstant
|
|
* or
|
|
* pseudoconstant OP CTID
|
|
* where OP is a range operator such as <, <=, >, or >=, the CTID Var belongs
|
|
* to relation "rel", and nothing on the other side of the clause does.
|
|
*/
|
|
static bool
|
|
IsTidRangeClause(RestrictInfo *rinfo, RelOptInfo *rel)
|
|
{
|
|
Oid opno;
|
|
|
|
if (!IsBinaryTidClause(rinfo, rel))
|
|
return false;
|
|
opno = ((OpExpr *) rinfo->clause)->opno;
|
|
|
|
if (opno == TIDLessOperator || opno == TIDLessEqOperator ||
|
|
opno == TIDGreaterOperator || opno == TIDGreaterEqOperator)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* 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(PlannerInfo *root, 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;
|
|
if (!node->useOr)
|
|
return false;
|
|
Assert(list_length(node->args) == 2);
|
|
arg1 = linitial(node->args);
|
|
arg2 = lsecond(node->args);
|
|
|
|
/* CTID must be first argument */
|
|
if (arg1 && IsA(arg1, Var) &&
|
|
IsCTIDVar((Var *) arg1, rel))
|
|
{
|
|
/* The other argument must be a pseudoconstant */
|
|
if (bms_is_member(rel->relid, pull_varnos(root, arg2)) ||
|
|
contain_volatile_functions(arg2))
|
|
return false;
|
|
|
|
return true; /* success */
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* Is the RestrictInfo usable as a CTID qual for the specified rel?
|
|
*
|
|
* This function considers only base cases; AND/OR combination is handled
|
|
* below.
|
|
*/
|
|
static bool
|
|
RestrictInfoIsTidQual(PlannerInfo *root, RestrictInfo *rinfo, RelOptInfo *rel)
|
|
{
|
|
/*
|
|
* We may ignore pseudoconstant clauses (they can't contain Vars, so could
|
|
* not match anyway).
|
|
*/
|
|
if (rinfo->pseudoconstant)
|
|
return false;
|
|
|
|
/*
|
|
* If clause must wait till after some lower-security-level restriction
|
|
* clause, reject it.
|
|
*/
|
|
if (!restriction_is_securely_promotable(rinfo, rel))
|
|
return false;
|
|
|
|
/*
|
|
* Check all base cases.
|
|
*/
|
|
if (IsTidEqualClause(rinfo, rel) ||
|
|
IsTidEqualAnyClause(root, rinfo, rel) ||
|
|
IsCurrentOfClause(rinfo, rel))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Extract a set of CTID conditions from implicit-AND List of RestrictInfos
|
|
*
|
|
* 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
|
|
* equality conditions.
|
|
*
|
|
* This function is mainly concerned with handling AND/OR recursion.
|
|
* However, we do have a special rule to enforce: if there is a CurrentOfExpr
|
|
* qual, we *must* return that and only that, else the executor may fail.
|
|
* Ordinarily a CurrentOfExpr would be all alone anyway because of grammar
|
|
* restrictions, but it is possible for RLS quals to appear AND'ed with it.
|
|
* It's even possible (if fairly useless) for the RLS quals to be CTID quals.
|
|
* So we must scan the whole rlist to see if there's a CurrentOfExpr. Since
|
|
* we have to do that, we also apply some very-trivial preference rules about
|
|
* which of the other possibilities should be chosen, in the unlikely event
|
|
* that there's more than one choice.
|
|
*/
|
|
static List *
|
|
TidQualFromRestrictInfoList(PlannerInfo *root, List *rlist, RelOptInfo *rel,
|
|
bool *isCurrentOf)
|
|
{
|
|
RestrictInfo *tidclause = NULL; /* best simple CTID qual so far */
|
|
List *orlist = NIL; /* best OR'ed CTID qual so far */
|
|
ListCell *l;
|
|
|
|
*isCurrentOf = false;
|
|
|
|
foreach(l, rlist)
|
|
{
|
|
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
|
|
|
|
if (restriction_is_or_clause(rinfo))
|
|
{
|
|
List *rlst = NIL;
|
|
ListCell *j;
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
Node *orarg = (Node *) lfirst(j);
|
|
List *sublist;
|
|
|
|
/* OR arguments should be ANDs or sub-RestrictInfos */
|
|
if (is_andclause(orarg))
|
|
{
|
|
List *andargs = ((BoolExpr *) orarg)->args;
|
|
bool sublistIsCurrentOf;
|
|
|
|
/* Recurse in case there are sub-ORs */
|
|
sublist = TidQualFromRestrictInfoList(root, andargs, rel,
|
|
&sublistIsCurrentOf);
|
|
if (sublistIsCurrentOf)
|
|
elog(ERROR, "IS CURRENT OF within OR clause");
|
|
}
|
|
else
|
|
{
|
|
RestrictInfo *ri = castNode(RestrictInfo, orarg);
|
|
|
|
Assert(!restriction_is_or_clause(ri));
|
|
if (RestrictInfoIsTidQual(root, ri, rel))
|
|
sublist = list_make1(ri);
|
|
else
|
|
sublist = NIL;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
if (rlst)
|
|
{
|
|
/*
|
|
* Accept the OR'ed list if it's the first one, or if it's
|
|
* shorter than the previous one.
|
|
*/
|
|
if (orlist == NIL || list_length(rlst) < list_length(orlist))
|
|
orlist = rlst;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Not an OR clause, so handle base cases */
|
|
if (RestrictInfoIsTidQual(root, rinfo, rel))
|
|
{
|
|
/* We can stop immediately if it's a CurrentOfExpr */
|
|
if (IsCurrentOfClause(rinfo, rel))
|
|
{
|
|
*isCurrentOf = true;
|
|
return list_make1(rinfo);
|
|
}
|
|
|
|
/*
|
|
* Otherwise, remember the first non-OR CTID qual. We could
|
|
* try to apply some preference order if there's more than
|
|
* one, but such usage seems very unlikely, so don't bother.
|
|
*/
|
|
if (tidclause == NULL)
|
|
tidclause = rinfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Prefer any singleton CTID qual to an OR'ed list. Again, it seems
|
|
* unlikely to be worth thinking harder than that.
|
|
*/
|
|
if (tidclause)
|
|
return list_make1(tidclause);
|
|
return orlist;
|
|
}
|
|
|
|
/*
|
|
* Extract a set of CTID range conditions from implicit-AND List of RestrictInfos
|
|
*
|
|
* Returns a List of CTID range qual RestrictInfos for the specified rel
|
|
* (with implicit AND semantics across the list), or NIL if there are no
|
|
* usable range conditions or if the rel's table AM does not support TID range
|
|
* scans.
|
|
*/
|
|
static List *
|
|
TidRangeQualFromRestrictInfoList(List *rlist, RelOptInfo *rel)
|
|
{
|
|
List *rlst = NIL;
|
|
ListCell *l;
|
|
|
|
if ((rel->amflags & AMFLAG_HAS_TID_RANGE) == 0)
|
|
return NIL;
|
|
|
|
foreach(l, rlist)
|
|
{
|
|
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
|
|
|
|
if (IsTidRangeClause(rinfo, rel))
|
|
rlst = lappend(rlst, rinfo);
|
|
}
|
|
|
|
return rlst;
|
|
}
|
|
|
|
/*
|
|
* 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 void
|
|
BuildParameterizedTidPaths(PlannerInfo *root, RelOptInfo *rel, List *clauses)
|
|
{
|
|
ListCell *l;
|
|
|
|
foreach(l, clauses)
|
|
{
|
|
RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
|
|
List *tidquals;
|
|
Relids required_outer;
|
|
|
|
/*
|
|
* 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 expending the cycles to check.
|
|
* And we definitely won't find a CurrentOfExpr here. Hence, we don't
|
|
* use RestrictInfoIsTidQual; but this must match that function
|
|
* otherwise.
|
|
*/
|
|
if (rinfo->pseudoconstant ||
|
|
!restriction_is_securely_promotable(rinfo, rel) ||
|
|
!IsTidEqualClause(rinfo, rel))
|
|
continue;
|
|
|
|
/*
|
|
* 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));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* create_tidscan_paths
|
|
* Create paths corresponding to direct TID scans of the given rel.
|
|
*
|
|
* Candidate paths are added to the rel's pathlist (using add_path).
|
|
*/
|
|
bool
|
|
create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
|
|
{
|
|
List *tidquals;
|
|
List *tidrangequals;
|
|
bool isCurrentOf;
|
|
|
|
/*
|
|
* If any suitable quals exist in the rel's baserestrict list, generate a
|
|
* plain (unparameterized) TidPath with them.
|
|
*
|
|
* We skip this when enable_tidscan = false, except when the qual is
|
|
* CurrentOfExpr. In that case, a TID scan is the only correct path.
|
|
*/
|
|
tidquals = TidQualFromRestrictInfoList(root, rel->baserestrictinfo, rel,
|
|
&isCurrentOf);
|
|
|
|
if (tidquals != NIL && (enable_tidscan || isCurrentOf))
|
|
{
|
|
/*
|
|
* 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));
|
|
|
|
/*
|
|
* When the qual is CurrentOfExpr, the path that we just added is the
|
|
* only one the executor can handle, so we should return before adding
|
|
* any others. Returning true lets the caller know not to add any
|
|
* others, either.
|
|
*/
|
|
if (isCurrentOf)
|
|
return true;
|
|
}
|
|
|
|
/* Skip the rest if TID scans are disabled. */
|
|
if (!enable_tidscan)
|
|
return false;
|
|
|
|
/*
|
|
* If there are range quals in the baserestrict list, generate a
|
|
* TidRangePath.
|
|
*/
|
|
tidrangequals = TidRangeQualFromRestrictInfoList(rel->baserestrictinfo,
|
|
rel);
|
|
|
|
if (tidrangequals != NIL)
|
|
{
|
|
/*
|
|
* 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_tidrangescan_path(root, rel,
|
|
tidrangequals,
|
|
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);
|
|
|
|
return false;
|
|
}
|
|
|