diff --git a/contrib/tidrangescan/Makefile b/contrib/tidrangescan/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..76775dbf1fca03c98f19b11786fcbc62169e865a --- /dev/null +++ b/contrib/tidrangescan/Makefile @@ -0,0 +1,21 @@ +# contrib/tidrangescan/Makefile + +MODULES = tidrangescan +EXTENSION = tidrangescan +REGRESS = tidrangescan +REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress -c 0 -d 1 --single_node + +DATA = tidrangescan--1.0.sql + +override CPPFLAGS :=$(filter-out -fPIE, $(CPPFLAGS)) -fPIC + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/tidrangescan +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/tidrangescan/README.md b/contrib/tidrangescan/README.md new file mode 100644 index 0000000000000000000000000000000000000000..cbc4ca0d7aa741cafd4b172a013941f9acfd0789 --- /dev/null +++ b/contrib/tidrangescan/README.md @@ -0,0 +1,11 @@ +Tidrangescan provides a new scan type. Its implementation is based on the extensible operator of openGauss. + +Tidrangescan adds a new exector node to support the use of the inequality operator to access system column *ctid* of a relation. For example, WHERE ctid >= '(10,0)'; will return all tuples on page 10 and over. +``` sql +postgres=# EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid >= '(10,0)'; + QUERY PLAN +------------------------------------- + Tid Range Scan on t1 + TID Cond: (ctid >= '(10,0)'::tid) +(2 rows) +``` diff --git a/contrib/tidrangescan/expected/tidrangescan.out b/contrib/tidrangescan/expected/tidrangescan.out new file mode 100644 index 0000000000000000000000000000000000000000..3c600c2c137346092f13703aa674ec23fa7536b8 --- /dev/null +++ b/contrib/tidrangescan/expected/tidrangescan.out @@ -0,0 +1,269 @@ +-- tests for tidrangescans +create extension tidrangescan; +SET enable_seqscan TO off; +CREATE TABLE tidrangescan(id integer, data text); +-- empty table +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid < '(1, 0)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid < '(1,0)'::tid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid < '(1, 0)'; + ctid +------ +(0 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid > '(9, 0)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid > '(9,0)'::tid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid > '(9, 0)'; + ctid +------ +(0 rows) + +-- insert enough tuples to fill at least two pages +INSERT INTO tidrangescan SELECT i,repeat('x', 100) FROM generate_series(1,200) AS s(i); +-- remove all tuples after the 10th tuple on each page. Trying to ensure +-- we get the same layout with all CPU architectures and smaller than standard +-- page sizes. +DELETE FROM tidrangescan +WHERE substring(ctid::text FROM ',(\d+)\)')::integer > 10 OR substring(ctid::text FROM '\((\d+),')::integer > 2; +VACUUM tidrangescan; +-- range scans with upper bound +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid < '(1,0)'::tid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)'; + ctid +-------- + (0,1) + (0,2) + (0,3) + (0,4) + (0,5) + (0,6) + (0,7) + (0,8) + (0,9) + (0,10) +(10 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid <= '(1,5)'; + QUERY PLAN +------------------------------------ + Tid Range Scan on tidrangescan + TID Cond: (ctid <= '(1,5)'::tid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid <= '(1,5)'; + ctid +-------- + (0,1) + (0,2) + (0,3) + (0,4) + (0,5) + (0,6) + (0,7) + (0,8) + (0,9) + (0,10) + (1,1) + (1,2) + (1,3) + (1,4) + (1,5) +(15 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid < '(0,0)'::tid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)'; + ctid +------ +(0 rows) + +-- range scans with lower bound +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid > '(2,8)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid > '(2,8)'::tid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid > '(2,8)'; + ctid +-------- + (2,9) + (2,10) +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE '(2,8)' < ctid; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: ('(2,8)'::tid < ctid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE '(2,8)' < ctid; + ctid +-------- + (2,9) + (2,10) +(2 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid >= '(2,8)'; + QUERY PLAN +------------------------------------ + Tid Range Scan on tidrangescan + TID Cond: (ctid >= '(2,8)'::tid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid >= '(2,8)'; + ctid +-------- + (2,8) + (2,9) + (2,10) +(3 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid >= '(100,0)'; + QUERY PLAN +-------------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid >= '(100,0)'::tid) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid >= '(100,0)'; + ctid +------ +(0 rows) + +-- range scans with both bounds +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid > '(1,4)' AND '(1,7)' >= ctid; + QUERY PLAN +---------------------------------------------------------------- + Tid Range Scan on tidrangescan + TID Cond: ((ctid > '(1,4)'::tid) AND ('(1,7)'::tid >= ctid)) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE ctid > '(1,4)' AND '(1,7)' >= ctid; + ctid +------- + (1,5) + (1,6) + (1,7) +(3 rows) + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE '(1,7)' >= ctid AND ctid > '(1,4)'; + QUERY PLAN +---------------------------------------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (('(1,7)'::tid >= ctid) AND (ctid > '(1,4)'::tid)) +(2 rows) + +SELECT ctid FROM tidrangescan WHERE '(1,7)' >= ctid AND ctid > '(1,4)'; + ctid +------- + (1,5) + (1,6) + (1,7) +(3 rows) + +-- extreme offsets +SELECT ctid FROM tidrangescan WHERE ctid > '(0,65535)' AND ctid < '(1,0)' LIMIT 1; + ctid +------ +(0 rows) + +SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)' LIMIT 1; + ctid +------ +(0 rows) + +SELECT ctid FROM tidrangescan WHERE ctid > '(4294967295,65535)'; + ctid +------ +(0 rows) + +SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)'; + ctid +------ +(0 rows) + +-- NULLs in the range cannot return tuples +SELECT ctid FROM tidrangescan WHERE ctid >= (SELECT NULL::tid); + ctid +------ +(0 rows) + +-- cursors +-- Ensure we get a TID Range scan without a Materialize node. +EXPLAIN (COSTS OFF) +DECLARE c CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)'; + QUERY PLAN +----------------------------------- + Tid Range Scan on tidrangescan + TID Cond: (ctid < '(1,0)'::tid) +(2 rows) + +BEGIN; +DECLARE c CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)'; +FETCH NEXT c; + ctid +------- + (0,1) +(1 row) + +FETCH NEXT c; + ctid +------- + (0,2) +(1 row) + +FETCH PRIOR c; + ctid +------- + (0,1) +(1 row) + +FETCH FIRST c; + ctid +------- + (0,1) +(1 row) + +FETCH LAST c; + ctid +-------- + (0,10) +(1 row) + +COMMIT; +DROP TABLE tidrangescan; +RESET enable_seqscan; diff --git a/contrib/tidrangescan/sql/tidrangescan.sql b/contrib/tidrangescan/sql/tidrangescan.sql new file mode 100644 index 0000000000000000000000000000000000000000..20ba292a1fcefb07dbd23b00f50595816bede8e0 --- /dev/null +++ b/contrib/tidrangescan/sql/tidrangescan.sql @@ -0,0 +1,92 @@ +-- tests for tidrangescans +create extension tidrangescan; + +SET enable_seqscan TO off; +CREATE TABLE tidrangescan(id integer, data text); + +-- empty table +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid < '(1, 0)'; +SELECT ctid FROM tidrangescan WHERE ctid < '(1, 0)'; + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid > '(9, 0)'; +SELECT ctid FROM tidrangescan WHERE ctid > '(9, 0)'; + +-- insert enough tuples to fill at least two pages +INSERT INTO tidrangescan SELECT i,repeat('x', 100) FROM generate_series(1,200) AS s(i); + +-- remove all tuples after the 10th tuple on each page. Trying to ensure +-- we get the same layout with all CPU architectures and smaller than standard +-- page sizes. +DELETE FROM tidrangescan +WHERE substring(ctid::text FROM ',(\d+)\)')::integer > 10 OR substring(ctid::text FROM '\((\d+),')::integer > 2; +VACUUM tidrangescan; + +-- range scans with upper bound +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)'; +SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)'; + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid <= '(1,5)'; +SELECT ctid FROM tidrangescan WHERE ctid <= '(1,5)'; + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)'; +SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)'; + +-- range scans with lower bound +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid > '(2,8)'; +SELECT ctid FROM tidrangescan WHERE ctid > '(2,8)'; + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE '(2,8)' < ctid; +SELECT ctid FROM tidrangescan WHERE '(2,8)' < ctid; + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid >= '(2,8)'; +SELECT ctid FROM tidrangescan WHERE ctid >= '(2,8)'; + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid >= '(100,0)'; +SELECT ctid FROM tidrangescan WHERE ctid >= '(100,0)'; + +-- range scans with both bounds +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE ctid > '(1,4)' AND '(1,7)' >= ctid; +SELECT ctid FROM tidrangescan WHERE ctid > '(1,4)' AND '(1,7)' >= ctid; + +EXPLAIN (COSTS OFF) +SELECT ctid FROM tidrangescan WHERE '(1,7)' >= ctid AND ctid > '(1,4)'; +SELECT ctid FROM tidrangescan WHERE '(1,7)' >= ctid AND ctid > '(1,4)'; + +-- extreme offsets +SELECT ctid FROM tidrangescan WHERE ctid > '(0,65535)' AND ctid < '(1,0)' LIMIT 1; +SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)' LIMIT 1; + +SELECT ctid FROM tidrangescan WHERE ctid > '(4294967295,65535)'; +SELECT ctid FROM tidrangescan WHERE ctid < '(0,0)'; + +-- NULLs in the range cannot return tuples +SELECT ctid FROM tidrangescan WHERE ctid >= (SELECT NULL::tid); + +-- cursors + +-- Ensure we get a TID Range scan without a Materialize node. +EXPLAIN (COSTS OFF) +DECLARE c CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)'; + +BEGIN; +DECLARE c CURSOR FOR SELECT ctid FROM tidrangescan WHERE ctid < '(1,0)'; +FETCH NEXT c; +FETCH NEXT c; +FETCH PRIOR c; +FETCH FIRST c; +FETCH LAST c; +COMMIT; + +DROP TABLE tidrangescan; + +RESET enable_seqscan; diff --git a/contrib/tidrangescan/tidrangescan--1.0.sql b/contrib/tidrangescan/tidrangescan--1.0.sql new file mode 100644 index 0000000000000000000000000000000000000000..b2d0f3eabc01d690ed3d5115fe45d2b29d90956e --- /dev/null +++ b/contrib/tidrangescan/tidrangescan--1.0.sql @@ -0,0 +1,5 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION tidrangescan" to load this file. \quit + +CREATE FUNCTION pg_catalog.tidrangescan_invoke() + RETURNS VOID AS '$libdir/tidrangescan','tidrangescan_invoke' LANGUAGE C STRICT; \ No newline at end of file diff --git a/contrib/tidrangescan/tidrangescan.control b/contrib/tidrangescan/tidrangescan.control new file mode 100644 index 0000000000000000000000000000000000000000..ed5e19c5ec620918869a8b1a8d9de1ff9b798845 --- /dev/null +++ b/contrib/tidrangescan/tidrangescan.control @@ -0,0 +1,5 @@ +# tidrangescan extension +comment = 'example implementation for custom-scan-provider interface' +default_version = '1.0' +module_pathname = '$libdir/tidrangescan' +relocatable = true diff --git a/contrib/tidrangescan/tidrangescan.cpp b/contrib/tidrangescan/tidrangescan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fc4ddf99ece0b047405388c7493b4093d37aa9bc --- /dev/null +++ b/contrib/tidrangescan/tidrangescan.cpp @@ -0,0 +1,1056 @@ +/* + * contrib/tidrangescan/tidrangescan.cpp + * Routines to support TID range scans of relations + * + */ +#include "postgres.h" +#include "access/relscan.h" +#include "access/sysattr.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "commands/explain.h" +#include "executor/executor.h" +#include "executor/execdebug.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" +#include "optimizer/paths.h" +#include "optimizer/pathnode.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/placeholder.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/subselect.h" +#include "parser/parsetree.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/guc.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/spccache.h" +#include "access/tableam.h" +#include "access/htup.h" +#include "executor/nodeExtensible.h" +#include "commands/extension.h" + +#include "tidrangescan.h" + +/* missing declaration in pg_operator.h */ +#ifndef TIDGreaterOperator +#define TIDGreaterOperator 2800 +#endif +#ifndef TIDLessEqOperator +#define TIDLessEqOperator 2801 +#endif +#ifndef TIDGreaterEqOperator +#define TIDGreaterEqOperator 2802 +#endif + +PG_MODULE_MAGIC; +PG_FUNCTION_INFO_V1(tidrangescan_invoke); + +/* ---------------- + * TidRangeScanState information + * + * trss_tidexprs list of TidOpExpr structs (see nodeTidrangescan.c) + * trss_mintid the lowest TID in the scan range + * trss_maxtid the highest TID in the scan range + * trss_inScan is a scan currently in progress? + * ---------------- + */ +typedef struct { + ExtensiblePlanState css; + List *trss_tidexprs; + ItemPointerData trss_mintid; + ItemPointerData trss_maxtid; + bool trss_inScan; +} TidRangeScanState; + +typedef enum { + TIDEXPR_UPPER_BOUND, + TIDEXPR_LOWER_BOUND +} TidExprType; + +/* Upper or lower range bound for scan */ +typedef struct TidOpExpr { + TidExprType exprtype; /* type of op; lower or upper */ + ExprState *exprstate; /* ExprState for a TID-yielding subexpr */ + bool inclusive; /* whether op is inclusive */ +} TidOpExpr; + +static void SetTidRangeScanPath(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); +/* ExtensiblePathMethods */ +static Plan *PlanTidRangeScanPath(PlannerInfo *root, RelOptInfo *rel, struct ExtensiblePath *best_path, List *tlist, + List *clauses, List *extensible_plans); + +/* ExtensiblePlanMethods */ +static Node *CreateTidRangeScanState(ExtensiblePlan *extensible_plan); + +/* ExtensibleExecMethods */ +static void ExecInitTidRangeScan(ExtensiblePlanState *node, EState *estate, int eflags); +static TupleTableSlot *ExecTidRangeScan(ExtensiblePlanState *node); +static void ExecEndTidRangeScan(ExtensiblePlanState *node); +static void ExplainTidRangeScan(ExtensiblePlanState *node, List *ancestors, ExplainState *es); +static void ExecReScanTidRangeScan(ExtensiblePlanState *node); +static tidrangescan_context* get_session_context(); + +static uint32 tidrangescan_index; +#define ENABLE_TIDRANGESCAN (get_session_context()->enable_tidrangescan) + +#define IsCTIDVar(node) \ + ((node) != NULL && IsA((node), Var) && ((Var *)(node))->varattno == SelfItemPointerAttributeNumber && \ + ((Var *)(node))->varlevelsup == 0) + +/* static table of ExtensiblePlan callbacks */ +static ExtensiblePathMethods tidrangescan_path_methods = { + "Tid Range Scan", /* ExtensibleName */ + PlanTidRangeScanPath, /* PlanExtensiblePath */ +}; + +static ExtensiblePlanMethods tidrangescan_scan_methods = { + "Tid Range Scan", /* ExtensibleName */ + CreateTidRangeScanState, /* CreateExtensiblePlanState */ +}; + +static ExtensibleExecMethods tidrangescan_exec_methods = { + "Tid Range Scan", /* ExtensibleName */ + ExecInitTidRangeScan, /* BeginExtensiblePlan */ + ExecTidRangeScan, /* ExecExtensiblePlan */ + ExecEndTidRangeScan, /* EndExtensiblePlan */ + ExecReScanTidRangeScan, /* ReScanExtensiblePlan */ + ExplainTidRangeScan /* ExplainExtensiblePlan */ +}; + +void tidrangescan_invoke(void) { + ereport(DEBUG2, (errmsg("dummy function to let process load this library."))); + return; +} + +List *ExecInitExprList(List *nodes, PlanState *parent) +{ + List *result = NIL; + ListCell *lc; + + foreach (lc, nodes) { + Expr *e = (Expr *)lfirst(lc); + + result = lappend(result, ExecInitExpr(e, parent)); + } + + return result; +} + +/* + * Does this Var represent the CTID column of the specified baserel? + */ +static inline bool IsCTIDVarRel(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 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 = (Node *)linitial(node->args); + arg2 = (Node *)lsecond(node->args); + + /* Look for CTID as either argument */ + other = NULL; + other_relids = NULL; + if (arg1 && IsA(arg1, Var) && IsCTIDVarRel((Var *)arg1, rel)) { + other = arg2; + other_relids = rinfo->right_relids; + } + if (!other && arg2 && IsA(arg2, Var) && IsCTIDVarRel((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 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; +} + +/* + * 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; + + foreach (l, rlist) { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); + + if (IsTidRangeClause(rinfo, rel)) + rlst = lappend(rlst, rinfo); + } + + return rlst; +} + +/* + * cost_tidrangescan + * Determines and sets the costs of scanning a relation using a range of + * TIDs for 'path' + * + * 'baserel' is the relation to be scanned + * 'tidrangequals' is the list of TID-checkable range quals + * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL + */ +void cost_tidrangescan(Path *path, PlannerInfo *root, RelOptInfo *baserel, List *tidrangequals, + ParamPathInfo *param_info) +{ + Selectivity selectivity; + double pages; + Cost startup_cost = 0; + Cost run_cost = 0; + QualCost qpqual_cost; + Cost cpu_per_tuple; + QualCost tid_qual_cost; + double ntuples; + double nseqpages; + double spc_random_page_cost; + double spc_seq_page_cost; + + /* Should only be applied to base relations */ + Assert(baserel->relid > 0); + Assert(baserel->rtekind == RTE_RELATION); + + /* Mark the path with the correct row estimate */ + if (param_info) + path->rows = param_info->ppi_rows; + else + path->rows = baserel->rows; + + /* Count how many tuples and pages we expect to scan */ + selectivity = clauselist_selectivity(root, tidrangequals, baserel->relid, JOIN_INNER, NULL); + pages = ceil(selectivity * baserel->pages); + + if (pages <= 0.0) + pages = 1.0; + + /* + * The first page in a range requires a random seek, but each subsequent + * page is just a normal sequential page read. NOTE: it's desirable for + * TID Range Scans to cost more than the equivalent Sequential Scans, + * because Seq Scans have some performance advantages such as scan + * synchronization and parallelizability, and we'd prefer one of them to + * be picked unless a TID Range Scan really is better. + */ + ntuples = selectivity * baserel->tuples; + nseqpages = pages - 1.0; + + if (!ENABLE_TIDRANGESCAN) + startup_cost += g_instance.cost_cxt.disable_cost; + + /* + * The TID qual expressions will be computed once, any other baserestrict + * quals once per retrieved tuple. + */ + cost_qual_eval(&tid_qual_cost, tidrangequals, root); + + /* fetch estimated page cost for tablespace containing table */ + get_tablespace_page_costs(baserel->reltablespace, &spc_random_page_cost, &spc_seq_page_cost); + + /* disk costs; 1 random page and the remainder as seq pages */ + run_cost += spc_random_page_cost + spc_seq_page_cost * nseqpages; + + /* Add scanning CPU costs */ + if (param_info != NULL) { + /* Include costs of pushed-down clauses */ + cost_qual_eval(&qpqual_cost, param_info->ppi_clauses, root); + + qpqual_cost.startup += baserel->baserestrictcost.startup; + qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple; + } else { + qpqual_cost = baserel->baserestrictcost; + } + + /* + * XXX currently we assume TID quals are a subset of qpquals at this + * point; they will be removed (if possible) when we create the plan, so + * we subtract their cost from the total qpqual cost. (If the TID quals + * can't be removed, this is a mistake and we're going to underestimate + * the CPU cost a bit.) + */ + startup_cost += qpqual_cost.startup + tid_qual_cost.per_tuple; + cpu_per_tuple = u_sess->attr.attr_sql.cpu_tuple_cost + qpqual_cost.per_tuple - tid_qual_cost.per_tuple; + run_cost += cpu_per_tuple * ntuples; + + path->startup_cost = startup_cost; + path->total_cost = startup_cost + run_cost; +} + +/* + * SetTidRangeScanPath - entrypoint of the series of extensible-scan execution. + * It adds ExtensiblePath if referenced relation has inequality expressions on + * the ctid system column. + */ +static void SetTidRangeScanPath(PlannerInfo *root, RelOptInfo *baserel, Index rtindex, RangeTblEntry *rte) +{ + List *tidrangequals = NIL; + + /* only plain relations are supported */ + if (baserel->rtekind != RTE_RELATION || rte->relkind == RELKIND_FOREIGN_TABLE || rte->tablesample != NULL) + return; + + /* + * If there are range quals in the baserestrict list, generate a + * TidRangePath. + */ + tidrangequals = TidRangeQualFromRestrictInfoList(baserel->baserestrictinfo, baserel); + + if (tidrangequals != NIL) { + ExtensiblePath *cpath = makeNode(ExtensiblePath); + Relids required_outer; + + /* + * This path uses no join clauses, but it could still have required + * parameterization due to LATERAL refs in its tlist. + */ + required_outer = baserel->lateral_relids; + + cpath->path.pathtype = T_ExtensiblePlan; + cpath->path.parent = baserel; + cpath->path.param_info = get_baserel_parampathinfo(root, baserel, required_outer); + cpath->path.pathkeys = NIL; /* always unordered */ + cpath->flags = EXTENSIBLEPATH_SUPPORT_BACKWARD_SCAN | EXTENSIBLEPATH_SUPPORT_PROJECTION; + + cpath->extensible_private = tidrangequals; + cpath->methods = &tidrangescan_path_methods; + + cost_tidrangescan(&cpath->path, root, baserel, tidrangequals, cpath->path.param_info); + + add_path(root, baserel, &cpath->path); + } +} + +/* + * PlanTidRangeScanPath - A method of ExtensiblePath; that populate a extensible + * object being delivered from ExtensiblePlan type, according to the supplied + * EXtensiblePath object. + */ +static Plan *PlanTidRangeScanPath(PlannerInfo *root, RelOptInfo *rel, struct ExtensiblePath *best_path, List *tlist, + List *clauses, List *extensible_plans) +{ + ExtensiblePlan *node = makeNode(ExtensiblePlan); + Index scan_relid = best_path->path.parent->relid; + List *tidrangequals = best_path->extensible_private; + Plan *plan = &node->scan.plan; + + /* 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 + * tidrangequals list. tidrangequals has AND semantics, so we can simply + * remove any qual that appears in it. + */ + { + List *qpqual = NIL; + ListCell *l; + + foreach (l, clauses) { + RestrictInfo *rinfo = lfirst_node(RestrictInfo, l); + + if (rinfo->pseudoconstant) + continue; /* we may drop pseudoconstants here */ + if (list_member_ptr(tidrangequals, rinfo)) + continue; /* simple duplicate */ + qpqual = lappend(qpqual, rinfo); + } + clauses = qpqual; + } + + /* Reduce RestrictInfo lists to bare expressions; ignore pseudoconstants */ + tidrangequals = extract_actual_clauses(tidrangequals, false); + clauses = extract_actual_clauses(clauses, false); + + plan->targetlist = tlist; + plan->qual = clauses; + plan->lefttree = NULL; + plan->righttree = NULL; + node->scan.scanrelid = scan_relid; + node->extensible_exprs = tidrangequals; + + node->flags = best_path->flags; + node->scan.plan.startup_cost = best_path->path.startup_cost; + node->scan.plan.total_cost = best_path->path.total_cost; + node->scan.plan.plan_rows = best_path->path.rows; + node->scan.plan.plan_width = rel->width; + node->methods = &tidrangescan_scan_methods; + + return plan; +} + +/* + * CreateTidRangeScanState - A method of ExtensiblePlan; that populate a extensible + * object being delivered from ExtensiblePlanState type, according to the + * supplied ExtensiblePath object. + */ +Node *CreateTidRangeScanState(ExtensiblePlan *extensible_plan) +{ + TidRangeScanState *tidrangestate; + + /* + * create state structure + */ + tidrangestate = (TidRangeScanState *)palloc0(sizeof(TidRangeScanState)); + NodeSetTag(tidrangestate, T_ExtensiblePlanState); + tidrangestate->css.methods = &tidrangescan_exec_methods; + + /* + * mark scan as not in progress, and TID range as not computed yet + */ + tidrangestate->trss_inScan = false; + return (Node *)&tidrangestate->css; +} + +/* + * For the given 'expr', build and return an appropriate TidOpExpr taking into + * account the expr's operator and operand order. + */ +static TidOpExpr * +MakeTidOpExpr(OpExpr *expr, TidRangeScanState *tidstate) +{ + Node *arg1 = get_leftop((Expr *) expr); + Node *arg2 = get_rightop((Expr *) expr); + ExprState *exprstate = NULL; + bool invert = false; + TidOpExpr *tidopexpr; + + if (IsCTIDVar(arg1)) + exprstate = ExecInitExpr((Expr *)arg2, &tidstate->css.ss.ps); + else if (IsCTIDVar(arg2)) { + exprstate = ExecInitExpr((Expr *)arg1, &tidstate->css.ss.ps); + invert = true; + } else + elog(ERROR, "could not identify CTID variable"); + + tidopexpr = (TidOpExpr *)palloc(sizeof(TidOpExpr)); + tidopexpr->inclusive = false; /* for now */ + + switch (expr->opno) { + case TIDLessEqOperator: + tidopexpr->inclusive = true; + /* fall through */ + case TIDLessOperator: + tidopexpr->exprtype = invert ? TIDEXPR_LOWER_BOUND : TIDEXPR_UPPER_BOUND; + break; + case TIDGreaterEqOperator: + tidopexpr->inclusive = true; + /* fall through */ + case TIDGreaterOperator: + tidopexpr->exprtype = invert ? TIDEXPR_UPPER_BOUND : TIDEXPR_LOWER_BOUND; + break; + default: + elog(ERROR, "could not identify CTID operator"); + } + + tidopexpr->exprstate = exprstate; + + return tidopexpr; +} + +/* + * Extract the qual subexpressions that yield TIDs to search for, + * and compile them into ExprStates if they're ordinary expressions. + */ +static void TidExprListCreate(TidRangeScanState *tidrangestate) +{ + ExtensiblePlan *node = (ExtensiblePlan *)tidrangestate->css.ss.ps.plan; + List *tidexprs = NIL; + ListCell *l; + + foreach (l, node->extensible_exprs) { + OpExpr *opexpr = (OpExpr *)lfirst(l); + TidOpExpr *tidopexpr; + + if (!IsA(opexpr, OpExpr)) + elog(ERROR, "could not identify CTID expression"); + + tidopexpr = MakeTidOpExpr(opexpr, tidrangestate); + tidexprs = lappend(tidexprs, tidopexpr); + } + + tidrangestate->trss_tidexprs = tidexprs; +} + +/* ---------------------------------------------------------------- + * ExecInitTidRangeScan + * + * Initializes the tid range scan's state information, creates + * scan keys, and opens the scan relation. + * + * Parameters: + * node: TidRangeScan node produced by the planner. + * estate: the execution state initialized in InitPlan. + * ---------------------------------------------------------------- + */ +static void ExecInitTidRangeScan(ExtensiblePlanState *node, EState *estate, int eflags) +{ + TidRangeScanState *ctss = (TidRangeScanState *)node; + + ctss->css.ss.ss_currentScanDesc = NULL; /* no table scan here */ + TidExprListCreate(ctss); +} + +/* + * ItemPointerInc + * Increment 'pointer' by 1 only paying attention to the ItemPointer's + * type's range limits and not MaxOffsetNumber and FirstOffsetNumber. + * This may result in 'pointer' becoming !OffsetNumberIsValid. + * + * If the pointer is already the maximum possible values permitted by the + * range of the ItemPointer's types, then do nothing. + */ +void ItemPointerInc(ItemPointer pointer) +{ + BlockNumber blk = ItemPointerGetBlockNumberNoCheck(pointer); + OffsetNumber off = ItemPointerGetOffsetNumberNoCheck(pointer); + + if (off == PG_UINT16_MAX) { + if (blk != InvalidBlockNumber) { + off = 0; + blk++; + } + } else + off++; + + ItemPointerSet(pointer, blk, off); +} + +/* + * ItemPointerDec + * Decrement 'pointer' by 1 only paying attention to the ItemPointer's + * type's range limits and not MaxOffsetNumber and FirstOffsetNumber. + * This may result in 'pointer' becoming !OffsetNumberIsValid. + * + * If the pointer is already the minimum possible values permitted by the + * range of the ItemPointer's types, then do nothing. This does rely on + * FirstOffsetNumber being 1 rather than 0. + */ +void ItemPointerDec(ItemPointer pointer) +{ + BlockNumber blk = ItemPointerGetBlockNumberNoCheck(pointer); + OffsetNumber off = ItemPointerGetOffsetNumberNoCheck(pointer); + + if (off == 0) { + if (blk != 0) { + off = PG_UINT16_MAX; + blk--; + } + } else + off--; + + ItemPointerSet(pointer, blk, off); +} + +/* ---------------------------------------------------------------- + * TidRangeEval + * + * Compute and set node's block and offset range to scan by evaluating + * the trss_tidexprs. Returns false if we detect the range cannot + * contain any tuples. Returns true if it's possible for the range to + * contain tuples. + * ---------------------------------------------------------------- + */ +static bool TidRangeEval(TidRangeScanState *node) +{ + ExprContext *econtext = node->css.ss.ps.ps_ExprContext; + ItemPointerData lowerBound; + ItemPointerData upperBound; + ListCell *l; + + /* + * Set the upper and lower bounds to the absolute limits of the range of + * the ItemPointer type. Below we'll try to narrow this range on either + * side by looking at the TidOpExprs. + */ + ItemPointerSet(&lowerBound, 0, 0); + ItemPointerSet(&upperBound, InvalidBlockNumber, PG_UINT16_MAX); + + foreach (l, node->trss_tidexprs) { + TidOpExpr *tidopexpr = (TidOpExpr *)lfirst(l); + ItemPointer itemptr; + bool isNull; + + /* Evaluate this bound. */ + itemptr = + (ItemPointer)DatumGetPointer(ExecEvalExprSwitchContext(tidopexpr->exprstate, econtext, &isNull, NULL)); + + /* If the bound is NULL, *nothing* matches the qual. */ + if (isNull) + return false; + + if (tidopexpr->exprtype == TIDEXPR_LOWER_BOUND) { + ItemPointerData lb; + + ItemPointerCopy(itemptr, &lb); + + /* + * Normalize non-inclusive ranges to become inclusive. The + * resulting ItemPointer here may not be a valid item pointer. + */ + if (!tidopexpr->inclusive) + ItemPointerInc(&lb); + + /* Check if we can narrow the range using this qual */ + if (ItemPointerCompare(&lb, &lowerBound) > 0) + ItemPointerCopy(&lb, &lowerBound); + } + + else if (tidopexpr->exprtype == TIDEXPR_UPPER_BOUND) { + ItemPointerData ub; + + ItemPointerCopy(itemptr, &ub); + + /* + * Normalize non-inclusive ranges to become inclusive. The + * resulting ItemPointer here may not be a valid item pointer. + */ + if (!tidopexpr->inclusive) + ItemPointerDec(&ub); + + /* Check if we can narrow the range using this qual */ + if (ItemPointerCompare(&ub, &upperBound) < 0) + ItemPointerCopy(&ub, &upperBound); + } + } + + ItemPointerCopy(&lowerBound, &node->trss_mintid); + ItemPointerCopy(&upperBound, &node->trss_maxtid); + + return true; +} + +bool heap_getnextslot_tidrange(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot, + ItemPointer mintid, ItemPointer maxtid) +{ + HeapTuple tuple; + + while((tuple = heap_getnext(sscan, direction)) != NULL) { + if (ItemPointerCompare(&tuple->t_self, mintid) < 0) + { + ExecClearTuple(slot); + + /* + * When scanning backwards, the TIDs will be in descending order. + * Future tuples in this direction will be lower still, so we can + * just return false to indicate there will be no more tuples. + */ + if (ScanDirectionIsBackward(direction)) + return false; + + continue; + } + + /* + * Likewise for the final page, we must filter out TIDs greater than + * maxtid. + */ + if (ItemPointerCompare(&tuple->t_self, maxtid) > 0) + { + ExecClearTuple(slot); + + /* + * When scanning forward, the TIDs will be in ascending order. + * Future tuples in this direction will be higher still, so we can + * just return false to indicate there will be no more tuples. + */ + if (ScanDirectionIsForward(direction)) + return false; + continue; + } + + break; + } + if (tuple) { + ExecStoreTuple(tuple, slot, sscan->rs_cbuf, false); + return true; + } + + ExecClearTuple(slot); + return false; +} + +/* + * heap_setscanlimits - restrict range of a heapscan + * + * startBlk is the page to start at + * numBlks is number of pages to scan (InvalidBlockNumber means "all") + */ +void heap_setscanlimits(TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlks) +{ + Assert(!sscan->rs_inited); /* else too late to change */ + /* else rs_startblock is significant */ + Assert(!(sscan->rs_flags & SO_ALLOW_SYNC)); + + /* Check startBlk is valid (but allow case of zero blocks...) */ + Assert(startBlk == 0 || startBlk < sscan->rs_nblocks); + + sscan->rs_startblock = startBlk; +} + +void heap_set_tidrange(TableScanDesc sscan, ItemPointer mintid, ItemPointer maxtid) +{ + BlockNumber startBlk; + BlockNumber numBlks; + ItemPointerData highestItem; + ItemPointerData lowestItem; + + /* + * For relations without any pages, we can simply leave the TID range + * unset. There will be no tuples to scan, therefore no tuples outside + * the given TID range. + */ + if (sscan->rs_nblocks == 0) + return; + + /* + * Set up some ItemPointers which point to the first and last possible + * tuples in the heap. + */ + ItemPointerSet(&highestItem, sscan->rs_nblocks - 1, MaxOffsetNumber); + ItemPointerSet(&lowestItem, 0, FirstOffsetNumber); + + /* + * If the given maximum TID is below the highest possible TID in the + * relation, then restrict the range to that, otherwise we scan to the end + * of the relation. + */ + if (ItemPointerCompare(maxtid, &highestItem) < 0) + ItemPointerCopy(maxtid, &highestItem); + + /* + * If the given minimum TID is above the lowest possible TID in the + * relation, then restrict the range to only scan for TIDs above that. + */ + if (ItemPointerCompare(mintid, &lowestItem) > 0) + ItemPointerCopy(mintid, &lowestItem); + + /* + * Check for an empty range and protect from would be negative results + * from the numBlks calculation below. + */ + if (ItemPointerCompare(&highestItem, &lowestItem) < 0) + { + /* Set an empty range of blocks to scan */ + heap_setscanlimits(sscan, 0, 0); + return; + } + + /* + * Calculate the first block and the number of blocks we must scan. We + * could be more aggressive here and perform some more validation to try + * and further narrow the scope of blocks to scan by checking if the + * lowerItem has an offset above MaxOffsetNumber. In this case, we could + * advance startBlk by one. Likewise, if highestItem has an offset of 0 + * we could scan one fewer blocks. However, such an optimization does not + * seem worth troubling over, currently. + */ + startBlk = ItemPointerGetBlockNumberNoCheck(&lowestItem); + + numBlks = ItemPointerGetBlockNumberNoCheck(&highestItem) - + ItemPointerGetBlockNumberNoCheck(&lowestItem) + 1; + + /* Set the start block and number of blocks to scan */ + heap_setscanlimits(sscan, startBlk, numBlks); +} + +/* ---------------------------------------------------------------- + * ExecReScanTidRangeScan(node) + * ---------------------------------------------------------------- + */ +static void ExecReScanTidRangeScan(ExtensiblePlanState *node) +{ + /* mark scan as not in progress, and tid range list as not computed yet */ + ((TidRangeScanState *)node)->trss_inScan = false; + + /* + * We must wait until TidRangeNext before calling table_rescan_tidrange. + */ + ExecScanReScan(&node->ss); +} + +#define SO_TYPE_TIDRANGESCAN 1 << 9 + +/* + * table_beginscan_tidrange is the entry point for setting up a TableScanDesc + * for a TID range scan. + */ +static inline TableScanDesc table_beginscan_tidrange(Relation rel, Snapshot snapshot, ItemPointer mintid, + ItemPointer maxtid) +{ + TableScanDesc sscan; + uint32 flags = SO_TYPE_TIDRANGESCAN | SO_ALLOW_PAGEMODE; + + sscan = heap_beginscan(rel, snapshot, 0, NULL); + + sscan->rs_flags = flags; + /* Set the range of TIDs to scan */ + heap_set_tidrange(sscan, mintid, maxtid); + + return sscan; +} + +/* + * table_rescan_tidrange resets the scan position and sets the minimum and + * maximum TID range to scan for a TableScanDesc created by + * table_beginscan_tidrange. + */ +static inline void table_rescan_tidrange(TableScanDesc sscan, ItemPointer mintid, ItemPointer maxtid) +{ + /* Ensure table_beginscan_tidrange() was used. */ + Assert((sscan->rs_flags & SO_TYPE_TIDRANGESCAN) != 0); + + heap_rescan(sscan, NULL); + heap_set_tidrange(sscan, mintid, maxtid); +} + +/* ---------------------------------------------------------------- + * TidRangeNext + * + * Retrieve a tuple from the TidRangeScan node's currentRelation + * using the TIDs in the TidRangeScanState information. + * + * ---------------------------------------------------------------- + */ +static TupleTableSlot *TidRangeNext(ExtensiblePlanState *node) +{ + TableScanDesc scandesc; + EState *estate; + ScanDirection direction; + TupleTableSlot *slot; + TidRangeScanState *trss; + + /* + * extract necessary information from TID scan node + */ + scandesc = node->ss.ss_currentScanDesc; + estate = node->ss.ps.state; + slot = node->ss.ss_ScanTupleSlot; + direction = estate->es_direction; + trss = (TidRangeScanState *)node; + + if (!trss->trss_inScan) { + /* First time through, compute TID range to scan */ + if (!TidRangeEval(trss)) + return NULL; + + if (scandesc == NULL) { + scandesc = table_beginscan_tidrange(node->ss.ss_currentRelation, estate->es_snapshot, &trss->trss_mintid, + &trss->trss_maxtid); + node->ss.ss_currentScanDesc = scandesc; + } else { + /* rescan with the updated TID range */ + table_rescan_tidrange(scandesc, &trss->trss_mintid, &trss->trss_maxtid); + } + + trss->trss_inScan = true; + } + + /* Fetch the next tuple. */ + if (!heap_getnextslot_tidrange(scandesc, direction, slot, &trss->trss_mintid, &trss->trss_maxtid)) { + trss->trss_inScan = false; + ExecClearTuple(slot); + } + + return slot; +} + +/* + * TidRangeRecheck -- access method routine to recheck a tuple in EvalPlanQual + */ +static bool TidRangeRecheck(TidRangeScanState *node, TupleTableSlot *slot) +{ + return true; +} + +/* ---------------------------------------------------------------- + * ExecTidRangeScan(node) + * + * Scans the relation using tids and returns the next qualifying tuple. + * We call the ExecScan() routine and pass it the appropriate + * access method functions. + * + * Conditions: + * -- the "cursor" maintained by the AMI is positioned at the tuple + * returned previously. + * + * Initial States: + * -- the relation indicated is opened for TID range scanning. + * ---------------------------------------------------------------- + */ +static TupleTableSlot *ExecTidRangeScan(ExtensiblePlanState *pstate) +{ + return ExecScan(&pstate->ss, (ExecScanAccessMtd)TidRangeNext, (ExecScanRecheckMtd)TidRangeRecheck); +} + +/* ---------------------------------------------------------------- + * ExecEndTidRangeScan + * + * Releases any storage allocated through C routines. + * Returns nothing. + * ---------------------------------------------------------------- + */ +static void ExecEndTidRangeScan(ExtensiblePlanState *node) +{ + TableScanDesc scan = node->ss.ss_currentScanDesc; + + if (scan != NULL) + heap_endscan(scan); + + /* + * Free the exprcontext + */ + ExecFreeExprContext(&node->ss.ps); + + /* + * clear out tuple table slots + */ + if (node->ss.ps.ps_ResultTupleSlot) + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + ExecClearTuple(node->ss.ss_ScanTupleSlot); +} + +/* + * ExplainTidRangeScan - A method of ExtensiblePlanState; that shows extra info + * on EXPLAIN command. + */ +static void ExplainTidRangeScan(ExtensiblePlanState *node, List *ancestors, ExplainState *es) +{ + TidRangeScanState *ctss = (TidRangeScanState *) node; + ExtensiblePlan *cscan = (ExtensiblePlan *) ctss->css.ss.ps.plan; + + /* logic copied from show_qual and show_expression */ + if (cscan->extensible_exprs) + { + bool useprefix = es->verbose; + Node *qual; + List *context; + char *exprstr; + + /* + * The tidrangequals list has AND semantics, so be sure to + * show it as an AND condition. + */ + qual = (Node*)make_ands_explicit(cscan->extensible_exprs); + + /* Set up deparsing context */ + context = deparse_context_for_planstate((Node*)ctss, ancestors, es->rtable); + + /* Deparse the expression */ + exprstr = deparse_expression(qual, context, useprefix, false); + + /* And add to es->str */ + ExplainPropertyText("TID Cond", exprstr, es); + } +} + +static tidrangescan_context* get_session_context() +{ + if (u_sess->attr.attr_common.extension_session_vars_array[tidrangescan_index] == NULL) { + init_session_vars(); + } + return (tidrangescan_context*)u_sess->attr.attr_common.extension_session_vars_array[tidrangescan_index]; +} + +void set_extension_index(uint32 index) +{ + tidrangescan_index = index; +} + +void init_session_vars(void) +{ + RepallocSessionVarsArrayIfNecessary(); + + tidrangescan_context *cxt = (tidrangescan_context*)MemoryContextAlloc(u_sess->self_mem_cxt, sizeof(tidrangescan_context)); + u_sess->attr.attr_common.extension_session_vars_array[tidrangescan_index] = cxt; + cxt->enable_tidrangescan = true; + + DefineCustomBoolVariable("enable_tidrangescan", + "Enables the planner's use of tidrange-scan plans.", + NULL, + &ENABLE_TIDRANGESCAN, + true, + PGC_USERSET, + 0, + NULL, NULL, NULL); +} + +/* + * Entrypoint of this extension + */ +void _PG_init(void) +{ + ereport(LOG, (errmsg("init tidrangescan."))); + set_rel_pathlist_hook = SetTidRangeScanPath; +} + +void _PG_fini(void) +{ + set_rel_pathlist_hook = NULL; +} \ No newline at end of file diff --git a/contrib/tidrangescan/tidrangescan.h b/contrib/tidrangescan/tidrangescan.h new file mode 100644 index 0000000000000000000000000000000000000000..45f996c7bc0b7d3dddfee813a5aad43b7cdb9994 --- /dev/null +++ b/contrib/tidrangescan/tidrangescan.h @@ -0,0 +1,14 @@ +#ifndef TIDRANGESCAN_H +#define TIDRANGESCAN_H + +extern "C" void _PG_init(void); +extern "C" void _PG_fini(void); +extern "C" void init_session_vars(void); +extern "C" void set_extension_index(uint32 index); +extern "C" void tidrangescan_invoke(void); + +typedef struct tidrangescan_context { + bool enable_tidrangescan; +} tidrangescan_context; + +#endif \ No newline at end of file