diff options
author | David Rowley <drowley@postgresql.org> | 2021-02-27 22:59:36 +1300 |
---|---|---|
committer | David Rowley <drowley@postgresql.org> | 2021-02-27 22:59:36 +1300 |
commit | bb437f995d47405ecd92cf66df71f7f7e40ed460 (patch) | |
tree | 0ee50f8a501e1ecc30d5cfd0eeb6ed0bcd41e2b2 /src/backend/optimizer/path/tidpath.c | |
parent | f4adc41c4f92cc91d507b19e397140c35bb9fd71 (diff) |
Add TID Range Scans to support efficient scanning ranges of TIDs
This adds a new executor node named TID Range Scan. The query planner
will generate paths for TID Range scans when quals are discovered on base
relations which search for ranges on the table's ctid column. These
ranges may be open at either end. For example, WHERE ctid >= '(10,0)';
will return all tuples on page 10 and over.
To support this, two new optional callback functions have been added to
table AM. scan_set_tidrange is used to set the scan range to just the
given range of TIDs. scan_getnextslot_tidrange fetches the next tuple
in the given range.
For AMs were scanning ranges of TIDs would not make sense, these functions
can be set to NULL in the TableAmRoutine. The query planner won't
generate TID Range Scan Paths in that case.
Author: Edmund Horner, David Rowley
Reviewed-by: David Rowley, Tomas Vondra, Tom Lane, Andres Freund, Zhihong Yu
Discussion: https://postgr.es/m/CAMyN-kB-nFTkF=VA_JPwFNo08S0d-Yk0F741S2B7LDmYAi8eyA@mail.gmail.com
Diffstat (limited to 'src/backend/optimizer/path/tidpath.c')
-rw-r--r-- | src/backend/optimizer/path/tidpath.c | 119 |
1 files changed, 107 insertions, 12 deletions
diff --git a/src/backend/optimizer/path/tidpath.c b/src/backend/optimizer/path/tidpath.c index 0845b460e2c..0725d950c54 100644 --- a/src/backend/optimizer/path/tidpath.c +++ b/src/backend/optimizer/path/tidpath.c @@ -2,9 +2,9 @@ * * tidpath.c * Routines to determine which TID conditions are usable for scanning - * a given relation, and create TidPaths accordingly. + * a given relation, and create TidPaths and TidRangePaths accordingly. * - * What we are looking for here is WHERE conditions of the form + * 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 @@ -23,6 +23,9 @@ * 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-2021, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -63,14 +66,14 @@ IsCTIDVar(Var *var, RelOptInfo *rel) /* * Check to see if a RestrictInfo is of the form - * CTID = pseudoconstant + * CTID OP pseudoconstant * or - * pseudoconstant = CTID - * where the CTID Var belongs to relation "rel", and nothing on the - * other side of the clause does. + * 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 -IsTidEqualClause(RestrictInfo *rinfo, RelOptInfo *rel) +IsBinaryTidClause(RestrictInfo *rinfo, RelOptInfo *rel) { OpExpr *node; Node *arg1, @@ -83,10 +86,9 @@ IsTidEqualClause(RestrictInfo *rinfo, RelOptInfo *rel) return false; node = (OpExpr *) rinfo->clause; - /* Operator must be tideq */ - if (node->opno != TIDEqualOperator) + /* OpExpr must have two arguments */ + if (list_length(node->args) != 2) return false; - Assert(list_length(node->args) == 2); arg1 = linitial(node->args); arg2 = lsecond(node->args); @@ -118,6 +120,50 @@ IsTidEqualClause(RestrictInfo *rinfo, RelOptInfo *rel) /* * 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. @@ -222,7 +268,7 @@ TidQualFromRestrictInfo(PlannerInfo *root, RestrictInfo *rinfo, RelOptInfo *rel) * * 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. + * equality conditions. * * This function is just concerned with handling AND/OR recursion. */ @@ -302,6 +348,34 @@ TidQualFromRestrictInfoList(PlannerInfo *root, List *rlist, RelOptInfo *rel) } /* + * 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. * @@ -385,6 +459,7 @@ void create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) { List *tidquals; + List *tidrangequals; /* * If any suitable quals exist in the rel's baserestrict list, generate a @@ -392,7 +467,7 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) */ tidquals = TidQualFromRestrictInfoList(root, rel->baserestrictinfo, rel); - if (tidquals) + if (tidquals != NIL) { /* * This path uses no join clauses, but it could still have required @@ -405,6 +480,26 @@ create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel) } /* + * 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.) |