doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/ctidscan.sgml | 52 ++ doc/src/sgml/filelist.sgml | 1 + src/include/catalog/pg_operator.h | 4 + src/test/modules/Makefile | 1 + src/test/modules/ctidscan/Makefile | 16 + src/test/modules/ctidscan/ctidscan.c | 828 ++++++++++++++++++++++++ src/test/modules/ctidscan/expected/ctidscan.out | 588 +++++++++++++++++ src/test/modules/ctidscan/sql/ctidscan.sql | 132 ++++ src/test/regress/expected/custom_scan.out | 0 src/test/regress/sql/custom_scan.sql | 53 ++ 11 files changed, 1676 insertions(+) diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index a698d0f..59eab97 100644 --- a/doc/src/sgml/contrib.sgml +++ b/doc/src/sgml/contrib.sgml @@ -109,6 +109,7 @@ CREATE EXTENSION module_name FROM unpackaged; &btree-gist; &chkpass; &citext; + &ctidscan; &cube; &dblink; &dict-int; diff --git a/doc/src/sgml/ctidscan.sgml b/doc/src/sgml/ctidscan.sgml new file mode 100644 index 0000000..fa21b0f --- /dev/null +++ b/doc/src/sgml/ctidscan.sgml @@ -0,0 +1,52 @@ + + + + ctidscan + + + ctidscan + + + + This module implements a custom-scan provider that utilizes inequality + operator that involves the ctid system column. + + + + This module provides no SQL accessible interface. For installation, + all you need to do is just load the module to the server. + + You can load it an individual session using: + +LOAD 'ctidscan'; + + + or, you can also take more typical usage with extension preloading + using or + in + postgresql.conf. + + Then, planner may consider more cheap execution path if supplied query + involves above operators. + + + + + enable_ctidscan (bool) + + enable_ctidscan configuration parameter + + + + + enable_ctidscan turns on/off functionality of + ctidscan custom-scan provider. + If turned off, it does not offer alternative scan path even if + supplied query is sufficient to run by ctidscan plan. + Its default is true. + Anybody can change using SET command. + + + + + diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index f03b72a..15d569e 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -107,6 +107,7 @@ + diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index 88c737b..2ab15a5 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -161,15 +161,19 @@ DESCR("equal"); #define TIDEqualOperator 387 DATA(insert OID = 402 ( "<>" PGNSP PGUID b f f 27 27 16 402 387 tidne neqsel neqjoinsel )); DESCR("not equal"); +#define TIDNotEqualOperator 402 DATA(insert OID = 2799 ( "<" PGNSP PGUID b f f 27 27 16 2800 2802 tidlt scalarltsel scalarltjoinsel )); 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"); diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 93d93af..c288276 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -6,6 +6,7 @@ include $(top_builddir)/src/Makefile.global SUBDIRS = \ commit_ts \ + ctidscan \ worker_spi \ dummy_seclabel \ test_shm_mq \ diff --git a/src/test/modules/ctidscan/Makefile b/src/test/modules/ctidscan/Makefile new file mode 100644 index 0000000..bbe280a --- /dev/null +++ b/src/test/modules/ctidscan/Makefile @@ -0,0 +1,16 @@ +# contrib/ctidscan/Makefile + +MODULES = ctidscan + +REGRESS = ctidscan + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/ctidscan +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/ctidscan/ctidscan.c b/src/test/modules/ctidscan/ctidscan.c new file mode 100644 index 0000000..54f0c85 --- /dev/null +++ b/src/test/modules/ctidscan/ctidscan.c @@ -0,0 +1,828 @@ +/* + * ctidscan.c + * + * A custom-scan provide that utilizes ctid system column within + * inequality-operators, to skip block reads never referenced. + * + * 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/guc.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/spccache.h" + +PG_MODULE_MAGIC; + +/* + * NOTE: We don't use any special data type to save the private data. + * All we want to save in private fields is expression-list that shall + * be adjusted by setrefs.c/subselect.c, so we put it on the custom_exprs + * of CustomScan structure, not custom_private field. + * Due to the interface contract, only expression nodes are allowed to put + * on the custom_exprs, and we have to pay attention the core backend may + * adjust expression items. + */ + +/* + * CtidScanState - state object of ctidscan on executor. + * It has few additional internal state. The 'ctid_quals' has list of + * ExprState for inequality operators that involve ctid system column. + */ +typedef struct { + CustomScanState css; + List *ctid_quals; /* list of ExprState for inequality ops */ +} CtidScanState; + +/* static variables */ +static bool enable_ctidscan; +static set_rel_pathlist_hook_type set_rel_pathlist_next = NULL; + +/* function declarations */ +void _PG_init(void); + +static void SetCtidScanPath(PlannerInfo *root, + RelOptInfo *rel, + Index rti, + RangeTblEntry *rte); +/* CustomPathMethods */ +static Plan *PlanCtidScanPath(PlannerInfo *root, + RelOptInfo *rel, + CustomPath *best_path, + List *tlist, + List *clauses); + +/* CustomScanMethods */ +static Node *CreateCtidScanState(CustomScan *custom_plan); + +/* CustomScanExecMethods */ +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 */ + PlanCtidScanPath, /* PlanCustomPath */ + NULL, /* TextOutCustomPath */ +}; + +static CustomScanMethods ctidscan_scan_methods = { + "ctidscan", /* CustomName */ + CreateCtidScanState, /* CreateCustomScanState */ + NULL, /* TextOutCustomScan */ +}; + +static CustomExecMethods ctidscan_exec_methods = { + "ctidscan", /* CustomName */ + BeginCtidScan, /* BeginCustomScan */ + ExecCtidScan, /* ExecCustomScan */ + EndCtidScan, /* EndCustomScan */ + ReScanCtidScan, /* ReScanCustomScan */ + NULL, /* MarkPosCustomScan */ + NULL, /* RestrPosCustomScan */ + ExplainCtidScan, /* ExplainCustomScan */ +}; + +#define IsCTIDVar(node,rtindex) \ + ((node) != NULL && \ + IsA((node), Var) && \ + ((Var *) (node))->varno == (rtindex) && \ + ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \ + ((Var *) (node))->varlevelsup == 0) + +/* + * IsCTidInequalExpr + * + * It checks whether the given restriction clauses enables to determine + * the zone to be scanned, or not. + */ +static bool +IsCTidInequalExpr(RelOptInfo *rel, RestrictInfo *rinfo) +{ + if (is_opclause(rinfo->clause)) + { + OpExpr *op = (OpExpr *) rinfo->clause; + 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 false; + + if (list_length(op->args) != 2) + return false; /* should not happen */ + + arg1 = linitial(op->args); + arg2 = lsecond(op->args); + + if (IsCTIDVar(arg1, rel->relid)) + other = arg2; + else if (IsCTIDVar(arg2, rel->relid)) + other = arg1; + else + return false; + + if (exprType(other) != TIDOID) + return false; /* should not happen */ + + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return false; + + return true; + } + return false; +} + +/* + * 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, + CustomPath *cpath) +{ + Path *path = &cpath->path; + List *ctid_quals = cpath->custom_private; + 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_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 (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) + { + RestrictInfo *rinfo = lfirst(lc); + OpExpr *op = (OpExpr *) rinfo->clause; + 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); + if (!OidIsValid(opno)) + elog(ERROR, "could not find commutator for operator %u", + 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 from scan-size estimation + * perspective. + */ + 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", opno); + break; + } + } + else if (IsA(other, Param)) + { + ItemPointerData temp; + + /* + * Just a "very" rough estimation. We cannot know exact value + * of parameter until execution stage, so assume roughly + * 33% blocks shall be read by an operator. + */ + switch (opno) + { + case TIDLessOperator: + case TIDLessEqualOperator: + ItemPointerSetBlockNumber(&temp, baserel->pages * 2 / 3); + ItemPointerSetOffsetNumber(&temp, 0); + if (ItemPointerCompare(&temp, &ip_max) < 0) + ItemPointerCopy(&temp, &ip_max); + has_max_val = true; + break; + case TIDGreaterOperator: + case TIDGreaterEqualOperator: + ItemPointerSetBlockNumber(&temp, baserel->pages / 3); + ItemPointerSetOffsetNumber(&temp, 0); + if (ItemPointerCompare(&temp, &ip_max) > 0) + ItemPointerCopy(&temp, &ip_max); + has_min_val = true; + break; + default: + elog(ERROR, "unexpected operator code: %u", op->opno); + break; + } + } + } + + /* estimate min/max block numbers */ + 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 = Max(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 = Max(bnum_max - bnum_min, 0) + 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 = Max(bnum_max - bnum_min, 0) + 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 = baserel->tuples * (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, + NULL, + &spc_seq_page_cost); + + /* Disk costs */ + run_cost += spc_seq_page_cost * num_pages; + + /* CPU costs; logic originates 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 eliminate the cost for the ctid inequality operators, + * because it is still subset of qpquals and needs to be evaluated + * on run time to check offset-number (even we can skip blocks that + * are never referenced obviously). + */ + startup_cost += qpqual_cost.startup; + cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple; + run_cost += cpu_per_tuple * ntuples; + + /* Just a spice to prefer SeqScan, if inequality operators tooks + * obvious out-of-range thus its cost is identical with SeqScan. + */ + startup_cost += 1.0; + + path->startup_cost = startup_cost; + path->total_cost = startup_cost + run_cost; +} + +/* + * SetCtidScanPath - entrypoint of the series of custom-scan execution. + * It adds CustomPath if referenced relation has inequality expressions on + * the ctid system column. + */ +static void +SetCtidScanPath(PlannerInfo *root, RelOptInfo *baserel, + Index rtindex, 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; + + /* + * NOTE: Unlike built-in execution path, always we can have core path + * even though ctid scan is not available. So, simply, we don't add + * any paths, instead of adding disable_cost. + */ + if (!enable_ctidscan) + return; + + /* walk on the restrict info */ + foreach (lc, baserel->baserestrictinfo) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + Assert(IsA(rinfo, RestrictInfo)); + if (IsCTidInequalExpr(baserel, rinfo)) + ctid_quals = lappend(ctid_quals, rinfo); + } + + /* + * 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) + { + CustomPath *cpath; + 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; + + cpath = palloc0(sizeof(CustomPath)); + cpath->path.type = T_CustomPath; + cpath->path.pathtype = T_CustomScan; + cpath->path.parent = baserel; + cpath->path.param_info + = get_baserel_parampathinfo(root, baserel, required_outer); + cpath->flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN; + cpath->custom_private = ctid_quals; + cpath->methods = &ctidscan_path_methods; + + CTidEstimateCosts(root, baserel, cpath); + + add_path(baserel, &cpath->path); + } +} + +/* + * PlanCtidScanPlan - 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) +{ + CustomScan *cscan = makeNode(CustomScan); + List *scan_quals = extract_actual_clauses(clauses, false); + List *ctid_quals + = extract_actual_clauses(best_path->custom_private, false); + + cscan->flags = best_path->flags; + cscan->methods = &ctidscan_scan_methods; + + /* set scanrelid */ + cscan->scan.scanrelid = rel->relid; + /* set targetlist as is */ + cscan->scan.plan.targetlist = tlist; + /* reduce RestrictInfo list to bare expressions */ + cscan->scan.plan.qual = list_difference(scan_quals, ctid_quals); + /* set ctid related quals */ + cscan->custom_exprs = ctid_quals; + + return &cscan->scan.plan; +} + +/* + * 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; +} + +/* + * 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; + CustomScan *cscan = (CustomScan *) node->ss.ps.plan; + + /* + * In case of custom-scan 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 *)cscan->custom_exprs, &node->ss.ps); +} + +/* + * 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; + Datum value; + 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); + if (!OidIsValid(opno)) + elog(ERROR, "could not find commutator for operator %u", + op->opno); + } + else + elog(ERROR, "could not identify CTID variable"); + + /* evaluate the CTID value */ + value = 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; + } + + /* + * NOTE: ScanKeyInit saves only pointers, so we have to keep the + * ItemPointerData variable on somewhere available during the + * query execution. + */ + itemptr = MemoryContextAlloc(estate->es_query_cxt, + sizeof(ItemPointerData)); + ItemPointerCopy((ItemPointer) DatumGetPointer(value), itemptr); + + 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) - 1, + scan->rs_nblocks - 1); + heap_setscanlimits(scan, blknum, scan->rs_nblocks - blknum); + } + else if (direction == BackwardScanDirection && has_ubound) + { + BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid), + scan->rs_nblocks - 1); + heap_setscanlimits(scan, 0, 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; + CustomScan *cscan = (CustomScan *) ctss->css.ss.ps.plan; + + /* logic copied from show_qual and show_expression */ + if (cscan->custom_exprs) + { + bool useprefix = es->verbose; + Node *qual; + List *context; + char *exprstr; + + /* Convert AND list to explicit AND */ + qual = (Node *) make_ands_explicit(cscan->custom_exprs); + + /* 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("TID Range", exprstr, es); + } +} + +/* + * Entrypoint of this extension + */ +void +_PG_init(void) +{ + DefineCustomBoolVariable("enable_ctidscan", + "Enables the planner's use of ctid-scan plans.", + NULL, + &enable_ctidscan, + true, + PGC_USERSET, + GUC_NOT_IN_SAMPLE, + NULL, NULL, NULL); + + /* registration of the hook to add alternative path */ + set_rel_pathlist_next = set_rel_pathlist_hook; + set_rel_pathlist_hook = SetCtidScanPath; +} diff --git a/src/test/modules/ctidscan/expected/ctidscan.out b/src/test/modules/ctidscan/expected/ctidscan.out new file mode 100644 index 0000000..b16f088 --- /dev/null +++ b/src/test/modules/ctidscan/expected/ctidscan.out @@ -0,0 +1,588 @@ +-- +-- Regression Tests for CustomScan Interface with CtidScan Provider +-- +-- construction of test data +SET client_min_messages TO 'warning'; +SET SEED TO 0.20140702; +CREATE SCHEMA regtest_custom_scan; +SET search_path TO regtest_custom_scan, public; +CREATE TABLE t1 ( + a int primary key, + b float, + c text +); +INSERT INTO t1 (SELECT i, ceil(random()*10000.0) / 1000.0, md5(i::text) FROM generate_series(1,40000) i); +VACUUM ANALYZE t1; +CREATE TABLE t2 ( + x int primary key, + y float, + z text +); +INSERT INTO t2 (SELECT i, ceil(random()*10000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,40200) i); +VACUUM ANALYZE t2; +CREATE TABLE t3 ( + a int references t1(a), + x int references t2(x) +); +INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i); +RESET client_min_messages; +set log_error_verbosity = verbose; +-- +-- Check Plans if no special extensions are loaded. +-- +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid; + QUERY PLAN +--------------------------------- + Index Scan using t1_pkey on t1 + Index Cond: (a = 40) + Filter: (ctid < '(6,0)'::tid) +(3 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid; + QUERY PLAN +------------------------------------------------------------ + Seq Scan on t1 + Filter: ((c ~~ '%789%'::text) AND (ctid < '(5,0)'::tid)) +(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) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE '(8,0)'::tid < ctid AND c like '%ab%'; + QUERY PLAN +----------------------------------------------------------- + Seq Scan on t1 + Filter: (('(8,0)'::tid < ctid) AND (c ~~ '%ab%'::text)) +(2 rows) + +EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%'; + QUERY PLAN +---------------------------------------------------------------- + Seq Scan on t1 (cost=0.00..1013.00 rows=135 width=45) + Filter: (('(9999,99)'::tid > ctid) AND (c ~~ '%def%'::text)) +(2 rows) + +EXPLAIN (costs off) + SELECT * FROM t1, t2, t3 + WHERE t1.a = t3.a AND t2.x = t3.x + AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid + AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid + AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid; + QUERY PLAN +-------------------------------------------------------------------------------- + Nested Loop + -> Nested Loop + -> Seq Scan on t3 + Filter: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid)) + -> Index Scan using t1_pkey on t1 + Index Cond: (a = t3.a) + Filter: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid)) + -> Index Scan using t2_pkey on t2 + Index Cond: (x = t3.x) + Filter: ((ctid >= '(4,9999)'::tid) AND (ctid <= '(8,0)'::tid)) +(10 rows) + +EXPLAIN (costs off, verbose) + SELECT count(*), ceil(b) + FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid + GROUP BY ceil(b); + QUERY PLAN +---------------------------------------------------------------------------- + HashAggregate + Output: count(*), (ceil(b)) + Group Key: ceil(t1.b) + -> Seq Scan on regtest_custom_scan.t1 + Output: ceil(b) + Filter: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid)) +(6 rows) + +-- +-- Plan for same query but ctidscan was loaded +-- +LOAD '$libdir/ctidscan'; +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid; + QUERY PLAN +--------------------------------- + Index Scan using t1_pkey on t1 + Index Cond: (a = 40) + Filter: (ctid < '(6,0)'::tid) +(3 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid; + QUERY PLAN +------------------------------------ + Custom Scan (ctidscan) on t1 + Filter: (c ~~ '%789%'::text) + TID Range: (ctid < '(5,0)'::tid) +(3 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 + TID Range: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid)) +(2 rows) + +EXPLAIN (costs off) SELECT * FROM t1 WHERE '(8,0)'::tid < ctid AND c like '%ab%'; + QUERY PLAN +------------------------------------ + Custom Scan (ctidscan) on t1 + Filter: (c ~~ '%ab%'::text) + TID Range: ('(8,0)'::tid < ctid) +(3 rows) + +EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%'; -- SeqScan is cheaper + QUERY PLAN +---------------------------------------------------------------- + Seq Scan on t1 (cost=0.00..1013.00 rows=135 width=45) + Filter: (('(9999,99)'::tid > ctid) AND (c ~~ '%def%'::text)) +(2 rows) + +EXPLAIN (costs off) + SELECT * FROM t1, t2, t3 + WHERE t1.a = t3.a AND t2.x = t3.x + AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid + AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid + AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid; + QUERY PLAN +------------------------------------------------------------------------------------ + Nested Loop + -> Hash Join + Hash Cond: (t2.x = t3.x) + -> Custom Scan (ctidscan) on t2 + TID Range: ((ctid >= '(4,9999)'::tid) AND (ctid <= '(8,0)'::tid)) + -> Hash + -> Custom Scan (ctidscan) on t3 + TID Range: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid)) + -> Index Scan using t1_pkey on t1 + Index Cond: (a = t3.a) + Filter: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid)) +(11 rows) + +EXPLAIN (costs off, verbose) + SELECT count(*), ceil(b) + FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid + GROUP BY ceil(b); + QUERY PLAN +------------------------------------------------------------------------------- + HashAggregate + Output: count(*), (ceil(b)) + Group Key: ceil(t1.b) + -> Custom Scan (ctidscan) on regtest_custom_scan.t1 + Output: ceil(b) + TID Range: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid)) +(6 rows) + +-- +-- Run the query without EXPLAIN +-- +SELECT ctid,* FROM t1 WHERE ctid <= '(1,20)'::tid ORDER BY ctid DESC LIMIT 25; + ctid | a | b | c +--------+-----+-------+---------------------------------- + (1,20) | 117 | 2.893 | eb160de1de89d9058fcb0b968dbbbd68 + (1,19) | 116 | 4.68 | c45147dee729311ef5b5c3003946c48f + (1,18) | 115 | 3.107 | 2b44928ae11fb9384c4cf38708677c48 + (1,17) | 114 | 0.675 | 5fd0b37cd7dbbb00f97ba6ce92bf5add + (1,16) | 113 | 8.544 | 73278a4a86960eeb576a8fd4c9ec6997 + (1,15) | 112 | 4.443 | 7f6ffaa6bb0b408017b62254211691b5 + (1,14) | 111 | 2.845 | 698d51a19d8a121ce581499d7b701668 + (1,13) | 110 | 1.052 | 5f93f983524def3dca464469d2cf9f3e + (1,12) | 109 | 1.784 | 2723d092b63885e0d7c260cc007e8b9d + (1,11) | 108 | 2.646 | a3c65c2974270fd093ee8a9bf8ae7d0b + (1,10) | 107 | 3.065 | a97da629b098b75c294dffdc3e463904 + (1,9) | 106 | 3.412 | f0935e4cd5920aa6c7c996a5ee53a70f + (1,8) | 105 | 8.583 | 65b9eea6e1cc6bb9f0cd2a47751a186f + (1,7) | 104 | 6.715 | c9e1074f5b3f9fc8ea15d152add07294 + (1,6) | 103 | 4.109 | 6974ce5ac660610b44d9b9fed0ff9548 + (1,5) | 102 | 9.121 | ec8956637a99787bd197eacd77acce5e + (1,4) | 101 | 4.645 | 38b3eff8baf56627478ec76a704e9b52 + (1,3) | 100 | 1.237 | f899139df5e1059396431415e770c6dd + (1,2) | 99 | 5.83 | ac627ab1ccbdb62ec96e702f07f6425b + (1,1) | 98 | 4.243 | ed3d2c21991e3bef5e069713af9fa6ca + (0,97) | 97 | 3.799 | e2ef524fbf3d9fe611d5a8e90fefdc9c + (0,96) | 96 | 2.132 | 26657d5ff9020d2abefe558796b99584 + (0,95) | 95 | 6.077 | 812b4ba287f5ee0bc9d43bbf5bbe87fb + (0,94) | 94 | 0.365 | f4b9ec30ad9f68f89b29639786cb62ef + (0,93) | 93 | 2.718 | 98dce83da57b0395e163467c9dae521b +(25 rows) + +SELECT count(*) FROM t1 WHERE ctid > '(200,0)'::tid; + count +------- + 20600 +(1 row) + +SELECT ctid,* FROM t1 WHERE c like '%678%' AND ctid >= '(3,50)'::tid LIMIT 25; + ctid | a | b | c +---------+------+-------+---------------------------------- + (6,78) | 660 | 2.946 | 68264bdb65b97eeae6788aa3348e553c + (7,43) | 722 | 1.612 | c8ed21db4f678f3b13b9d5ee16489088 + (8,36) | 812 | 3.673 | 81e74d678581a3bb7a720b019f4f1a93 + (9,97) | 970 | 7.977 | 89fcd07f20b6785b92134bd6c1d0fa42 + (11,60) | 1127 | 8.636 | 678a1491514b7f1006d605e9161946b1 + (14,10) | 1368 | 9.3 | 6c1da886822c67822bcf3679d04369fa + (14,41) | 1399 | 5.773 | 602d1305678a8d5fdb372271e980da6a + (14,94) | 1452 | 2.564 | 6786f3c62fbf9021694f6e51cc07fe3c + (16,74) | 1626 | 8.511 | bc573864331a9e42e4511de6f678aa83 + (17,25) | 1674 | 4.681 | 757f843a169cc678064d9530d12a1881 + (17,56) | 1705 | 8.138 | 2a27b8144ac02f67687f76782a3b5d8f + (18,90) | 1836 | 4.489 | f26dab9bf6a137c3b6782e562794c2f2 + (19,41) | 1884 | 0.274 | f0fcf351df4eb6786e9bb6fc4e2dee02 + (22,48) | 2182 | 2.666 | d51b416788b6ee70eb0c381c06efc9f1 + (23,63) | 2294 | 3.001 | 229754d7799160502a143a72f6789927 + (23,83) | 2314 | 8.831 | db9eeb7e678863649bce209842e0d164 + (30,18) | 2928 | 0.852 | 9087b0efc7c7acd1ef7e153678809c77 + (30,19) | 2929 | 5.443 | a36b0dcd1e6384abc0e1867860ad3ee3 + (30,64) | 2974 | 9.84 | 6788076842014c83cedadbe6b0ba0314 + (30,85) | 2995 | 9.828 | cb12d7f933e7d102c52231bf62b8a678 + (32,21) | 3125 | 5.251 | 019f8b946a256d9357eadc5ace2c8678 + (32,32) | 3136 | 5.17 | 2ecd2bd94734e5dd392d8678bc64cdab + (32,58) | 3162 | 8.215 | d60678e8f2ba9c540798ebbde31177e8 + (32,64) | 3168 | 2.302 | ce393994e8cf430867408678d1a5a9c8 + (33,14) | 3215 | 5.748 | e2eacaff46787bfeefcaa24cf35264c7 +(25 rows) + +SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + ctid | a | b | c +--------+-----+-------+---------------------------------- + (3,1) | 292 | 3.11 | 1700002963a49da13542e0726b7bb758 + (3,2) | 293 | 4.932 | 53c3bce66e43be4f209556518c2fcb54 + (3,3) | 294 | 3.851 | 6883966fd8f918a4aa29be29d2c386fb + (3,4) | 295 | 9.821 | 49182f81e6a13cf5eaa496d51fea6406 + (3,5) | 296 | 1.623 | d296c101daa88a51f6ca8cfc1ac79b50 + (3,6) | 297 | 9.248 | 9fd81843ad7f202f26c1a174c7357585 + (3,7) | 298 | 8.073 | 26e359e83860db1d11b6acca57d8ea88 + (3,8) | 299 | 5.224 | ef0d3930a7b6c95bd2b32ed45989c61f + (3,9) | 300 | 4.252 | 94f6d7e04a4d452035300f18b984988c + (3,10) | 301 | 4.599 | 34ed066df378efacc9b924ec161e7639 +(10 rows) + +SELECT ctid,* FROM t1 WHERE '(80,0)'::tid < ctid AND c like '%ab%' LIMIT 25; + ctid | a | b | c +---------+------+-------+---------------------------------- + (80,4) | 7764 | 7.307 | fdff71fcab656abfbefaabecab1a7f6d + (80,10) | 7770 | 6.521 | c802ceaa43e6ad9ddc511cab5f34789c + (80,27) | 7787 | 7.681 | 72a8ab4748d4707fda159db0088d85de + (80,51) | 7811 | 5.957 | ddd1df443471e3abe89933f20d08116a + (80,52) | 7812 | 0.676 | eefc7bfe8fd6e2c8c01aa6ca7b1aab1a + (80,85) | 7845 | 0.486 | 0118a063b4aae95277f0bc1752c75abf + (80,89) | 7849 | 6.941 | ae3f4c649fb55c2ee3ef4d1abdb79ce5 + (81,19) | 7876 | 9.923 | 42dab56861d81108ee356d037190c315 + (81,36) | 7893 | 1.108 | 76fabdc82dd649afd7efa2d6894e568d + (81,43) | 7900 | 8.274 | 400c3241004b5db7ca7f5abfef2794f2 + (81,47) | 7904 | 4.554 | b5d62aa6024ab6a65a12c78c4c2d4efc + (81,50) | 7907 | 4.477 | 10b4945abe2e627db646b3c5226a4e50 + (81,56) | 7913 | 8.261 | 5527eaab87a00dbe1614481ef174f285 + (81,86) | 7943 | 6.993 | 0163cceb20f5ca7b313419c068abd9dc + (81,92) | 7949 | 0.418 | 5fedcaffc4aba6e57a3563b1c7d60c1d + (81,95) | 7952 | 1.682 | f316e3fe33f1f754851712c760ab9d48 + (82,2) | 7956 | 2.71 | 7fd4db88d31ab524e0afe153c4f9465a + (82,3) | 7957 | 2.215 | fa636c3d216834a2e0db24cc157ab5f0 + (82,9) | 7963 | 2.67 | a1d4c20b182ad7137ab3606f0e3fc8a4 + (82,12) | 7966 | 0.01 | ab00b14a2da2e3cdcc44f06265db6574 + (82,13) | 7967 | 0.551 | dab1263d1e6a88c9ba5e7e294def5e8b + (82,18) | 7972 | 4.943 | b5507f51b88a3ae4a99ba87e4877ab57 + (82,21) | 7975 | 6.321 | 25f09e44e51b17fb527fba402bfba5ab + (82,37) | 7991 | 0.868 | ef7be8c57773f2ab48d013434d3ad4f7 + (82,47) | 8001 | 0.091 | bc3c4a6331a8a9950945a1aa8c95ab8a +(25 rows) + +SELECT ctid,* FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%' LIMIT 25; + ctid | a | b | c +---------+------+-------+---------------------------------- + (1,13) | 110 | 1.052 | 5f93f983524def3dca464469d2cf9f3e + (1,28) | 125 | 8.49 | 3def184ad8f4755ff269862ea77393dd + (1,77) | 174 | 4.08 | bf8229696f7a3bb4700cfddef19fa23f + (2,5) | 199 | 1.387 | 84d9ee44e457ddef7f2c4f25dc8fa865 + (6,14) | 596 | 7.644 | b2eeb7362ef83deff5c7813a67e14f0a + (8,3) | 779 | 1.881 | 67d96d458abdef21792e6d8e590244e7 + (8,68) | 844 | 3.318 | e97ee2054defb209c35fe4dc94599061 + (8,91) | 867 | 0.073 | ede7e2b6d13a41ddf9f4bdef84fdc737 + (10,31) | 1001 | 0.921 | b8c37e33defde51cf91e1e03e51657da + (11,52) | 1119 | 7.95 | 8597a6cfa74defcbde3047c891d78f90 + (11,57) | 1124 | 9.97 | c7635bfd99248a2cdef8249ef7bfbef4 + (12,4) | 1168 | 5.197 | 2f29b6e3abc6ebdefb55456ea6ca5dc8 + (12,14) | 1178 | 1.548 | 7d771e0e8f3633ab54856925ecdefc5d + (13,91) | 1352 | 4.859 | 8b0dc65f996f98fd178a9defd0efa077 + (14,83) | 1441 | 5.126 | b197ffdef2ddc3308584dce7afa3661b + (16,2) | 1554 | 8.012 | 98986c005e5def2da341b4e0627d4712 + (16,90) | 1642 | 9.65 | 81c650caac28cdefce4de5ddc18befa0 + (17,5) | 1654 | 0.452 | 9d2682367c3935defcb1f9e247a97c0d + (17,77) | 1726 | 8.232 | 74563ba21a90da13dacf2a73e3ddefa7 + (17,97) | 1746 | 7.861 | 442cde81694ca09a626eeddefd1b74ca + (23,62) | 2293 | 8.685 | 5f6371c9126149517d9ba475def53139 + (25,36) | 2461 | 9.39 | cf05968255451bdefe3c5bc64d550517 + (26,87) | 2609 | 4.644 | 339a18def9898dd60a634b2ad8fbbd58 + (26,90) | 2612 | 6.852 | 1175defd049d3301e047ce50d93e9c7a + (27,46) | 2665 | 2.777 | e727fa59ddefcefb5d39501167623132 +(25 rows) + +SELECT t1.ctid,* FROM t1 NATURAL JOIN t3 WHERE t3.ctid IN ( + SELECT t3.ctid FROM t2 NATURAL JOIN t3 + WHERE t2.ctid BETWEEN '(4,0)'::tid AND '(5,0)'::tid) + LIMIT 25 OFFSET 50; + ctid | a | b | c | x +---------+-----+-------+----------------------------------+----- + (8,44) | 820 | 8.478 | e2a2dcc36a08a345332c751b2f2e476c | 640 + (1,21) | 118 | 5.258 | 5ef059938ba799aaa845e1c2e8a762bd | 636 + (8,92) | 868 | 9.159 | dd45045f8c68db9f54e70c67048d32e8 | 660 + (9,65) | 938 | 9.768 | 74bba22728b6185eec06286af6bec36d | 629 + (1,87) | 184 | 7.903 | 6cdd60ea0045eb7a6ec44c54d29ed402 | 672 + (9,88) | 961 | 0.201 | d707329bece455a462b58ce00d1194c9 | 612 + (0,94) | 94 | 0.365 | f4b9ec30ad9f68f89b29639786cb62ef | 664 + (7,51) | 730 | 7.128 | d5cfead94f5350c12c322b5b664544c1 | 669 + (4,52) | 440 | 6.344 | a8abb4bb284b5b27aa7cb790dc20f80b | 683 + (10,3) | 973 | 2.017 | ca75910166da03ff9d4655a0338e6b09 | 633 + (4,65) | 453 | 2.254 | 49ae49a23f67c759bf4fc791ba842aa2 | 674 + (9,48) | 921 | 5.248 | 430c3626b879b4005d41b8a46172e0c0 | 599 + (5,5) | 490 | 5.695 | c410003ef13d451727aeff9082c29a5c | 655 + (0,35) | 35 | 8.377 | 1c383cd30b7c298ab50293adfecb7b18 | 676 + (4,37) | 425 | 5.531 | 25b2822c2f5a3230abfadd476e8b04c9 | 611 + (10,12) | 982 | 1.32 | fec8d47d412bcbeece3d9128ae855a7a | 682 + (9,83) | 956 | 3.577 | 168908dd3227b8358eababa07fcaf091 | 612 + (9,66) | 939 | 7.47 | 3df1d4b96d8976ff5986393e8767f5b2 | 617 + (8,53) | 829 | 0.186 | ce78d1da254c0843eb23951ae077ff5f | 658 + (7,27) | 706 | 0.11 | 9c82c7143c102b71c593d98d96093fde | 609 + (8,63) | 839 | 3.211 | 8f7d807e1f53eff5f9efbe5cb81090fb | 678 + (8,33) | 809 | 6.587 | 32b30a250abd6331e03a2a1f16466346 | 622 + (8,1) | 777 | 2.071 | f1c1592588411002af340cbaedd6fc33 | 616 + (1,92) | 189 | 3.371 | a2557a7b2e94197ff767970b67041697 | 660 + (4,45) | 433 | 2.954 | 019d385eb67632a7e958e23f24bd07d7 | 625 +(25 rows) + +-- Misc Queries +SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50; + ctid | a | b | c +----------+-------+-------+---------------------------------- + (412,36) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0 + (412,35) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3 + (412,34) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1 + (412,33) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1 + (412,32) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9 + (412,31) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0 + (412,30) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73 + (412,29) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918 + (412,28) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7 + (412,27) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837 + (412,26) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5 + (412,25) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1 + (412,24) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f + (412,23) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e + (412,22) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452 + (412,21) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d + (412,20) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d + (412,19) | 39983 | 1.165 | c8f2e54fe7b8ab5c291ea8d5831669df + (412,18) | 39982 | 0.123 | 54996ced8ec545754b9c7404027969d8 + (412,17) | 39981 | 2.212 | 5e26badb3867ac7f26d3624ca39a9df4 + (412,16) | 39980 | 5.832 | 906fec3cca7ccc130fa2b1844aa10126 + (412,15) | 39979 | 4.755 | 34c5b339ea5917d60a26a3d1ef3a8fb2 + (412,14) | 39978 | 9.307 | 497290bed604efe673ff973099876689 + (412,13) | 39977 | 8.515 | 2a8b437cac4fd7012194170f76b385fe + (412,12) | 39976 | 7.702 | 589e19fafa037ef3e798363d7f9bd6d3 + (412,11) | 39975 | 1.391 | b4803e027e986edc9bb95df3e2a0b525 + (412,10) | 39974 | 3.145 | 1dc43216fffa0191d44329797ac898c0 + (412,9) | 39973 | 3.922 | 7a1fd501b45f517e975995e1ef9b956c + (412,8) | 39972 | 9.996 | ae0752582ef51ba1b182f3d7f7cd9751 + (412,7) | 39971 | 3.518 | b0c2187f8453302e766a91b72f65a6cf + (412,6) | 39970 | 5.561 | 12b25b878c7297605a57d9a104de9976 + (412,5) | 39969 | 9.684 | e0b6c164920a5a463aa98867f13038d4 + (412,4) | 39968 | 3.852 | f36a0f70291c903ae5664927b016c488 + (412,3) | 39967 | 7.527 | 385d960968e481fe04be1a04f429110d + (412,2) | 39966 | 7.57 | f339e1789582cc3bbe44b27ef01b3617 + (412,1) | 39965 | 4.137 | b404579a33d6c50199eefb174df7b02a + (411,97) | 39964 | 2.785 | 6e4fd1f8d7082b92ba9842ab88aab57e + (411,96) | 39963 | 4.053 | 21ee021ac65ce078bfd68b48368dc6a8 + (411,95) | 39962 | 4.783 | 76e91437c7f841334f01db0c85a8ec00 + (411,94) | 39961 | 2.377 | f80ca2ec372a23b59dc6a5bfee18bfac + (411,93) | 39960 | 3.522 | 744a37185fa8c4449a60bd11976f9045 + (411,92) | 39959 | 8.512 | 2d968cb99817ea5e49f4bcb70f7f154a + (411,91) | 39958 | 7.262 | b3f7bb40292f61fa966cb2a8a4cd339d + (411,90) | 39957 | 3.722 | eef43b55de33c2d530ea4a9669b81062 + (411,89) | 39956 | 3.402 | f5fee8f7da74f4887f5bcae2bafb6dd6 + (411,88) | 39955 | 5.841 | bc4d9b0e9bdbd3186592452785c479cc + (411,87) | 39954 | 6.971 | 23a28e6d57e4c5d8eb0bff70ae01ed09 + (411,86) | 39953 | 8.96 | e954fd23871b5505e296fe8fddf78623 + (411,85) | 39952 | 5.334 | 7ea9f068a460ee6c285e7ca7af850c51 + (411,84) | 39951 | 5.368 | a08758e937069b10802ab9331cd70273 +(50 rows) + +EXPLAIN (verbose) UPDATE t1 SET c = c || '_may_last_page' + WHERE ctid >= ( + SELECT '(' || relpages - 1 || ',20)' FROM pg_class + WHERE relname = 't1' AND + relnamespace = ( + SELECT oid FROM pg_namespace + WHERE nspname = 'regtest_custom_scan'))::tid + RETURNING ctid,*; + QUERY PLAN +-------------------------------------------------------------------------------------------------------------- + Update on regtest_custom_scan.t1 (cost=10.38..601.56 rows=13333 width=51) + Output: t1.ctid, t1.a, t1.b, t1.c + InitPlan 2 (returns $1) + -> Index Scan using pg_class_relname_nsp_index on pg_catalog.pg_class (cost=1.35..9.38 rows=1 width=4) + Output: (('('::text || ((pg_class.relpages - 1))::text) || ',20)'::text) + Index Cond: ((pg_class.relname = 't1'::name) AND (pg_class.relnamespace = $0)) + InitPlan 1 (returns $0) + -> Seq Scan on pg_catalog.pg_namespace (cost=0.00..1.07 rows=1 width=4) + Output: pg_namespace.oid + Filter: (pg_namespace.nspname = 'regtest_custom_scan'::name) + -> Custom Scan (ctidscan) on regtest_custom_scan.t1 (cost=1.00..592.18 rows=13333 width=51) + Output: t1.a, t1.b, (t1.c || '_may_last_page'::text), t1.ctid + TID Range: (t1.ctid >= ($1)::tid) +(13 rows) + +--set enable_ctidscan = off; +UPDATE t1 SET c = c || '_may_last_page' + WHERE ctid >= ( + SELECT '(' || relpages - 1 || ',20)' FROM pg_class + WHERE relname = 't1' AND + relnamespace = ( + SELECT oid FROM pg_namespace + WHERE nspname = 'regtest_custom_scan'))::tid + RETURNING ctid,*; + ctid | a | b | c +----------+-------+-------+------------------------------------------------ + (412,37) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d_may_last_page + (412,38) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d_may_last_page + (412,39) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452_may_last_page + (412,40) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e_may_last_page + (412,41) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f_may_last_page + (412,42) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1_may_last_page + (412,43) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5_may_last_page + (412,44) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837_may_last_page + (412,45) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7_may_last_page + (412,46) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918_may_last_page + (412,47) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73_may_last_page + (412,48) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0_may_last_page + (412,49) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9_may_last_page + (412,50) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1_may_last_page + (412,51) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1_may_last_page + (412,52) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3_may_last_page + (412,53) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0_may_last_page +(17 rows) + +SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50; + ctid | a | b | c +----------+-------+-------+------------------------------------------------ + (412,53) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0_may_last_page + (412,52) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3_may_last_page + (412,51) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1_may_last_page + (412,50) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1_may_last_page + (412,49) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9_may_last_page + (412,48) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0_may_last_page + (412,47) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73_may_last_page + (412,46) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918_may_last_page + (412,45) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7_may_last_page + (412,44) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837_may_last_page + (412,43) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5_may_last_page + (412,42) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1_may_last_page + (412,41) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f_may_last_page + (412,40) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e_may_last_page + (412,39) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452_may_last_page + (412,38) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d_may_last_page + (412,37) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d_may_last_page + (412,19) | 39983 | 1.165 | c8f2e54fe7b8ab5c291ea8d5831669df + (412,18) | 39982 | 0.123 | 54996ced8ec545754b9c7404027969d8 + (412,17) | 39981 | 2.212 | 5e26badb3867ac7f26d3624ca39a9df4 + (412,16) | 39980 | 5.832 | 906fec3cca7ccc130fa2b1844aa10126 + (412,15) | 39979 | 4.755 | 34c5b339ea5917d60a26a3d1ef3a8fb2 + (412,14) | 39978 | 9.307 | 497290bed604efe673ff973099876689 + (412,13) | 39977 | 8.515 | 2a8b437cac4fd7012194170f76b385fe + (412,12) | 39976 | 7.702 | 589e19fafa037ef3e798363d7f9bd6d3 + (412,11) | 39975 | 1.391 | b4803e027e986edc9bb95df3e2a0b525 + (412,10) | 39974 | 3.145 | 1dc43216fffa0191d44329797ac898c0 + (412,9) | 39973 | 3.922 | 7a1fd501b45f517e975995e1ef9b956c + (412,8) | 39972 | 9.996 | ae0752582ef51ba1b182f3d7f7cd9751 + (412,7) | 39971 | 3.518 | b0c2187f8453302e766a91b72f65a6cf + (412,6) | 39970 | 5.561 | 12b25b878c7297605a57d9a104de9976 + (412,5) | 39969 | 9.684 | e0b6c164920a5a463aa98867f13038d4 + (412,4) | 39968 | 3.852 | f36a0f70291c903ae5664927b016c488 + (412,3) | 39967 | 7.527 | 385d960968e481fe04be1a04f429110d + (412,2) | 39966 | 7.57 | f339e1789582cc3bbe44b27ef01b3617 + (412,1) | 39965 | 4.137 | b404579a33d6c50199eefb174df7b02a + (411,97) | 39964 | 2.785 | 6e4fd1f8d7082b92ba9842ab88aab57e + (411,96) | 39963 | 4.053 | 21ee021ac65ce078bfd68b48368dc6a8 + (411,95) | 39962 | 4.783 | 76e91437c7f841334f01db0c85a8ec00 + (411,94) | 39961 | 2.377 | f80ca2ec372a23b59dc6a5bfee18bfac + (411,93) | 39960 | 3.522 | 744a37185fa8c4449a60bd11976f9045 + (411,92) | 39959 | 8.512 | 2d968cb99817ea5e49f4bcb70f7f154a + (411,91) | 39958 | 7.262 | b3f7bb40292f61fa966cb2a8a4cd339d + (411,90) | 39957 | 3.722 | eef43b55de33c2d530ea4a9669b81062 + (411,89) | 39956 | 3.402 | f5fee8f7da74f4887f5bcae2bafb6dd6 + (411,88) | 39955 | 5.841 | bc4d9b0e9bdbd3186592452785c479cc + (411,87) | 39954 | 6.971 | 23a28e6d57e4c5d8eb0bff70ae01ed09 + (411,86) | 39953 | 8.96 | e954fd23871b5505e296fe8fddf78623 + (411,85) | 39952 | 5.334 | 7ea9f068a460ee6c285e7ca7af850c51 + (411,84) | 39951 | 5.368 | a08758e937069b10802ab9331cd70273 +(50 rows) + +-- PREPARE'd statement +PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1 + WHERE c 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: (c ~~ '%abc%'::text) + TID Range: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid)) +(3 rows) + +EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid); + QUERY PLAN +----------------------------------------------------------------------------------------- + Seq Scan on t1 + Filter: ((c ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid)) +(2 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 + + t1 + + (c ~~ '%abc%'::text) + + ((ctid >= '(0,0)'::tid) AND (ctid <= '(5,0)'::tid))+ + + + + + +(1 row) + +-- Turn off the feature +SET enable_ctidscan = off; +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) + +-- Test Cleanup +DROP SCHEMA regtest_custom_scan CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table t1 +drop cascades to table t2 +drop cascades to table t3 diff --git a/src/test/modules/ctidscan/sql/ctidscan.sql b/src/test/modules/ctidscan/sql/ctidscan.sql new file mode 100644 index 0000000..231768e --- /dev/null +++ b/src/test/modules/ctidscan/sql/ctidscan.sql @@ -0,0 +1,132 @@ +-- +-- Regression Tests for CustomScan Interface with CtidScan Provider +-- + +-- construction of test data +SET client_min_messages TO 'warning'; + +SET SEED TO 0.20140702; + +CREATE SCHEMA regtest_custom_scan; + +SET search_path TO regtest_custom_scan, public; + +CREATE TABLE t1 ( + a int primary key, + b float, + c text +); +INSERT INTO t1 (SELECT i, ceil(random()*10000.0) / 1000.0, md5(i::text) FROM generate_series(1,40000) i); +VACUUM ANALYZE t1; + +CREATE TABLE t2 ( + x int primary key, + y float, + z text +); +INSERT INTO t2 (SELECT i, ceil(random()*10000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,40200) i); +VACUUM ANALYZE t2; + +CREATE TABLE t3 ( + a int references t1(a), + x int references t2(x) +); +INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i); + +RESET client_min_messages; + +-- +-- Check Plans if no special extensions are loaded. +-- +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid; +EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid; +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 WHERE '(8,0)'::tid < ctid AND c like '%ab%'; +EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%'; +EXPLAIN (costs off) + SELECT * FROM t1, t2, t3 + WHERE t1.a = t3.a AND t2.x = t3.x + AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid + AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid + AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid; +EXPLAIN (costs off, verbose) + SELECT count(*), ceil(b) + FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid + GROUP BY ceil(b); + +-- +-- Plan for same query but ctidscan was loaded +-- +LOAD '$libdir/ctidscan'; +EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid; +EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid; +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 WHERE '(8,0)'::tid < ctid AND c like '%ab%'; +EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%'; -- SeqScan is cheaper +EXPLAIN (costs off) + SELECT * FROM t1, t2, t3 + WHERE t1.a = t3.a AND t2.x = t3.x + AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid + AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid + AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid; +EXPLAIN (costs off, verbose) + SELECT count(*), ceil(b) + FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid + GROUP BY ceil(b); + +-- +-- Run the query without EXPLAIN +-- +SELECT ctid,* FROM t1 WHERE ctid <= '(1,20)'::tid ORDER BY ctid DESC LIMIT 25; +SELECT count(*) FROM t1 WHERE ctid > '(200,0)'::tid; +SELECT ctid,* FROM t1 WHERE c like '%678%' AND ctid >= '(3,50)'::tid LIMIT 25; +SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; +SELECT ctid,* FROM t1 WHERE '(80,0)'::tid < ctid AND c like '%ab%' LIMIT 25; +SELECT ctid,* FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%' LIMIT 25; +SELECT t1.ctid,* FROM t1 NATURAL JOIN t3 WHERE t3.ctid IN ( + SELECT t3.ctid FROM t2 NATURAL JOIN t3 + WHERE t2.ctid BETWEEN '(4,0)'::tid AND '(5,0)'::tid) + LIMIT 25 OFFSET 50; + +-- Misc Queries +SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50; + +EXPLAIN (verbose) UPDATE t1 SET c = c || '_may_last_page' + WHERE ctid >= ( + SELECT '(' || relpages - 1 || ',20)' FROM pg_class + WHERE relname = 't1' AND + relnamespace = ( + SELECT oid FROM pg_namespace + WHERE nspname = 'regtest_custom_scan'))::tid + RETURNING ctid,*; +--set enable_ctidscan = off; +UPDATE t1 SET c = c || '_may_last_page' + WHERE ctid >= ( + SELECT '(' || relpages - 1 || ',20)' FROM pg_class + WHERE relname = 't1' AND + relnamespace = ( + SELECT oid FROM pg_namespace + WHERE nspname = 'regtest_custom_scan'))::tid + RETURNING ctid,*; + +SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50; + +-- PREPARE'd statement +PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1 + WHERE c 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); + +-- Turn off the feature +SET enable_ctidscan = off; + +EXPLAIN (costs off) +SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid; + +-- Test Cleanup +DROP SCHEMA regtest_custom_scan CASCADE; diff --git a/src/test/regress/expected/custom_scan.out b/src/test/regress/expected/custom_scan.out new file mode 100644 index 0000000..e69de29 diff --git a/src/test/regress/sql/custom_scan.sql b/src/test/regress/sql/custom_scan.sql new file mode 100644 index 0000000..683154b --- /dev/null +++ b/src/test/regress/sql/custom_scan.sql @@ -0,0 +1,53 @@ +-- +-- Regression Tests for CustomScan Interface with CtidScan Provider +-- + +-- construction of test data +SET client_min_messages TO 'warning'; + +SET SEED 0.20140702; + +CREATE SCHEMA regtest_custom_scan; + +SET search_path TO regtest_custom_scan, public; + +CREATE TABLE t1 ( + a int primary key, + b float, + c text +); +INSERT INTO t1 (SELECT i, ceil(random()*1000.0) / 1000.0, md5(i::text) FROM generate_series(1,1000) i); +VACUUM ANALYZE t1; + +CREATE TABLE t2 ( + x int primary key, + y float, + z text +); +INSERT INTO t2 (SELECT i, ceil(random()*1000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,1200) i); +VACUUM ANALYZE t2; + +CREATE TABLE t3 ( + a int references t1(a), + x int references t2(x) +); +INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i); + +RESET client_min_messages; +-- +-- Check Plans if no special extensions are 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; +EXPLAIN (costs off) SELECT * FROM t1, t2, t3 + WHERE t1.a = t3.a AND t2.x = t3.x + AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid + AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8.0)'::tid + AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid; + + + +-- Test Cleanup +DROP SCHEMA regtest_custom_scan CASCADE;