contrib/Makefile | 1 + contrib/ctidscan/Makefile | 18 + contrib/ctidscan/ctidscan.c | 885 +++++++++++++++++++++++++++++++++ contrib/ctidscan/ctidscan.control | 5 + contrib/ctidscan/expected/ctidscan.out | 331 ++++++++++++ contrib/ctidscan/sql/ctidscan.sql | 59 +++ src/include/catalog/pg_operator.h | 3 + 7 files changed, 1302 insertions(+) diff --git a/contrib/Makefile b/contrib/Makefile index b37d0dd..9b4b6ad 100644 --- a/contrib/Makefile +++ b/contrib/Makefile @@ -12,6 +12,7 @@ SUBDIRS = \ btree_gist \ chkpass \ citext \ + ctidscan \ cube \ dblink \ dict_int \ diff --git a/contrib/ctidscan/Makefile b/contrib/ctidscan/Makefile new file mode 100644 index 0000000..fbea380 --- /dev/null +++ b/contrib/ctidscan/Makefile @@ -0,0 +1,18 @@ +# contrib/ctidscan/Makefile + +MODULES = ctidscan + +EXTENSION = ctidscan + +REGRESS = ctidscan + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/ctidscan +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c new file mode 100644 index 0000000..a8af99e --- /dev/null +++ b/contrib/ctidscan/ctidscan.c @@ -0,0 +1,885 @@ +/* + * ctidscan.c + * + * Definition of Custom TidScan implementation. + * + * It is designed to demonstrate Custom Scan APIs; that allows to override + * a part of executor node. This extension focus on a workload that tries + * to fetch records with tid larger or less than a particular value. + * In case when inequality operators were given, this module construct + * a custom scan path that enables to skip records not to be read. Then, + * if it was the cheapest one, it shall be used to run the query. + * Custom Scan APIs callbacks this extension when executor tries to fetch + * underlying records, then it utilizes existing heap_getnext() but seek + * the records to be read prior to fetching the first record. + * + * Portions Copyright (c) 2014, PostgreSQL Global Development Group + */ +#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/nodeCustom.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 "storage/bufmgr.h" +#include "storage/itemptr.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/spccache.h" + +PG_MODULE_MAGIC; + +typedef struct { + CustomPath cpath; + List *ctid_quals; +} CtidScanPath; + +typedef struct { + CustomScan cscan; + List *ctid_quals; +} CtidScanPlan; + +typedef struct { + CustomScanState css; + List *ctid_quals; /* list of ExprState for inequality ops */ +} CtidScanState; + +/* function declarations */ +void _PG_init(void); + +static void CreateCtidScanPath(PlannerInfo *root, + RelOptInfo *baserel, + RangeTblEntry *rte); +static Plan *PlanCtidScanPath(PlannerInfo *root, + RelOptInfo *rel, + CustomPath *best_path, + List *tlist, + List *clauses); +static void TextOutCtidScanPath(StringInfo str, const CustomPath *cpath); + +static void SetCtidScanPlanRef(PlannerInfo *root, + CustomScan *custom_plan, + int rtoffset); +static void FinalizeCtidScanPlan(PlannerInfo *root, + CustomScan *custom_plan, + bool (*finalize_primnode)(), + void *finalize_context); +static Node *CreateCtidScanState(CustomScan *custom_plan); +static void TextOutCtidScanPlan(StringInfo str, const CustomScan *node); +static CustomScan *CopyCtidScanPlan(const CustomScan *from); + +static void BeginCtidScan(CustomScanState *node, EState *estate, int eflags); +static void ReScanCtidScan(CustomScanState *node); +static TupleTableSlot *ExecCtidScan(CustomScanState *node); +static void EndCtidScan(CustomScanState *node); +static void ExplainCtidScan(CustomScanState *node, List *ancestors, + ExplainState *es); + +/* static table of custom-scan callbacks */ +static CustomPathMethods ctidscan_path_methods = { + "ctidscan", /* CustomName */ + CreateCtidScanPath, /* CreateCustomScanPath */ + PlanCtidScanPath, /* PlanCustomPath */ + TextOutCtidScanPath, /* TextOutCustomPath */ +}; + +static CustomScanMethods ctidscan_scan_methods = { + "ctidscan", /* CustomName */ + SetCtidScanPlanRef, /* SetCustomScanRef */ + FinalizeCtidScanPlan, /* FinalizeCustomScan */ + CreateCtidScanState, /* CreateCustomScanState */ + TextOutCtidScanPlan, /* TextOutCustomScan */ + CopyCtidScanPlan, /* CopyCustomScan */ +}; + +static CustomExecMethods ctidscan_exec_methods = { + "ctidscan", /* CustomName */ + BeginCtidScan, /* BeginCustomScan */ + ExecCtidScan, /* ExecCustomScan */ + EndCtidScan, /* EndCustomScan */ + ReScanCtidScan, /* ReScanCustomScan */ + NULL, /* MarkPosCustomScan */ + NULL, /* RestrPosCustomScan */ + ExplainCtidScan, /* ExplainCustomScan */ + NULL, /* GetSpecialCustomVar */ +}; + +#define IsCTIDVar(node,rtindex) \ + ((node) != NULL && \ + IsA((node), Var) && \ + ((Var *) (node))->varno == (rtindex) && \ + ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \ + ((Var *) (node))->varlevelsup == 0) + +/* + * CTidQualFromExpr + * + * It checks whether the given restriction clauses enables to determine + * the zone to be scanned, or not. If one or more restriction clauses are + * available, it returns a list of them, or NIL elsewhere. + * The caller can consider all the conditions are chained with AND- + * boolean operator, so all the operator works for narrowing down the + * scope of custom tid scan. + */ +static List * +CTidQualFromExpr(Node *expr, int varno) +{ + if (is_opclause(expr)) + { + OpExpr *op = (OpExpr *) expr; + Node *arg1; + Node *arg2; + Node *other = NULL; + + /* only inequality operators are candidate */ + if (op->opno != TIDLessOperator && + op->opno != TIDLessEqualOperator && + op->opno != TIDGreaterOperator && + op->opno != TIDGreaterEqualOperator) + return NULL; + + if (list_length(op->args) != 2) + return false; + + arg1 = linitial(op->args); + arg2 = lsecond(op->args); + + if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + if (exprType(other) != TIDOID) + return NULL; /* probably can't happen */ + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL; + + return list_make1(copyObject(op)); + } + else if (and_clause(expr)) + { + List *rlst = NIL; + ListCell *lc; + + foreach(lc, ((BoolExpr *) expr)->args) + { + List *temp = CTidQualFromExpr((Node *) lfirst(lc), varno); + + rlst = list_concat(rlst, temp); + } + return rlst; + } + return NIL; +} + +/* + * CTidEstimateCosts + * + * It estimates cost to scan the target relation according to the given + * restriction clauses. Its logic to scan relations are almost same as + * SeqScan doing, because it uses regular heap_getnext(), except for + * the number of tuples to be scanned if restriction clauses work well. +*/ +static void +CTidEstimateCosts(PlannerInfo *root, + RelOptInfo *baserel, + CtidScanPath *ctid_path) +{ + Path *path = &ctid_path->cpath.path; + List *ctid_quals = ctid_path->ctid_quals; + ListCell *lc; + double ntuples; + ItemPointerData ip_min; + ItemPointerData ip_max; + bool has_min_val = false; + bool has_max_val = false; + BlockNumber num_pages; + Cost startup_cost = 0; + Cost run_cost = 0; + Cost cpu_per_tuple; + QualCost qpqual_cost; + QualCost ctid_qual_cost; + double spc_random_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 (path->param_info) + path->rows = path->param_info->ppi_rows; + else + path->rows = baserel->rows; + + /* Estimate how many tuples we may retrieve */ + ItemPointerSet(&ip_min, 0, 0); + ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber); + foreach (lc, ctid_quals) + { + OpExpr *op = lfirst(lc); + Oid opno; + Node *other; + + Assert(is_opclause(op)); + if (IsCTIDVar(linitial(op->args), baserel->relid)) + { + opno = op->opno; + other = lsecond(op->args); + } + else if (IsCTIDVar(lsecond(op->args), baserel->relid)) + { + /* To simplifies, we assume as if Var node is 1st argument */ + opno = get_commutator(op->opno); + other = linitial(op->args); + } + else + elog(ERROR, "could not identify CTID variable"); + + if (IsA(other, Const)) + { + ItemPointer ip = (ItemPointer)(((Const *) other)->constvalue); + + /* + * Just an rough estimation, we don't distinct inequality and + * inequality-or-equal operator. + */ + switch (opno) + { + case TIDLessOperator: + case TIDLessEqualOperator: + if (ItemPointerCompare(ip, &ip_max) < 0) + ItemPointerCopy(ip, &ip_max); + has_max_val = true; + break; + case TIDGreaterOperator: + case TIDGreaterEqualOperator: + if (ItemPointerCompare(ip, &ip_min) > 0) + ItemPointerCopy(ip, &ip_min); + has_min_val = true; + break; + default: + elog(ERROR, "unexpected operator code: %u", op->opno); + break; + } + } + } + + /* estimated number of tuples in this relation */ + ntuples = baserel->pages * baserel->tuples; + + if (has_min_val && has_max_val) + { + /* case of both side being bounded */ + BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid); + BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid); + + bnum_max = Min(bnum_max, baserel->pages); + bnum_min = Max(bnum_min, 0); + num_pages = Min(bnum_max - bnum_min + 1, 1); + } + else if (has_min_val) + { + /* case of only lower side being bounded */ + BlockNumber bnum_max = baserel->pages; + BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid); + + bnum_min = Max(bnum_min, 0); + num_pages = Min(bnum_max - bnum_min + 1, 1); + } + else if (has_max_val) + { + /* case of only upper side being bounded */ + BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid); + BlockNumber bnum_min = 0; + + bnum_max = Min(bnum_max, baserel->pages); + num_pages = Min(bnum_max - bnum_min + 1, 1); + } + else + { + /* + * Just a rough estimation. We assume half of records shall be + * read using this restriction clause, but indeterministic until + * executor run it actually. + */ + num_pages = Max((baserel->pages + 1) / 2, 1); + } + ntuples *= ((double) num_pages) / ((double) baserel->pages); + + /* + * The TID qual expressions will be computed once, any other baserestrict + * quals once per retrieved tuple. + */ + cost_qual_eval(&ctid_qual_cost, ctid_quals, root); + + /* fetch estimated page cost for tablespace containing table */ + get_tablespace_page_costs(baserel->reltablespace, + &spc_random_page_cost, + NULL); + + /* disk costs --- assume each tuple on a different page */ + run_cost += spc_random_page_cost * ntuples; + + /* + * Add scanning CPU costs + * (logic copied from get_restriction_qual_cost) + */ + if (path->param_info) + { + /* Include costs of pushed-down clauses */ + cost_qual_eval(&qpqual_cost, path->param_info->ppi_clauses, root); + + qpqual_cost.startup += baserel->baserestrictcost.startup; + qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple; + } + else + qpqual_cost = baserel->baserestrictcost; + + /* + * We don't decrease cost for the inequality operators, because + * it is subset of qpquals and still in. + */ + startup_cost += qpqual_cost.startup + ctid_qual_cost.per_tuple; + cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple - + ctid_qual_cost.per_tuple; + run_cost = cpu_per_tuple * ntuples; + + path->startup_cost = startup_cost; + path->total_cost = startup_cost + run_cost; +} + +/* + * CreateCtidScanPath - entrypoint of the series of custom-scan execution. + * It adds CustomPath if referenced relation has inequality expressions on + * the ctid system column. + */ +static void +CreateCtidScanPath(PlannerInfo *root, RelOptInfo *baserel, RangeTblEntry *rte) +{ + char relkind; + ListCell *lc; + List *ctid_quals = NIL; + + /* only plain relations are supported */ + if (rte->rtekind != RTE_RELATION) + return; + relkind = get_rel_relkind(rte->relid); + if (relkind != RELKIND_RELATION && + relkind != RELKIND_MATVIEW && + relkind != RELKIND_TOASTVALUE) + return; + + /* walk on the restrict info */ + foreach (lc, baserel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + List *temp; + + if (!IsA(rinfo, RestrictInfo)) + continue; /* probably should never happen */ + temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid); + ctid_quals = list_concat(ctid_quals, temp); + } + + /* + * OK, it is case when a part of restriction clause makes sense to + * reduce number of tuples, so we will add a custom scan path being + * provided by this module. + */ + if (ctid_quals != NIL) + { + CtidScanPath *ctid_path; + Relids required_outer; + + /* + * We don't support pushing join clauses into the quals of a ctidscan, + * but it could still have required parameterization due to LATERAL + * refs in its tlist. + */ + required_outer = baserel->lateral_relids; + + ctid_path = palloc0(sizeof(CtidScanPath)); + ctid_path->cpath.path.type = T_CustomPath; + ctid_path->cpath.path.pathtype = T_CustomScan; + ctid_path->cpath.path.parent = baserel; + ctid_path->cpath.path.param_info + = get_baserel_parampathinfo(root, baserel, required_outer); + ctid_path->cpath.flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN; + ctid_path->cpath.methods = &ctidscan_path_methods; + ctid_path->ctid_quals = ctid_quals; + + CTidEstimateCosts(root, baserel, ctid_path); + + add_path(baserel, &ctid_path->cpath.path); + } +} + +/* + * CreateCtidScanPlan - A method of CustomPath; that populate a custom + * object being delivered from CustomScan type, according to the supplied + * CustomPath object. + */ +static Plan * +PlanCtidScanPath(PlannerInfo *root, + RelOptInfo *rel, + CustomPath *best_path, + List *tlist, + List *clauses) +{ + CtidScanPath *ctid_path = (CtidScanPath *) best_path; + CtidScanPlan *ctid_scan; + List *ctid_quals = ctid_path->ctid_quals; + + ctid_scan = palloc0(sizeof(CtidScanPlan)); + NodeSetTag(ctid_scan, T_CustomScan); + ctid_scan->cscan.flags = best_path->flags; + ctid_scan->cscan.methods = &ctidscan_scan_methods; + + /* set scanrelid */ + ctid_scan->cscan.scan.scanrelid = rel->relid; + /* set targetlist as is */ + ctid_scan->cscan.scan.plan.targetlist = tlist; + /* reduce RestrictInfo list to bare expressions */ + ctid_scan->cscan.scan.plan.qual + = extract_actual_clauses(clauses, false); + /* set ctid related quals */ + if (best_path->path.param_info) + ctid_quals = (List *) + replace_nestloop_params(root, (Node *)ctid_quals); + ctid_scan->ctid_quals = ctid_quals; + + /* + * If there are any pseudoconstant clauses attached to this node, insert a + * gating Result node that evaluates the pseudoconstants as one-time + * quals. + */ + if (root->hasPseudoConstantQuals) + { + List *pseudoconstants = extract_actual_clauses(clauses, true); + + if (pseudoconstants != NIL) + return (Plan *) make_result(root, + tlist, + (Node *) pseudoconstants, + (Plan *) ctid_scan); + } + return (Plan *) ctid_scan; +} + +/* + * TextOutCtidScanPath - A method of CustomPath; that shows a text + * representation of the supplied CustomPath object. + */ +static void +TextOutCtidScanPath(StringInfo str, const CustomPath *cpath) +{ + CtidScanPath *ctid_path = (CtidScanPath *)cpath; + + appendStringInfo(str, " :ctid_quals %s", + nodeToString(ctid_path->ctid_quals)); +} + +/* + * SetCtidScanPlanRef - A method of CustomScan; that fixes up rtindex + * of Var nodes + */ +#define fix_scan_list(root, lst, rtoffset) \ + ((List *) fix_scan_expr(root, (Node *) (lst), rtoffset)) + +static void +SetCtidScanPlanRef(PlannerInfo *root, + CustomScan *custom_plan, + int rtoffset) +{ + CtidScanPlan *ctidscan = (CtidScanPlan *) custom_plan; + Scan *scan = &ctidscan->cscan.scan; + + scan->scanrelid += rtoffset; + scan->plan.targetlist = + fix_scan_list(root, scan->plan.targetlist, rtoffset); + scan->plan.qual = + fix_scan_list(root, scan->plan.qual, rtoffset); + ctidscan->ctid_quals = + fix_scan_list(root, ctidscan->ctid_quals, rtoffset); +} + +/* + * FinalizeCtidScanPlan - A method of CustomScan; that handles callbacks + * by finalize_plan(). + */ +static void +FinalizeCtidScanPlan(PlannerInfo *root, + CustomScan *custom_plan, + bool (*finalize_primnode)(), + void *finalize_context) +{ + CtidScanPlan *ctid_plan = (CtidScanPlan *) custom_plan; + + /* applies finalize_primnode() on ctid_quals also */ + finalize_primnode((Node *)ctid_plan->ctid_quals, finalize_context); +} + +/* + * CreateCtidScanState - A method of CustomScan; that populate a custom + * object being delivered from CustomScanState type, according to the + * supplied CustomPath object. + */ +static Node * +CreateCtidScanState(CustomScan *custom_plan) +{ + CtidScanState *ctss = palloc0(sizeof(CtidScanState)); + + NodeSetTag(ctss, T_CustomScanState); + ctss->css.flags = custom_plan->flags; + ctss->css.methods = &ctidscan_exec_methods; + + return (Node *)&ctss->css; +} + +/* + * TextOutCtidScanPlan - A method of CustomScan; that generates text + * representation of the given object. + */ +static void +TextOutCtidScanPlan(StringInfo str, const CustomScan *node) +{ + CtidScanPlan *ctid_plan = (CtidScanPlan *) node; + + appendStringInfo(str, " :ctid_quals %s", + nodeToString(ctid_plan->ctid_quals)); +} + +/* + * CopyCtidScanPlan - A method of CustomScan; that create a copied object. + */ +static CustomScan * +CopyCtidScanPlan(const CustomScan *from) +{ + CtidScanPlan *oldnode = (CtidScanPlan *) from; + CtidScanPlan *newnode = palloc0(sizeof(CtidScanPlan)); + + NodeSetTag(newnode, T_CustomScan); + newnode->ctid_quals = copyObject(oldnode->ctid_quals); + + return &newnode->cscan; +} + +/* + * BeginCtidScan - A method of CustomScanState; that initializes + * the supplied CtidScanState object, at beginning of the executor. + */ +static void +BeginCtidScan(CustomScanState *node, EState *estate, int eflags) +{ + CtidScanState *ctss = (CtidScanState *) node; + CtidScanPlan *ctid_plan = (CtidScanPlan *) node->ss.ps.plan; + + /* + * In case of custom-plan provider that offers an alternative way + * to scan a particular relation, most of the needed initialization, + * like relation open or assignment of scan tuple-slot or projection + * info, shall be done by the core implementation. So, all we need + * to have is initialization of own local properties. + */ + ctss->ctid_quals = (List *) + ExecInitExpr((Expr *)ctid_plan->ctid_quals, &node->ss.ps); + + /* Do nothing anymore in EXPLAIN (no ANALYZE) case. */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; + + /* scandesc shall be set later */ + ctss->css.ss.ss_currentScanDesc = NULL; +} + +/* + * ReScanCtidScan - A method of CustomScanState; that rewind the current + * seek position. + */ +static void +ReScanCtidScan(CustomScanState *node) +{ + CtidScanState *ctss = (CtidScanState *)node; + HeapScanDesc scan = ctss->css.ss.ss_currentScanDesc; + EState *estate = node->ss.ps.state; + ScanDirection direction = estate->es_direction; + Relation relation = ctss->css.ss.ss_currentRelation; + ExprContext *econtext = ctss->css.ss.ps.ps_ExprContext; + ScanKeyData keys[2]; + bool has_ubound = false; + bool has_lbound = false; + ItemPointerData ip_max; + ItemPointerData ip_min; + ListCell *lc; + + /* once close the existing scandesc, if any */ + if (scan) + { + heap_endscan(scan); + scan = ctss->css.ss.ss_currentScanDesc = NULL; + } + + /* walks on the inequality operators */ + foreach (lc, ctss->ctid_quals) + { + FuncExprState *fexstate = (FuncExprState *) lfirst(lc); + OpExpr *op = (OpExpr *)fexstate->xprstate.expr; + Node *arg1 = linitial(op->args); + Node *arg2 = lsecond(op->args); + Index scanrelid; + Oid opno; + ExprState *exstate; + ItemPointer itemptr; + bool isnull; + + scanrelid = ((Scan *)ctss->css.ss.ps.plan)->scanrelid; + if (IsCTIDVar(arg1, scanrelid)) + { + exstate = (ExprState *) lsecond(fexstate->args); + opno = op->opno; + } + else if (IsCTIDVar(arg2, scanrelid)) + { + exstate = (ExprState *) linitial(fexstate->args); + opno = get_commutator(op->opno); + } + else + elog(ERROR, "could not identify CTID variable"); + + itemptr = (ItemPointer) + DatumGetPointer(ExecEvalExprSwitchContext(exstate, + econtext, + &isnull, + NULL)); + if (isnull) + { + /* + * Whole of the restriction clauses chained with AND- boolean + * operators because false, if one of the clauses has NULL result. + * So, we can immediately break the evaluation to inform caller + * it does not make sense to scan any more. + * In this case, scandesc is kept to NULL. + */ + return; + } + + switch (opno) + { + case TIDLessOperator: + if (!has_ubound || + ItemPointerCompare(itemptr, &ip_max) <= 0) + { + ScanKeyInit(&keys[0], + SelfItemPointerAttributeNumber, + BTLessStrategyNumber, + F_TIDLT, + PointerGetDatum(itemptr)); + ItemPointerCopy(itemptr, &ip_max); + has_ubound = true; + } + break; + + case TIDLessEqualOperator: + if (!has_ubound || + ItemPointerCompare(itemptr, &ip_max) < 0) + { + ScanKeyInit(&keys[0], + SelfItemPointerAttributeNumber, + BTLessEqualStrategyNumber, + F_TIDLE, + PointerGetDatum(itemptr)); + ItemPointerCopy(itemptr, &ip_max); + has_ubound = true; + } + break; + + case TIDGreaterOperator: + if (!has_lbound || + ItemPointerCompare(itemptr, &ip_min) >= 0) + { + ScanKeyInit(&keys[1], + SelfItemPointerAttributeNumber, + BTGreaterStrategyNumber, + F_TIDGT, + PointerGetDatum(itemptr)); + ItemPointerCopy(itemptr, &ip_min); + has_lbound = true; + } + break; + + case TIDGreaterEqualOperator: + if (!has_lbound || + ItemPointerCompare(itemptr, &ip_min) > 0) + { + ScanKeyInit(&keys[1], + SelfItemPointerAttributeNumber, + BTGreaterEqualStrategyNumber, + F_TIDGE, + PointerGetDatum(itemptr)); + ItemPointerCopy(itemptr, &ip_min); + has_lbound = true; + } + break; + + default: + elog(ERROR, "unsupported operator"); + break; + } + } + + /* begin heapscan with the key above */ + if (has_ubound && has_lbound) + scan = heap_beginscan(relation, estate->es_snapshot, 2, &keys[0]); + else if (has_ubound) + scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[0]); + else if (has_lbound) + scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[1]); + else + scan = heap_beginscan(relation, estate->es_snapshot, 0, NULL); + + /* Seek the starting position, if possible */ + if (direction == ForwardScanDirection && has_lbound) + { + BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_min.ip_blkid), + scan->rs_nblocks - 1); + scan->rs_startblock = blknum; + } + else if (direction == BackwardScanDirection && has_ubound) + { + BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid), + scan->rs_nblocks - 1); + scan->rs_startblock = blknum; + } + ctss->css.ss.ss_currentScanDesc = scan; +} + +/* + * CTidAccessCustomScan + * + * Access method of ExecCtidScan below. It fetches a tuple from the underlying + * heap scan that was started from the point according to the tid clauses. + */ +static TupleTableSlot * +CTidAccessCustomScan(CustomScanState *node) +{ + CtidScanState *ctss = (CtidScanState *) node; + HeapScanDesc scan; + TupleTableSlot *slot; + EState *estate = node->ss.ps.state; + ScanDirection direction = estate->es_direction; + HeapTuple tuple; + + if (!ctss->css.ss.ss_currentScanDesc) + ReScanCtidScan(node); + scan = ctss->css.ss.ss_currentScanDesc; + Assert(scan != NULL); + + /* + * get the next tuple from the table + */ + tuple = heap_getnext(scan, direction); + if (!HeapTupleIsValid(tuple)) + return NULL; + + slot = ctss->css.ss.ss_ScanTupleSlot; + ExecStoreTuple(tuple, slot, scan->rs_cbuf, false); + + return slot; +} + +static bool +CTidRecheckCustomScan(CustomScanState *node, TupleTableSlot *slot) +{ + return true; +} + +/* + * ExecCtidScan - A method of CustomScanState; that fetches a tuple + * from the relation, if exist anymore. + */ +static TupleTableSlot * +ExecCtidScan(CustomScanState *node) +{ + return ExecScan(&node->ss, + (ExecScanAccessMtd) CTidAccessCustomScan, + (ExecScanRecheckMtd) CTidRecheckCustomScan); +} + +/* + * CTidEndCustomScan - A method of CustomScanState; that closes heap and + * scan descriptor, and release other related resources. + */ +static void +EndCtidScan(CustomScanState *node) +{ + CtidScanState *ctss = (CtidScanState *)node; + + if (ctss->css.ss.ss_currentScanDesc) + heap_endscan(ctss->css.ss.ss_currentScanDesc); +} + +/* + * ExplainCtidScan - A method of CustomScanState; that shows extra info + * on EXPLAIN command. + */ +static void +ExplainCtidScan(CustomScanState *node, List *ancestors, ExplainState *es) +{ + CtidScanState *ctss = (CtidScanState *) node; + CtidScanPlan *ctid_plan = (CtidScanPlan *) ctss->css.ss.ps.plan; + + /* logic copied from show_qual and show_expression */ + if (ctid_plan->ctid_quals) + { + bool useprefix = es->verbose; + Node *qual; + List *context; + char *exprstr; + + /* Convert AND list to explicit AND */ + qual = (Node *) make_ands_explicit(ctid_plan->ctid_quals); + + /* Set up deparsing context */ + context = deparse_context_for_planstate((Node *)&node->ss.ps, + ancestors, + es->rtable, + es->rtable_names); + + /* Deparse the expression */ + exprstr = deparse_expression(qual, context, useprefix, false); + + /* And add to es->str */ + ExplainPropertyText("ctid quals", exprstr, es); + } +} + +/* + * Entrypoint of this extension + */ +void +_PG_init(void) +{ + register_custom_path_provider(&ctidscan_path_methods); +} diff --git a/contrib/ctidscan/ctidscan.control b/contrib/ctidscan/ctidscan.control new file mode 100644 index 0000000..ad63432 --- /dev/null +++ b/contrib/ctidscan/ctidscan.control @@ -0,0 +1,5 @@ +# ctidscan extension +comment = 'example implementation for custom-plan interface' +default_version = '1.0' +module_pathname = '$libdir/ctidscan' +relocatable = true diff --git a/contrib/ctidscan/expected/ctidscan.out b/contrib/ctidscan/expected/ctidscan.out new file mode 100644 index 0000000..9963a43 --- /dev/null +++ b/contrib/ctidscan/expected/ctidscan.out @@ -0,0 +1,331 @@ +-- +-- Regression Tests for Custom Plan APIs +-- +-- construction of test data +SET client_min_messages TO 'warning'; +CREATE SCHEMA regtest_custom_scan; +SET search_path TO regtest_custom_scan, public; +CREATE TABLE t1 ( + a int primary key, + b text +); +INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s); +VACUUM ANALYZE t1; +CREATE TABLE t2 ( + x int primary key, + y text +); +INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s); +VACUUM ANALYZE t2; +RESET client_min_messages; +-- +-- Check Plans if no special extension is loaded. +-- +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40; + QUERY PLAN +-------------------------------- + Index Scan using t1_pkey on t1 + Index Cond: (a = 40) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%'; + QUERY PLAN +-------------------------------- + Seq Scan on t1 + Filter: (b ~~ '%789%'::text) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid; + QUERY PLAN +------------------------------------ + Tid Scan on t1 + TID Cond: (ctid = '(2,10)'::tid) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + QUERY PLAN +------------------------------------------------------------------ + Seq Scan on t1 + Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid)) +(2 rows) + +-- +-- Plan for same query but ctidscan was loaded +-- +LOAD '$libdir/ctidscan'; +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40; + QUERY PLAN +-------------------------------- + Index Scan using t1_pkey on t1 + Index Cond: (a = 40) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%'; + QUERY PLAN +-------------------------------- + Seq Scan on t1 + Filter: (b ~~ '%789%'::text) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid; + QUERY PLAN +------------------------------------ + Tid Scan on t1 + TID Cond: (ctid = '(2,10)'::tid) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + QUERY PLAN +---------------------------------------------------------------------- + Custom Scan (ctidscan) on t1 + Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid)) + ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid)) +(3 rows) + +EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid; + QUERY PLAN +-------------------------------------------------- + Merge Join + Merge Cond: (t1.ctid = t2.ctid) + -> Sort + Sort Key: t1.ctid + -> Custom Scan (ctidscan) on t1 + Filter: (ctid < '(2,10)'::tid) + ctid quals: (ctid < '(2,10)'::tid) + -> Sort + Sort Key: t2.ctid + -> Custom Scan (ctidscan) on t2 + Filter: (ctid > '(1,75)'::tid) + ctid quals: (ctid > '(1,75)'::tid) +(12 rows) + +SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid; + ctid | a | b +---------+-----+---------------------------------- + (0,1) | 1 | c4ca4238a0b923820dcc509a6f75849b + (0,2) | 2 | c81e728d9d4c2f636f067f89cc14862c + (0,3) | 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3 + (0,4) | 4 | a87ff679a2f3e71d9181a67b7542122c + (0,5) | 5 | e4da3b7fbbce2345d7772b0674a318d5 + (0,6) | 6 | 1679091c5a880faf6fb5e6087eb1b2dc + (0,7) | 7 | 8f14e45fceea167a5a36dedd4bea2543 + (0,8) | 8 | c9f0f895fb98ab9159f51fd0297e236d + (0,9) | 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26 + (0,10) | 10 | d3d9446802a44259755d38e6d163e820 + (0,11) | 11 | 6512bd43d9caa6e02c990b0a82652dca + (0,12) | 12 | c20ad4d76fe97759aa27a0c99bff6710 + (0,13) | 13 | c51ce410c124a10e0db5e4b97fc2af39 + (0,14) | 14 | aab3238922bcc25a6f606eb525ffdc56 + (0,15) | 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3 + (0,16) | 16 | c74d97b01eae257e44aa9d5bade97baf + (0,17) | 17 | 70efdf2ec9b086079795c442636b55fb + (0,18) | 18 | 6f4922f45568161a8cdf4ad2299f6d23 + (0,19) | 19 | 1f0e3dad99908345f7439f8ffabdffc4 + (0,20) | 20 | 98f13708210194c475687be6106a3b84 + (0,21) | 21 | 3c59dc048e8850243be8079a5c74d079 + (0,22) | 22 | b6d767d2f8ed5d21a44b0e5886680cb9 + (0,23) | 23 | 37693cfc748049e45d87b8c7d8b9aacd + (0,24) | 24 | 1ff1de774005f8da13f42943881c655f + (0,25) | 25 | 8e296a067a37563370ded05f5a3bf3ec + (0,26) | 26 | 4e732ced3463d06de0ca9a15b6153677 + (0,27) | 27 | 02e74f10e0327ad868d138f2b4fdd6f0 + (0,28) | 28 | 33e75ff09dd601bbe69f351039152189 + (0,29) | 29 | 6ea9ab1baa0efb9e19094440c317e21b + (0,30) | 30 | 34173cb38f07f89ddbebc2ac9128303f + (0,31) | 31 | c16a5320fa475530d9583c34fd356ef5 + (0,32) | 32 | 6364d3f0f495b6ab9dcf8d3b5c6e0b01 + (0,33) | 33 | 182be0c5cdcd5072bb1864cdee4d3d6e + (0,34) | 34 | e369853df766fa44e1ed0ff613f563bd + (0,35) | 35 | 1c383cd30b7c298ab50293adfecb7b18 + (0,36) | 36 | 19ca14e7ea6328a42e0eb13d585e4c22 + (0,37) | 37 | a5bfc9e07964f8dddeb95fc584cd965d + (0,38) | 38 | a5771bce93e200c36f7cd9dfd0e5deaa + (0,39) | 39 | d67d8ab4f4c10bf22aa353e27879133c + (0,40) | 40 | d645920e395fedad7bbbed0eca3fe2e0 + (0,41) | 41 | 3416a75f4cea9109507cacd8e2f2aefc + (0,42) | 42 | a1d0c6e83f027327d8461063f4ac58a6 + (0,43) | 43 | 17e62166fc8586dfa4d1bc0e1742c08b + (0,44) | 44 | f7177163c833dff4b38fc8d2872f1ec6 + (0,45) | 45 | 6c8349cc7260ae62e3b1396831a8398f + (0,46) | 46 | d9d4f495e875a2e075a1a4a6e1b9770f + (0,47) | 47 | 67c6a1e7ce56d3d6fa748ab6d9af3fd7 + (0,48) | 48 | 642e92efb79421734881b53e1e1b18b6 + (0,49) | 49 | f457c545a9ded88f18ecee47145a72c0 + (0,50) | 50 | c0c7c76d30bd3dcaefc96f40275bdc0a + (0,51) | 51 | 2838023a778dfaecdc212708f721b788 + (0,52) | 52 | 9a1158154dfa42caddbd0694a4e9bdc8 + (0,53) | 53 | d82c8d1619ad8176d665453cfb2e55f0 + (0,54) | 54 | a684eceee76fc522773286a895bc8436 + (0,55) | 55 | b53b3a3d6ab90ce0268229151c9bde11 + (0,56) | 56 | 9f61408e3afb633e50cdf1b20de6f466 + (0,57) | 57 | 72b32a1f754ba1c09b3695e0cb6cde7f + (0,58) | 58 | 66f041e16a60928b05a7e228a89c3799 + (0,59) | 59 | 093f65e080a295f8076b1c5722a46aa2 + (0,60) | 60 | 072b030ba126b2f4b2374f342be9ed44 + (0,61) | 61 | 7f39f8317fbdb1988ef4c628eba02591 + (0,62) | 62 | 44f683a84163b3523afe57c2e008bc8c + (0,63) | 63 | 03afdbd66e7929b125f8597834fa83a4 + (0,64) | 64 | ea5d2f1c4608232e07d3aa3d998e5135 + (0,65) | 65 | fc490ca45c00b1249bbe3554a4fdf6fb + (0,66) | 66 | 3295c76acbf4caaed33c36b1b5fc2cb1 + (0,67) | 67 | 735b90b4568125ed6c3f678819b6e058 + (0,68) | 68 | a3f390d88e4c41f2747bfa2f1b5f87db + (0,69) | 69 | 14bfa6bb14875e45bba028a21ed38046 + (0,70) | 70 | 7cbbc409ec990f19c78c75bd1e06f215 + (0,71) | 71 | e2c420d928d4bf8ce0ff2ec19b371514 + (0,72) | 72 | 32bb90e8976aab5298d5da10fe66f21d + (0,73) | 73 | d2ddea18f00665ce8623e36bd4e3c7c5 + (0,74) | 74 | ad61ab143223efbc24c7d2583be69251 + (0,75) | 75 | d09bf41544a3365a46c9077ebb5e35c3 + (0,76) | 76 | fbd7939d674997cdb4692d34de8633c4 + (0,77) | 77 | 28dd2c7955ce926456240b2ff0100bde + (0,78) | 78 | 35f4a8d465e6e1edc05f3d8ab658c551 + (0,79) | 79 | d1fe173d08e959397adf34b1d77e88d7 + (0,80) | 80 | f033ab37c30201f73f142449d037028d + (0,81) | 81 | 43ec517d68b6edd3015b3edc9a11367b + (0,82) | 82 | 9778d5d219c5080b9a6a17bef029331c + (0,83) | 83 | fe9fc289c3ff0af142b6d3bead98a923 + (0,84) | 84 | 68d30a9594728bc39aa24be94b319d21 + (0,85) | 85 | 3ef815416f775098fe977004015c6193 + (0,86) | 86 | 93db85ed909c13838ff95ccfa94cebd9 + (0,87) | 87 | c7e1249ffc03eb9ded908c236bd1996d + (0,88) | 88 | 2a38a4a9316c49e5a833517c45d31070 + (0,89) | 89 | 7647966b7343c29048673252e490f736 + (0,90) | 90 | 8613985ec49eb8f757ae6439e879bb2a + (0,91) | 91 | 54229abfcfa5649e7003b83dd4755294 + (0,92) | 92 | 92cc227532d17e56e07902b254dfad10 + (0,93) | 93 | 98dce83da57b0395e163467c9dae521b + (0,94) | 94 | f4b9ec30ad9f68f89b29639786cb62ef + (0,95) | 95 | 812b4ba287f5ee0bc9d43bbf5bbe87fb + (0,96) | 96 | 26657d5ff9020d2abefe558796b99584 + (0,97) | 97 | e2ef524fbf3d9fe611d5a8e90fefdc9c + (0,98) | 98 | ed3d2c21991e3bef5e069713af9fa6ca + (0,99) | 99 | ac627ab1ccbdb62ec96e702f07f6425b + (0,100) | 100 | f899139df5e1059396431415e770c6dd + (0,101) | 101 | 38b3eff8baf56627478ec76a704e9b52 + (0,102) | 102 | ec8956637a99787bd197eacd77acce5e + (0,103) | 103 | 6974ce5ac660610b44d9b9fed0ff9548 + (0,104) | 104 | c9e1074f5b3f9fc8ea15d152add07294 + (0,105) | 105 | 65b9eea6e1cc6bb9f0cd2a47751a186f + (0,106) | 106 | f0935e4cd5920aa6c7c996a5ee53a70f + (0,107) | 107 | a97da629b098b75c294dffdc3e463904 + (0,108) | 108 | a3c65c2974270fd093ee8a9bf8ae7d0b + (0,109) | 109 | 2723d092b63885e0d7c260cc007e8b9d + (0,110) | 110 | 5f93f983524def3dca464469d2cf9f3e + (0,111) | 111 | 698d51a19d8a121ce581499d7b701668 + (0,112) | 112 | 7f6ffaa6bb0b408017b62254211691b5 + (0,113) | 113 | 73278a4a86960eeb576a8fd4c9ec6997 + (0,114) | 114 | 5fd0b37cd7dbbb00f97ba6ce92bf5add + (0,115) | 115 | 2b44928ae11fb9384c4cf38708677c48 + (0,116) | 116 | c45147dee729311ef5b5c3003946c48f + (0,117) | 117 | eb160de1de89d9058fcb0b968dbbbd68 + (0,118) | 118 | 5ef059938ba799aaa845e1c2e8a762bd + (0,119) | 119 | 07e1cd7dca89a1678042477183b7ac3f + (0,120) | 120 | da4fb5c6e93e74d3df8527599fa62642 + (1,1) | 121 | 4c56ff4ce4aaf9573aa5dff913df997a + (1,2) | 122 | a0a080f42e6f13b3a2df133f073095dd + (1,3) | 123 | 202cb962ac59075b964b07152d234b70 + (1,4) | 124 | c8ffe9a587b126f152ed3d89a146b445 + (1,5) | 125 | 3def184ad8f4755ff269862ea77393dd + (1,6) | 126 | 069059b7ef840f0c74a814ec9237b6ec + (1,7) | 127 | ec5decca5ed3d6b8079e2e7e7bacc9f2 + (1,8) | 128 | 76dc611d6ebaafc66cc0879c71b5db5c + (1,9) | 129 | d1f491a404d6854880943e5c3cd9ca25 + (1,10) | 130 | 9b8619251a19057cff70779273e95aa6 + (1,11) | 131 | 1afa34a7f984eeabdbb0a7d494132ee5 + (1,12) | 132 | 65ded5353c5ee48d0b7d48c591b8f430 + (1,13) | 133 | 9fc3d7152ba9336a670e36d0ed79bc43 + (1,14) | 134 | 02522a2b2726fb0a03bb19f2d8d9524d + (1,15) | 135 | 7f1de29e6da19d22b51c68001e7e0e54 + (1,16) | 136 | 42a0e188f5033bc65bf8d78622277c4e + (1,17) | 137 | 3988c7f88ebcb58c6ce932b957b6f332 + (1,18) | 138 | 013d407166ec4fa56eb1e1f8cbe183b9 + (1,19) | 139 | e00da03b685a0dd18fb6a08af0923de0 +(139 rows) + +SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid; + ctid | a | b +------+---+--- +(0 rows) + +SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + ctid | a | b +---------+-----+---------------------------------- + (2,115) | 355 | 82cec96096d4281b7c95cd7e74623496 + (2,116) | 356 | 6c524f9d5d7027454a783c841250ba71 + (2,117) | 357 | fb7b9ffa5462084c5f4e7e85a093e6d7 + (2,118) | 358 | aa942ab2bfa6ebda4840e7360ce6e7ef + (2,119) | 359 | c058f544c737782deacefa532d9add4c + (2,120) | 360 | e7b24b112a44fdd9ee93bdf998c6ca0e + (3,1) | 361 | 52720e003547c70561bf5e03b95aa99f + (3,2) | 362 | c3e878e27f52e2a57ace4d9a76fd9acf + (3,3) | 363 | 00411460f7c92d2124a67ea0f4cb5f85 + (3,4) | 364 | bac9162b47c56fc8a4d2a519803d51b3 + (3,5) | 365 | 9be40cee5b0eee1462c82c6964087ff9 + (3,6) | 366 | 5ef698cd9fe650923ea331c15af3b160 + (3,7) | 367 | 05049e90fa4f5039a8cadc6acbb4b2cc + (3,8) | 368 | cf004fdc76fa1a4f25f62e0eb5261ca3 + (3,9) | 369 | 0c74b7f78409a4022a2c4c5a5ca3ee19 + (3,10) | 370 | d709f38ef758b5066ef31b18039b8ce5 +(16 rows) + +SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid; + ctid | a | b | x | y +--------+-----+----------------------------------+-----+------------------------------------------------------------------ + (1,76) | 196 | 084b6fbb10729ed4da8c3d3f5a3ae7c9 | 157 | 6c4b761a28b734fe93831e3fb400ce876c4b761a28b734fe93831e3fb400ce87 + (1,77) | 197 | 85d8ce590ad8981ca2c8286f79f59954 | 158 | 06409663226af2f3114485aa4e0a23b406409663226af2f3114485aa4e0a23b4 + (1,78) | 198 | 0e65972dce68dad4d52d063967f0a705 | 159 | 140f6969d5213fd0ece03148e62e461e140f6969d5213fd0ece03148e62e461e + (1,79) | 199 | 84d9ee44e457ddef7f2c4f25dc8fa865 | 160 | b73ce398c39f506af761d2277d853a92b73ce398c39f506af761d2277d853a92 + (1,80) | 200 | 3644a684f98ea8fe223c713b77189a77 | 161 | bd4c9ab730f5513206b999ec0d90d1fbbd4c9ab730f5513206b999ec0d90d1fb + (1,81) | 201 | 757b505cfd34c64c85ca5b5690ee5293 | 162 | 82aa4b0af34c2313a562076992e50aa382aa4b0af34c2313a562076992e50aa3 + (2,1) | 241 | f340f1b1f65b6df5b5e3f94d95b11daf | 163 | 0777d5c17d4066b82ab86dff8a46af6f0777d5c17d4066b82ab86dff8a46af6f + (2,2) | 242 | e4a6222cdb5b34375400904f03d8e6a5 | 164 | fa7cdfad1a5aaf8370ebeda47a1ff1c3fa7cdfad1a5aaf8370ebeda47a1ff1c3 + (2,3) | 243 | cb70ab375662576bd1ac5aaf16b3fca4 | 165 | 9766527f2b5d3e95d4a733fcfb77bd7e9766527f2b5d3e95d4a733fcfb77bd7e + (2,4) | 244 | 9188905e74c28e489b44e954ec0b9bca | 166 | 7e7757b1e12abcb736ab9a754ffb617a7e7757b1e12abcb736ab9a754ffb617a + (2,5) | 245 | 0266e33d3f546cb5436a10798e657d97 | 167 | 5878a7ab84fb43402106c575658472fa5878a7ab84fb43402106c575658472fa + (2,6) | 246 | 38db3aed920cf82ab059bfccbd02be6a | 168 | 006f52e9102a8d3be2fe5614f42ba989006f52e9102a8d3be2fe5614f42ba989 + (2,7) | 247 | 3cec07e9ba5f5bb252d13f5f431e4bbb | 169 | 3636638817772e42b59d74cff571fbb33636638817772e42b59d74cff571fbb3 + (2,8) | 248 | 621bf66ddb7c962aa0d22ac97d69b793 | 170 | 149e9677a5989fd342ae44213df68868149e9677a5989fd342ae44213df68868 + (2,9) | 249 | 077e29b11be80ab57e1a2ecabb7da330 | 171 | a4a042cf4fd6bfb47701cbc8a1653adaa4a042cf4fd6bfb47701cbc8a1653ada +(15 rows) + +PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1 + WHERE b like '%abc%' AND ctid BETWEEN $1 AND $2; +EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid); + QUERY PLAN +----------------------------------------------------------------------------------------- + Custom Scan (ctidscan) on t1 + Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid)) + ctid quals: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid)) +(3 rows) + +EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid); + QUERY PLAN +----------------------------------------------------------------------------------------- + Custom Scan (ctidscan) on t1 + Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid)) + ctid quals: ((ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid)) +(3 rows) + +-- Also, EXPLAIN with none-text format +EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid); + QUERY PLAN +----------------------------------------------------------------------------------------------------------- + + + + + + + Custom Scan + + ctidscan + + t1 + + ((b ~~ '%abc%'::text) AND (ctid >= '(0,0)'::tid) AND (ctid <= '(5,0)'::tid))+ + ((ctid >= '(0,0)'::tid) AND (ctid <= '(5,0)'::tid)) + + + + + + +(1 row) + +-- Test cleanup +DROP SCHEMA regtest_custom_scan CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table t1 +drop cascades to table t2 diff --git a/contrib/ctidscan/sql/ctidscan.sql b/contrib/ctidscan/sql/ctidscan.sql new file mode 100644 index 0000000..26c22c2 --- /dev/null +++ b/contrib/ctidscan/sql/ctidscan.sql @@ -0,0 +1,59 @@ +-- +-- Regression Tests for Custom Plan APIs +-- + +-- construction of test data +SET client_min_messages TO 'warning'; + +CREATE SCHEMA regtest_custom_scan; + +SET search_path TO regtest_custom_scan, public; + +CREATE TABLE t1 ( + a int primary key, + b text +); +INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s); +VACUUM ANALYZE t1; + +CREATE TABLE t2 ( + x int primary key, + y text +); +INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s); +VACUUM ANALYZE t2; + +RESET client_min_messages; +-- +-- Check Plans if no special extension is loaded. +-- +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40; +EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%'; +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid; +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + +-- +-- Plan for same query but ctidscan was loaded +-- +LOAD '$libdir/ctidscan'; +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40; +EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%'; +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid; +EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; +EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid; + +SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid; +SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid; +SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; +SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid; + +PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1 + WHERE b like '%abc%' AND ctid BETWEEN $1 AND $2; +EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid); +EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid); + +-- Also, EXPLAIN with none-text format +EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid); + +-- Test cleanup +DROP SCHEMA regtest_custom_scan CASCADE; diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index d7dcd1c..0dd131c 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -166,10 +166,13 @@ DESCR("less than"); #define TIDLessOperator 2799 DATA(insert OID = 2800 ( ">" PGNSP PGUID b f f 27 27 16 2799 2801 tidgt scalargtsel scalargtjoinsel )); DESCR("greater than"); +#define TIDGreaterOperator 2800 DATA(insert OID = 2801 ( "<=" PGNSP PGUID b f f 27 27 16 2802 2800 tidle scalarltsel scalarltjoinsel )); DESCR("less than or equal"); +#define TIDLessEqualOperator 2801 DATA(insert OID = 2802 ( ">=" PGNSP PGUID b f f 27 27 16 2801 2799 tidge scalargtsel scalargtjoinsel )); DESCR("greater than or equal"); +#define TIDGreaterEqualOperator 2802 DATA(insert OID = 410 ( "=" PGNSP PGUID b t t 20 20 16 410 411 int8eq eqsel eqjoinsel )); DESCR("equal");