LCOV - code coverage report
Current view: top level - src/backend/optimizer/path - tidpath.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 136 142 95.8 %
Date: 2024-04-19 18:11:10 Functions: 12 12 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * tidpath.c
       4             :  *    Routines to determine which TID conditions are usable for scanning
       5             :  *    a given relation, and create TidPaths and TidRangePaths accordingly.
       6             :  *
       7             :  * For TidPaths, we look for WHERE conditions of the form
       8             :  * "CTID = pseudoconstant", which can be implemented by just fetching
       9             :  * the tuple directly via heap_fetch().  We can also handle OR'd conditions
      10             :  * such as (CTID = const1) OR (CTID = const2), as well as ScalarArrayOpExpr
      11             :  * conditions of the form CTID = ANY(pseudoconstant_array).  In particular
      12             :  * this allows
      13             :  *      WHERE ctid IN (tid1, tid2, ...)
      14             :  *
      15             :  * As with indexscans, our definition of "pseudoconstant" is pretty liberal:
      16             :  * we allow anything that doesn't involve a volatile function or a Var of
      17             :  * the relation under consideration.  Vars belonging to other relations of
      18             :  * the query are allowed, giving rise to parameterized TID scans.
      19             :  *
      20             :  * We also support "WHERE CURRENT OF cursor" conditions (CurrentOfExpr),
      21             :  * which amount to "CTID = run-time-determined-TID".  These could in
      22             :  * theory be translated to a simple comparison of CTID to the result of
      23             :  * a function, but in practice it works better to keep the special node
      24             :  * representation all the way through to execution.
      25             :  *
      26             :  * Additionally, TidRangePaths may be created for conditions of the form
      27             :  * "CTID relop pseudoconstant", where relop is one of >,>=,<,<=, and
      28             :  * AND-clauses composed of such conditions.
      29             :  *
      30             :  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
      31             :  * Portions Copyright (c) 1994, Regents of the University of California
      32             :  *
      33             :  *
      34             :  * IDENTIFICATION
      35             :  *    src/backend/optimizer/path/tidpath.c
      36             :  *
      37             :  *-------------------------------------------------------------------------
      38             :  */
      39             : #include "postgres.h"
      40             : 
      41             : #include "access/sysattr.h"
      42             : #include "catalog/pg_operator.h"
      43             : #include "catalog/pg_type.h"
      44             : #include "nodes/nodeFuncs.h"
      45             : #include "optimizer/optimizer.h"
      46             : #include "optimizer/pathnode.h"
      47             : #include "optimizer/paths.h"
      48             : #include "optimizer/restrictinfo.h"
      49             : 
      50             : 
      51             : /*
      52             :  * Does this Var represent the CTID column of the specified baserel?
      53             :  */
      54             : static inline bool
      55      793330 : IsCTIDVar(Var *var, RelOptInfo *rel)
      56             : {
      57             :     /* The vartype check is strictly paranoia */
      58      793330 :     if (var->varattno == SelfItemPointerAttributeNumber &&
      59        1438 :         var->vartype == TIDOID &&
      60        1438 :         var->varno == rel->relid &&
      61        1318 :         var->varnullingrels == NULL &&
      62        1318 :         var->varlevelsup == 0)
      63        1318 :         return true;
      64      792012 :     return false;
      65             : }
      66             : 
      67             : /*
      68             :  * Check to see if a RestrictInfo is of the form
      69             :  *      CTID OP pseudoconstant
      70             :  * or
      71             :  *      pseudoconstant OP CTID
      72             :  * where OP is a binary operation, the CTID Var belongs to relation "rel",
      73             :  * and nothing on the other side of the clause does.
      74             :  */
      75             : static bool
      76      784086 : IsBinaryTidClause(RestrictInfo *rinfo, RelOptInfo *rel)
      77             : {
      78             :     OpExpr     *node;
      79             :     Node       *arg1,
      80             :                *arg2,
      81             :                *other;
      82             :     Relids      other_relids;
      83             : 
      84             :     /* Must be an OpExpr */
      85      784086 :     if (!is_opclause(rinfo->clause))
      86      156000 :         return false;
      87      628086 :     node = (OpExpr *) rinfo->clause;
      88             : 
      89             :     /* OpExpr must have two arguments */
      90      628086 :     if (list_length(node->args) != 2)
      91          48 :         return false;
      92      628038 :     arg1 = linitial(node->args);
      93      628038 :     arg2 = lsecond(node->args);
      94             : 
      95             :     /* Look for CTID as either argument */
      96      628038 :     other = NULL;
      97      628038 :     other_relids = NULL;
      98     1226092 :     if (arg1 && IsA(arg1, Var) &&
      99      598054 :         IsCTIDVar((Var *) arg1, rel))
     100             :     {
     101         928 :         other = arg2;
     102         928 :         other_relids = rinfo->right_relids;
     103             :     }
     104      732004 :     if (!other && arg2 && IsA(arg2, Var) &&
     105      103966 :         IsCTIDVar((Var *) arg2, rel))
     106             :     {
     107         228 :         other = arg1;
     108         228 :         other_relids = rinfo->left_relids;
     109             :     }
     110      628038 :     if (!other)
     111      626882 :         return false;
     112             : 
     113             :     /* The other argument must be a pseudoconstant */
     114        2312 :     if (bms_is_member(rel->relid, other_relids) ||
     115        1156 :         contain_volatile_functions(other))
     116           0 :         return false;
     117             : 
     118        1156 :     return true;                /* success */
     119             : }
     120             : 
     121             : /*
     122             :  * Check to see if a RestrictInfo is of the form
     123             :  *      CTID = pseudoconstant
     124             :  * or
     125             :  *      pseudoconstant = CTID
     126             :  * where the CTID Var belongs to relation "rel", and nothing on the
     127             :  * other side of the clause does.
     128             :  */
     129             : static bool
     130      442166 : IsTidEqualClause(RestrictInfo *rinfo, RelOptInfo *rel)
     131             : {
     132      442166 :     if (!IsBinaryTidClause(rinfo, rel))
     133      441420 :         return false;
     134             : 
     135         746 :     if (((OpExpr *) rinfo->clause)->opno == TIDEqualOperator)
     136         370 :         return true;
     137             : 
     138         376 :     return false;
     139             : }
     140             : 
     141             : /*
     142             :  * Check to see if a RestrictInfo is of the form
     143             :  *      CTID OP pseudoconstant
     144             :  * or
     145             :  *      pseudoconstant OP CTID
     146             :  * where OP is a range operator such as <, <=, >, or >=, the CTID Var belongs
     147             :  * to relation "rel", and nothing on the other side of the clause does.
     148             :  */
     149             : static bool
     150      341920 : IsTidRangeClause(RestrictInfo *rinfo, RelOptInfo *rel)
     151             : {
     152             :     Oid         opno;
     153             : 
     154      341920 :     if (!IsBinaryTidClause(rinfo, rel))
     155      341510 :         return false;
     156         410 :     opno = ((OpExpr *) rinfo->clause)->opno;
     157             : 
     158         410 :     if (opno == TIDLessOperator || opno == TIDLessEqOperator ||
     159         232 :         opno == TIDGreaterOperator || opno == TIDGreaterEqOperator)
     160         232 :         return true;
     161             : 
     162         178 :     return false;
     163             : }
     164             : 
     165             : /*
     166             :  * Check to see if a RestrictInfo is of the form
     167             :  *      CTID = ANY (pseudoconstant_array)
     168             :  * where the CTID Var belongs to relation "rel", and nothing on the
     169             :  * other side of the clause does.
     170             :  */
     171             : static bool
     172      335088 : IsTidEqualAnyClause(PlannerInfo *root, RestrictInfo *rinfo, RelOptInfo *rel)
     173             : {
     174             :     ScalarArrayOpExpr *node;
     175             :     Node       *arg1,
     176             :                *arg2;
     177             : 
     178             :     /* Must be a ScalarArrayOpExpr */
     179      335088 :     if (!(rinfo->clause && IsA(rinfo->clause, ScalarArrayOpExpr)))
     180      320446 :         return false;
     181       14642 :     node = (ScalarArrayOpExpr *) rinfo->clause;
     182             : 
     183             :     /* Operator must be tideq */
     184       14642 :     if (node->opno != TIDEqualOperator)
     185       14612 :         return false;
     186          30 :     if (!node->useOr)
     187           0 :         return false;
     188             :     Assert(list_length(node->args) == 2);
     189          30 :     arg1 = linitial(node->args);
     190          30 :     arg2 = lsecond(node->args);
     191             : 
     192             :     /* CTID must be first argument */
     193          60 :     if (arg1 && IsA(arg1, Var) &&
     194          30 :         IsCTIDVar((Var *) arg1, rel))
     195             :     {
     196             :         /* The other argument must be a pseudoconstant */
     197          60 :         if (bms_is_member(rel->relid, pull_varnos(root, arg2)) ||
     198          30 :             contain_volatile_functions(arg2))
     199           0 :             return false;
     200             : 
     201          30 :         return true;            /* success */
     202             :     }
     203             : 
     204           0 :     return false;
     205             : }
     206             : 
     207             : /*
     208             :  * Check to see if a RestrictInfo is a CurrentOfExpr referencing "rel".
     209             :  */
     210             : static bool
     211      335058 : IsCurrentOfClause(RestrictInfo *rinfo, RelOptInfo *rel)
     212             : {
     213             :     CurrentOfExpr *node;
     214             : 
     215             :     /* Must be a CurrentOfExpr */
     216      335058 :     if (!(rinfo->clause && IsA(rinfo->clause, CurrentOfExpr)))
     217      334666 :         return false;
     218         392 :     node = (CurrentOfExpr *) rinfo->clause;
     219             : 
     220             :     /* If it references this rel, we're good */
     221         392 :     if (node->cvarno == rel->relid)
     222         392 :         return true;
     223             : 
     224           0 :     return false;
     225             : }
     226             : 
     227             : /*
     228             :  * Extract a set of CTID conditions from the given RestrictInfo
     229             :  *
     230             :  * Returns a List of CTID qual RestrictInfos for the specified rel (with
     231             :  * implicit OR semantics across the list), or NIL if there are no usable
     232             :  * conditions.
     233             :  *
     234             :  * This function considers only base cases; AND/OR combination is handled
     235             :  * below.  Therefore the returned List never has more than one element.
     236             :  * (Using a List may seem a bit weird, but it simplifies the caller.)
     237             :  */
     238             : static List *
     239      342784 : TidQualFromRestrictInfo(PlannerInfo *root, RestrictInfo *rinfo, RelOptInfo *rel)
     240             : {
     241             :     /*
     242             :      * We may ignore pseudoconstant clauses (they can't contain Vars, so could
     243             :      * not match anyway).
     244             :      */
     245      342784 :     if (rinfo->pseudoconstant)
     246        5896 :         return NIL;
     247             : 
     248             :     /*
     249             :      * If clause must wait till after some lower-security-level restriction
     250             :      * clause, reject it.
     251             :      */
     252      336888 :     if (!restriction_is_securely_promotable(rinfo, rel))
     253        1586 :         return NIL;
     254             : 
     255             :     /*
     256             :      * Check all base cases.  If we get a match, return the clause.
     257             :      */
     258      670390 :     if (IsTidEqualClause(rinfo, rel) ||
     259      670146 :         IsTidEqualAnyClause(root, rinfo, rel) ||
     260      335058 :         IsCurrentOfClause(rinfo, rel))
     261         636 :         return list_make1(rinfo);
     262             : 
     263      334666 :     return NIL;
     264             : }
     265             : 
     266             : /*
     267             :  * Extract a set of CTID conditions from implicit-AND List of RestrictInfos
     268             :  *
     269             :  * Returns a List of CTID qual RestrictInfos for the specified rel (with
     270             :  * implicit OR semantics across the list), or NIL if there are no usable
     271             :  * equality conditions.
     272             :  *
     273             :  * This function is just concerned with handling AND/OR recursion.
     274             :  */
     275             : static List *
     276      349306 : TidQualFromRestrictInfoList(PlannerInfo *root, List *rlist, RelOptInfo *rel)
     277             : {
     278      349306 :     List       *rlst = NIL;
     279             :     ListCell   *l;
     280             : 
     281      692130 :     foreach(l, rlist)
     282             :     {
     283      343460 :         RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
     284             : 
     285      343460 :         if (restriction_is_or_clause(rinfo))
     286             :         {
     287             :             ListCell   *j;
     288             : 
     289             :             /*
     290             :              * We must be able to extract a CTID condition from every
     291             :              * sub-clause of an OR, or we can't use it.
     292             :              */
     293        3658 :             foreach(j, ((BoolExpr *) rinfo->orclause)->args)
     294             :             {
     295        3634 :                 Node       *orarg = (Node *) lfirst(j);
     296             :                 List       *sublist;
     297             : 
     298             :                 /* OR arguments should be ANDs or sub-RestrictInfos */
     299        3634 :                 if (is_andclause(orarg))
     300             :                 {
     301         700 :                     List       *andargs = ((BoolExpr *) orarg)->args;
     302             : 
     303             :                     /* Recurse in case there are sub-ORs */
     304         700 :                     sublist = TidQualFromRestrictInfoList(root, andargs, rel);
     305             :                 }
     306             :                 else
     307             :                 {
     308        2934 :                     RestrictInfo *ri = castNode(RestrictInfo, orarg);
     309             : 
     310             :                     Assert(!restriction_is_or_clause(ri));
     311        2934 :                     sublist = TidQualFromRestrictInfo(root, ri, rel);
     312             :                 }
     313             : 
     314             :                 /*
     315             :                  * If nothing found in this arm, we can't do anything with
     316             :                  * this OR clause.
     317             :                  */
     318        3634 :                 if (sublist == NIL)
     319             :                 {
     320        3586 :                     rlst = NIL; /* forget anything we had */
     321        3586 :                     break;      /* out of loop over OR args */
     322             :                 }
     323             : 
     324             :                 /*
     325             :                  * OK, continue constructing implicitly-OR'ed result list.
     326             :                  */
     327          48 :                 rlst = list_concat(rlst, sublist);
     328             :             }
     329             :         }
     330             :         else
     331             :         {
     332             :             /* Not an OR clause, so handle base cases */
     333      339850 :             rlst = TidQualFromRestrictInfo(root, rinfo, rel);
     334             :         }
     335             : 
     336             :         /*
     337             :          * Stop as soon as we find any usable CTID condition.  In theory we
     338             :          * could get CTID equality conditions from different AND'ed clauses,
     339             :          * in which case we could try to pick the most efficient one.  In
     340             :          * practice, such usage seems very unlikely, so we don't bother; we
     341             :          * just exit as soon as we find the first candidate.
     342             :          */
     343      343460 :         if (rlst)
     344         636 :             break;
     345             :     }
     346             : 
     347      349306 :     return rlst;
     348             : }
     349             : 
     350             : /*
     351             :  * Extract a set of CTID range conditions from implicit-AND List of RestrictInfos
     352             :  *
     353             :  * Returns a List of CTID range qual RestrictInfos for the specified rel
     354             :  * (with implicit AND semantics across the list), or NIL if there are no
     355             :  * usable range conditions or if the rel's table AM does not support TID range
     356             :  * scans.
     357             :  */
     358             : static List *
     359      348606 : TidRangeQualFromRestrictInfoList(List *rlist, RelOptInfo *rel)
     360             : {
     361      348606 :     List       *rlst = NIL;
     362             :     ListCell   *l;
     363             : 
     364      348606 :     if ((rel->amflags & AMFLAG_HAS_TID_RANGE) == 0)
     365           0 :         return NIL;
     366             : 
     367      690526 :     foreach(l, rlist)
     368             :     {
     369      341920 :         RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
     370             : 
     371      341920 :         if (IsTidRangeClause(rinfo, rel))
     372         232 :             rlst = lappend(rlst, rinfo);
     373             :     }
     374             : 
     375      348606 :     return rlst;
     376             : }
     377             : 
     378             : /*
     379             :  * Given a list of join clauses involving our rel, create a parameterized
     380             :  * TidPath for each one that is a suitable TidEqual clause.
     381             :  *
     382             :  * In principle we could combine clauses that reference the same outer rels,
     383             :  * but it doesn't seem like such cases would arise often enough to be worth
     384             :  * troubling over.
     385             :  */
     386             : static void
     387      454244 : BuildParameterizedTidPaths(PlannerInfo *root, RelOptInfo *rel, List *clauses)
     388             : {
     389             :     ListCell   *l;
     390             : 
     391      569736 :     foreach(l, clauses)
     392             :     {
     393      115492 :         RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
     394             :         List       *tidquals;
     395             :         Relids      required_outer;
     396             : 
     397             :         /*
     398             :          * Validate whether each clause is actually usable; we must check this
     399             :          * even when examining clauses generated from an EquivalenceClass,
     400             :          * since they might not satisfy the restriction on not having Vars of
     401             :          * our rel on the other side, or somebody might've built an operator
     402             :          * class that accepts type "tid" but has other operators in it.
     403             :          *
     404             :          * We currently consider only TidEqual join clauses.  In principle we
     405             :          * might find a suitable ScalarArrayOpExpr in the rel's joininfo list,
     406             :          * but it seems unlikely to be worth expending the cycles to check.
     407             :          * And we definitely won't find a CurrentOfExpr here.  Hence, we don't
     408             :          * use TidQualFromRestrictInfo; but this must match that function
     409             :          * otherwise.
     410             :          */
     411      115492 :         if (rinfo->pseudoconstant ||
     412      106864 :             !restriction_is_securely_promotable(rinfo, rel) ||
     413      106864 :             !IsTidEqualClause(rinfo, rel))
     414      115348 :             continue;
     415             : 
     416             :         /*
     417             :          * Check if clause can be moved to this rel; this is probably
     418             :          * redundant when considering EC-derived clauses, but we must check it
     419             :          * for "loose" join clauses.
     420             :          */
     421         156 :         if (!join_clause_is_movable_to(rinfo, rel))
     422          12 :             continue;
     423             : 
     424             :         /* OK, make list of clauses for this path */
     425         144 :         tidquals = list_make1(rinfo);
     426             : 
     427             :         /* Compute required outer rels for this path */
     428         144 :         required_outer = bms_union(rinfo->required_relids, rel->lateral_relids);
     429         144 :         required_outer = bms_del_member(required_outer, rel->relid);
     430             : 
     431         144 :         add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
     432             :                                                    required_outer));
     433             :     }
     434      454244 : }
     435             : 
     436             : /*
     437             :  * Test whether an EquivalenceClass member matches our rel's CTID Var.
     438             :  *
     439             :  * This is a callback for use by generate_implied_equalities_for_column.
     440             :  */
     441             : static bool
     442       94692 : ec_member_matches_ctid(PlannerInfo *root, RelOptInfo *rel,
     443             :                        EquivalenceClass *ec, EquivalenceMember *em,
     444             :                        void *arg)
     445             : {
     446      185972 :     if (em->em_expr && IsA(em->em_expr, Var) &&
     447       91280 :         IsCTIDVar((Var *) em->em_expr, rel))
     448         132 :         return true;
     449       94560 :     return false;
     450             : }
     451             : 
     452             : /*
     453             :  * create_tidscan_paths
     454             :  *    Create paths corresponding to direct TID scans of the given rel.
     455             :  *
     456             :  *    Candidate paths are added to the rel's pathlist (using add_path).
     457             :  */
     458             : void
     459      348606 : create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
     460             : {
     461             :     List       *tidquals;
     462             :     List       *tidrangequals;
     463             : 
     464             :     /*
     465             :      * If any suitable quals exist in the rel's baserestrict list, generate a
     466             :      * plain (unparameterized) TidPath with them.
     467             :      */
     468      348606 :     tidquals = TidQualFromRestrictInfoList(root, rel->baserestrictinfo, rel);
     469             : 
     470      348606 :     if (tidquals != NIL)
     471             :     {
     472             :         /*
     473             :          * This path uses no join clauses, but it could still have required
     474             :          * parameterization due to LATERAL refs in its tlist.
     475             :          */
     476         612 :         Relids      required_outer = rel->lateral_relids;
     477             : 
     478         612 :         add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
     479             :                                                    required_outer));
     480             :     }
     481             : 
     482             :     /*
     483             :      * If there are range quals in the baserestrict list, generate a
     484             :      * TidRangePath.
     485             :      */
     486      348606 :     tidrangequals = TidRangeQualFromRestrictInfoList(rel->baserestrictinfo,
     487             :                                                      rel);
     488             : 
     489      348606 :     if (tidrangequals != NIL)
     490             :     {
     491             :         /*
     492             :          * This path uses no join clauses, but it could still have required
     493             :          * parameterization due to LATERAL refs in its tlist.
     494             :          */
     495         202 :         Relids      required_outer = rel->lateral_relids;
     496             : 
     497         202 :         add_path(rel, (Path *) create_tidrangescan_path(root, rel,
     498             :                                                         tidrangequals,
     499             :                                                         required_outer));
     500             :     }
     501             : 
     502             :     /*
     503             :      * Try to generate parameterized TidPaths using equality clauses extracted
     504             :      * from EquivalenceClasses.  (This is important since simple "t1.ctid =
     505             :      * t2.ctid" clauses will turn into ECs.)
     506             :      */
     507      348606 :     if (rel->has_eclass_joins)
     508             :     {
     509             :         List       *clauses;
     510             : 
     511             :         /* Generate clauses, skipping any that join to lateral_referencers */
     512      105638 :         clauses = generate_implied_equalities_for_column(root,
     513             :                                                          rel,
     514             :                                                          ec_member_matches_ctid,
     515             :                                                          NULL,
     516             :                                                          rel->lateral_referencers);
     517             : 
     518             :         /* Generate a path for each usable join clause */
     519      105638 :         BuildParameterizedTidPaths(root, rel, clauses);
     520             :     }
     521             : 
     522             :     /*
     523             :      * Also consider parameterized TidPaths using "loose" join quals.  Quals
     524             :      * of the form "t1.ctid = t2.ctid" would turn into these if they are outer
     525             :      * join quals, for example.
     526             :      */
     527      348606 :     BuildParameterizedTidPaths(root, rel, rel->joininfo);
     528      348606 : }

Generated by: LCOV version 1.14