/*-------------------------------------------------------------------------
*
* 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 - 2025 , 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 ;
}
Messung V0.5 in Prozent C=93 H=94 G=93
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-08-08)
¤
*© Formatika GbR, Deutschland