contrib/Makefile | 1 + contrib/ctidscan/Makefile | 14 + contrib/ctidscan/ctidscan.c | 760 +++++++++++++++++++++++++++++ doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/ctidscan.sgml | 107 ++++ doc/src/sgml/filelist.sgml | 1 + src/backend/optimizer/path/costsize.c | 5 +- src/backend/optimizer/plan/setrefs.c | 2 +- src/include/catalog/pg_operator.h | 4 + src/include/optimizer/cost.h | 3 + src/include/optimizer/planmain.h | 1 + src/test/regress/GNUmakefile | 15 +- src/test/regress/input/custom_scan.source | 49 ++ src/test/regress/output/custom_scan.source | 290 +++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + 16 files changed, 1247 insertions(+), 9 deletions(-) diff --git a/contrib/Makefile b/contrib/Makefile index 8a2a937..703e5a5 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..708c5b7 --- /dev/null +++ b/contrib/ctidscan/Makefile @@ -0,0 +1,14 @@ +# contrib/ctidscan/Makefile + +MODULES = 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..31db244 --- /dev/null +++ b/contrib/ctidscan/ctidscan.c @@ -0,0 +1,760 @@ +/* + * 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 chepest 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) 2013, 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 "executor/nodeCustom.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" +#include "optimizer/paths.h" +#include "optimizer/pathnode.h" +#include "optimizer/planmain.h" +#include "optimizer/restrictinfo.h" +#include "storage/bufmgr.h" +#include "storage/itemptr.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/spccache.h" + +extern void _PG_init(void); + +PG_MODULE_MAGIC; + +static add_scan_path_hook_type add_scan_path_next; + +#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 chainned 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, + CustomPath *cpath) +{ + List *ctidquals = 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_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 (cpath->path.param_info) + cpath->path.rows = cpath->path.param_info->ppi_rows; + else + cpath->path.rows = baserel->rows; + + /* Estimate how many tuples we may retrieve */ + ItemPointerSet(&ip_min, 0, 0); + ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber); + foreach (lc, ctidquals) + { + 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 undeterministic untill + * 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 retrived tuple. + */ + cost_qual_eval(&ctid_qual_cost, ctidquals, 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 */ + get_restriction_qual_cost(root, baserel, + cpath->path.param_info, + &qpqual_cost); + + /* + * 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; + + cpath->path.startup_cost = startup_cost; + cpath->path.total_cost = startup_cost + run_cost; +} + +/* + * CTidAddScanPath + * + * It adds a custom scan path if inequality operators are given on the + * relation to be scanned and makes sense to reduce number of tuples. + */ +static void +CTidAddScanPath(PlannerInfo *root, + RelOptInfo *baserel, + RangeTblEntry *rte) +{ + char relkind; + List *rlst = NIL; + ListCell *lc; + + /* Gives another extensions chance to add a path */ + if (add_scan_path_next) + (*add_scan_path_next)(root, baserel, rte); + + /* All we support is regular relations */ + 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); + rlst = list_concat(rlst, 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 (rlst != NIL) + { + CustomPath *cpath = makeNode(CustomPath); + 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->path.pathtype = T_CustomScan; + cpath->path.parent = baserel; + cpath->path.param_info = get_baserel_parampathinfo(root, baserel, + required_outer); + cpath->custom_name = pstrdup("ctidscan"); + cpath->custom_flags = CUSTOM__SUPPORT_BACKWARD_SCAN; + cpath->custom_private = rlst; + + CTidEstimateCosts(root, baserel, cpath); + + add_path(baserel, &cpath->path); + } +} + +/* + * CTidInitCustomScanPlan + * + * It initializes the given CustomScan plan object according to the CustomPath + * being choosen by the optimizer. + */ +static void +CTidInitCustomScanPlan(PlannerInfo *root, + CustomScan *cscan_plan, + CustomPath *cscan_path, + List *tlist, + List *scan_clauses) +{ + List *ctidquals = cscan_path->custom_private; + + /* should be a base relation */ + Assert(cscan_path->path.parent->relid > 0); + Assert(cscan_path->path.parent->rtekind == RTE_RELATION); + + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + scan_clauses = extract_actual_clauses(scan_clauses, false); + + /* + * Most of initialization stuff was done at nodeCustomScan.c. So, all + * we need to do is to put clauses that were little bit adjusted and + * private stuff; list of restriction clauses in this case. + */ + cscan_plan->scan.plan.targetlist = tlist; + cscan_plan->scan.plan.qual = scan_clauses; + cscan_plan->custom_private = ctidquals; +} + +/* + * CTidScanState + * + * State of custom-tid scan during its execution. + */ +typedef struct { + Index scanrelid; /* range table index of the relation */ + ItemPointerData ip_min; /* minimum ItemPointer */ + ItemPointerData ip_max; /* maximum ItemPointer */ + int32 ip_min_comp; /* comparison policy to ip_min */ + int32 ip_max_comp; /* comparison policy to ip_max */ + bool ip_needs_eval; /* true, if needs to seek again */ + List *ctid_quals; /* list of ExprState for inequality ops */ +} CTidScanState; + +static bool +CTidEvalScanZone(CustomScanState *node) +{ + CTidScanState *ctss = node->custom_state; + ExprContext *econtext = node->ss.ps.ps_ExprContext; + ListCell *lc; + + /* + * See ItemPointerCompare(), ip_max_comp shall be usually either 1 or + * 0 if tid of fetched records are larger than or equal with ip_min. + * To detect end of scan, we shall check whether the result of + * ItemPointerCompare() is less than ip_max_comp, so it never touch + * the point if ip_max_comp is -1, because all the result is either + * 1, 0 or -1. So, it is same as "open ended" as if no termination + * condition was set. + */ + ctss->ip_min_comp = -1; + ctss->ip_max_comp = 1; + + /* 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); + Oid opno; + ExprState *exstate; + ItemPointer itemptr; + bool isnull; + + if (IsCTIDVar(arg1, ctss->scanrelid)) + { + exstate = (ExprState *) lsecond(fexstate->args); + opno = op->opno; + } + else if (IsCTIDVar(arg2, ctss->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) + { + /* + * OK, we could calculate a particular TID that should be + * larger than, less than or equal with fetched record, thus, + * it allows to determine upper or lower bounds of this scan. + */ + switch (opno) + { + case TIDLessOperator: + if (ctss->ip_max_comp > 0 || + ItemPointerCompare(itemptr, &ctss->ip_max) <= 0) + { + ItemPointerCopy(itemptr, &ctss->ip_max); + ctss->ip_max_comp = -1; + } + break; + case TIDLessEqualOperator: + if (ctss->ip_max_comp > 0 || + ItemPointerCompare(itemptr, &ctss->ip_max) < 0) + { + ItemPointerCopy(itemptr, &ctss->ip_max); + ctss->ip_max_comp = 0; + } + break; + case TIDGreaterOperator: + if (ctss->ip_min_comp < 0 || + ItemPointerCompare(itemptr, &ctss->ip_min) >= 0) + { + ItemPointerCopy(itemptr, &ctss->ip_min); + ctss->ip_min_comp = 0; + } + break; + case TIDGreaterEqualOperator: + if (ctss->ip_min_comp < 0 || + ItemPointerCompare(itemptr, &ctss->ip_min) > 0) + { + ItemPointerCopy(itemptr, &ctss->ip_min); + ctss->ip_min_comp = 1; + } + break; + default: + elog(ERROR, "unsupported operator"); + break; + } + } + else + { + /* + * Whole of the restriction clauses chainned 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. + */ + return false; + } + } + return true; +} + +/* + * CTidBeginCustomScan + * + * It initializes the given CustomScanState according to the CustomScan plan. + */ +static void +CTidBeginCustomScan(CustomScanState *node, int eflags) +{ + CustomScan *cscan = (CustomScan *)node->ss.ps.plan; + Index scanrelid = ((Scan *)node->ss.ps.plan)->scanrelid; + EState *estate = node->ss.ps.state; + CTidScanState *ctss; + + /* Do nothing anymore in EXPLAIN (no ANALYZE) case. */ + if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + return; + + /* Begin sequential scan, but pointer shall be seeked later */ + node->ss.ss_currentScanDesc + = heap_beginscan(node->ss.ss_currentRelation, + estate->es_snapshot, 0, NULL); + + /* init CTidScanState */ + ctss = palloc0(sizeof(CTidScanState)); + ctss->scanrelid = scanrelid; + ctss->ctid_quals = (List *) + ExecInitExpr((Expr *)cscan->custom_private, &node->ss.ps); + ctss->ip_needs_eval = true; + + node->custom_state = ctss; +} + +/* + * CTidSeekPosition + * + * It seeks current scan position into a particular point we specified. + * Next heap_getnext() will fetch a record from the point we seeked. + * It returns false, if specified position was out of range thus does not + * make sense to scan any mode. Elsewhere, true shall be return. + */ +static bool +CTidSeekPosition(HeapScanDesc scan, ItemPointer pos, ScanDirection direction) +{ + BlockNumber bnum = BlockIdGetBlockNumber(&pos->ip_blkid); + ItemPointerData save_mctid; + int save_mindex; + + Assert(direction == BackwardScanDirection || + direction == ForwardScanDirection); + + /* + * In case when block-number is out of the range, it is obvious that + * no tuples shall be fetched if forward scan direction. On the other + * hand, we have nothing special for backward scan direction. + * Note that heap_getnext() shall return NULL tuple just after + * heap_rescan() if NoMovementScanDirection is given. Caller of this + * function override scan direction if 'true' was returned, so it makes + * this scan terminated immediately. + */ + if (bnum >= scan->rs_nblocks) + { + heap_rescan(scan, NULL); + /* Termination of this scan immediately */ + if (direction == ForwardScanDirection) + return true; + /* Elsewhere, backward scan from the beginning */ + return false; + } + + /* save the marked position */ + ItemPointerCopy(&scan->rs_mctid, &save_mctid); + save_mindex = scan->rs_mindex; + + /* + * Ensure the block that includes the position shall be loaded on + * heap_restrpos(). Because heap_restrpos() internally calls + * heapgettup() or heapgettup_pagemode() that kicks heapgetpage() + * when rs_cblock is different from the block number being pointed + * by rs_mctid, it makes sense to put invalid block number not to + * match previous value. + */ + scan->rs_cblock = InvalidBlockNumber; + + /* Put a pseudo value as if heap_markpos() save a position. */ + ItemPointerCopy(pos, &scan->rs_mctid); + if (scan->rs_pageatatime) + scan->rs_mindex = ItemPointerGetOffsetNumber(pos) - 1; + + /* Seek to the point */ + heap_restrpos(scan); + + /* restore the marked position */ + ItemPointerCopy(&save_mctid, &scan->rs_mctid); + scan->rs_mindex = save_mindex; + + return true; +} + +/* + * CTidAccessCustomScan + * + * Access method of ExecScan(). 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 = node->custom_state; + HeapScanDesc scan = node->ss.ss_currentScanDesc; + TupleTableSlot *slot = node->ss.ss_ScanTupleSlot; + EState *estate = node->ss.ps.state; + ScanDirection direction = estate->es_direction; + HeapTuple tuple; + + if (ctss->ip_needs_eval) + { + /* It terminates this scan, if result set shall be obvious empty. */ + if (!CTidEvalScanZone(node)) + return NULL; + + if (direction == ForwardScanDirection) + { + /* seek to the point if min-tid was obvious */ + if (ctss->ip_min_comp != -1) + { + if (CTidSeekPosition(scan, &ctss->ip_min, direction)) + direction = NoMovementScanDirection; + } + else if (scan->rs_inited) + heap_rescan(scan, NULL); + } + else if (direction == BackwardScanDirection) + { + /* seel to the point if max-tid was obvious */ + if (ctss->ip_max_comp != 1) + { + if (CTidSeekPosition(scan, &ctss->ip_max, direction)) + direction = NoMovementScanDirection; + } + else if (scan->rs_inited) + heap_rescan(scan, NULL); + } + else + elog(ERROR, "unexpected scan direction"); + + ctss->ip_needs_eval = false; + } + + /* + * get the next tuple from the table + */ + tuple = heap_getnext(scan, direction); + if (!HeapTupleIsValid(tuple)) + return NULL; + + /* + * check whether the fetched tuple reached to the upper bound + * if forward scan, or the lower bound if backward scan. + */ + if (direction == ForwardScanDirection) + { + if (ItemPointerCompare(&tuple->t_self, + &ctss->ip_max) > ctss->ip_max_comp) + return NULL; + } + else if (direction == BackwardScanDirection) + { + if (ItemPointerCompare(&scan->rs_ctup.t_self, + &ctss->ip_min) < ctss->ip_min_comp) + return NULL; + } + ExecStoreTuple(tuple, slot, scan->rs_cbuf, false); + + return slot; +} + +/* + * CTidRecheckCustomScan + * + * Recheck method of ExecScan(). We don't need recheck logic. + */ +static bool +CTidRecheckCustomScan(CustomScanState *node, TupleTableSlot *slot) +{ + return true; +} + +/* + * CTidExecCustomScan + * + * It fetches a tuple from the underlying heap scan, according to + * the Execscan() manner. + */ +static TupleTableSlot * +CTidExecCustomScan(CustomScanState *node) +{ + return ExecScan(&node->ss, + (ExecScanAccessMtd) CTidAccessCustomScan, + (ExecScanRecheckMtd) CTidRecheckCustomScan); +} + +/* + * CTidEndCustomScan + * + * It terminates custom tid scan. + */ +static void +CTidEndCustomScan(CustomScanState *node) +{ + CTidScanState *ctss = node->custom_state; + + /* if ctss != NULL, we started underlying heap-scan */ + if (ctss) + heap_endscan(node->ss.ss_currentScanDesc); +} + +/* + * CTidReScanCustomScan + * + * It rewinds current position of the scan. Setting ip_needs_eval indicates + * to calculate the starting point again and rewinds underlying heap scan + * on the next ExecScan timing. + */ +static void +CTidReScanCustomScan(CustomScanState *node) +{ + CTidScanState *ctss = node->custom_state; + + ctss->ip_needs_eval = true; + + ExecScanReScan(&node->ss); +} + +/* + * Entrypoint of this extension + */ +void +_PG_init(void) +{ + CustomProvider provider; + + /* registration of callback on add scan path */ + add_scan_path_next = add_scan_path_hook; + add_scan_path_hook = CTidAddScanPath; + + /* registration of custom scan provider */ + memset(&provider, 0, sizeof(provider)); + snprintf(provider.name, sizeof(provider.name), "ctidscan"); + provider.InitCustomScanPlan = CTidInitCustomScanPlan; + provider.BeginCustomScan = CTidBeginCustomScan; + provider.ExecCustomScan = CTidExecCustomScan; + provider.EndCustomScan = CTidEndCustomScan; + provider.ReScanCustomScan = CTidReScanCustomScan; + + register_custom_provider(&provider); +} diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml index dd8e09e..4f23b74 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..60081f7 --- /dev/null +++ b/doc/src/sgml/ctidscan.sgml @@ -0,0 +1,107 @@ + + + + lo + + + ctidscan + + + + The ctidscan module provides an additional logic to scan + regular relations if WHERE clause contains inequality + operators that compares something with ctid system column. + It also performs as a proof-of-concept implementation that works on + the custom-scan APIs that enables to extend the core executor system. + + + + Overview + + Once this module is loaded, it registers itself as a custom-scan provider. + It allows to provide an additional scan path on regular relations using + qualifiers that reference ctid system column. + + + For example, the query below usually falls to sequential scan if this + module was not loaded. + +SELECT ctid,* FROM my_table WHERE ctid > '(100,0)'::tid; + + On the other hand, ctidscan module can construct an alternative + scan plan utilizing inequality operators that involve ctid + system column, to reduce number of rows to be processed. + It does not make sense obviously to read tuples within pages being located + on 99th page or prior. So, it seeks the internal pointer to scan into + (100,0) at beginning of the scan, even though it internally + uses same logic with sequential scan. + + + Usually, PostgreSQL runs queries with inequality operators + that involves ctid system column using sequential scan, as + follows. + +postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid; + QUERY PLAN +-------------------------------------------------------- + Seq Scan on t1 (cost=0.00..209.00 rows=3333 width=37) + Filter: (ctid > '(100,0)'::tid) +(2 rows) + + It works well except for the waste of i/o loads on the pages that contains + the records to be skipped. + + + On the other hands, an alternative scan path implemented with + ctidscan provides more efficient way; that skips the first + 100 pages prior to sequencial scan, as follows. + +postgres=# load 'ctidscan'; +LOAD +postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid; + QUERY PLAN +---------------------------------------------------------------------- + Custom Scan (ctidscan) on t1 (cost=0.00..100.00 rows=3333 width=37) + Filter: (ctid > '(100,0)'::tid) +(2 rows) + + The optimizer internally compares all the candidates of scan paths, then + chooses a path with cheapest cost. The custom-scan path provided by + ctidscan is usually cheaper than sequential scan because of + smaller number of tuples to be processed. + + + Of course, it shall not be choosen if we have more cheaper path than the + above custom-scan path. Index-scan based on equality operation is usually + cheaper than this custom-scan, so optimizer adopts it instead of sequential + scan or custom scan provided by ctidscan for instance. + +postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(100,0)'::tid AND a = 100; + QUERY PLAN +------------------------------------------------------------------- + Index Scan using t1_pkey on t1 (cost=0.29..8.30 rows=1 width=37) + Index Cond: (a = 100) + Filter: (ctid > '(100,0)'::tid) +(3 rows) + + + + Its usage is quite simple. All you need to do is, loading + the ctidscan into PostgreSQL using + command, + or + parameter, according to + your convenience. + + + We have no configurable parameter in this module, right now. + + + + Author + + KaiGai Kohei kaigai@kaigai.gr.jp + + + + diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 1e96829..0dfbdcc 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -105,6 +105,7 @@ + diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index e5b0cd7..c6e1634 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -130,9 +130,6 @@ static MergeScanSelCache *cached_scansel(PlannerInfo *root, static void cost_rescan(PlannerInfo *root, Path *path, Cost *rescan_startup_cost, Cost *rescan_total_cost); static bool cost_qual_eval_walker(Node *node, cost_qual_eval_context *context); -static void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel, - ParamPathInfo *param_info, - QualCost *qpqual_cost); static bool has_indexed_join_quals(NestPath *joinpath); static double approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals); @@ -3201,7 +3198,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context) * some of the quals. We assume baserestrictcost was previously set by * set_baserel_size_estimates(). */ -static void +void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel, ParamPathInfo *param_info, QualCost *qpqual_cost) diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index aae5a1c..30cf7e5 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -1084,7 +1084,7 @@ copyVar(Var *var) * We assume it's okay to update opcode info in-place. So this could possibly * scribble on the planner's input data structures, but it's OK. */ -static void +void fix_expr_common(PlannerInfo *root, Node *node) { /* We assume callers won't call us on a NULL pointer */ diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index 0350ef6..0c7a233 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -159,15 +159,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/include/optimizer/cost.h b/src/include/optimizer/cost.h index 444ab740..a2873ec 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -145,6 +145,9 @@ extern void final_cost_hashjoin(PlannerInfo *root, HashPath *path, extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan); extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root); extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root); +extern void get_restriction_qual_cost(PlannerInfo *root, RelOptInfo *baserel, + ParamPathInfo *param_info, + QualCost *qpqual_cost); extern void compute_semi_anti_join_factors(PlannerInfo *root, RelOptInfo *outerrel, RelOptInfo *innerrel, diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h index ba7ae7c..13cfba8 100644 --- a/src/include/optimizer/planmain.h +++ b/src/include/optimizer/planmain.h @@ -127,6 +127,7 @@ extern List *remove_useless_joins(PlannerInfo *root, List *joinlist); * prototypes for plan/setrefs.c */ extern Plan *set_plan_references(PlannerInfo *root, Plan *plan); +extern void fix_expr_common(PlannerInfo *root, Node *node); extern void fix_opfuncids(Node *node); extern void set_opfuncid(OpExpr *opexpr); extern void set_sa_opfuncid(ScalarArrayOpExpr *opexpr); diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile index d5935b6..9645025 100644 --- a/src/test/regress/GNUmakefile +++ b/src/test/regress/GNUmakefile @@ -90,6 +90,7 @@ regress_data_files = \ install-tests: all install install-lib installdirs-tests $(MAKE) -C $(top_builddir)/contrib/spi install + $(MAKE) -C $(top_builddir)/contrib/ctidscan install for file in $(regress_data_files); do \ $(INSTALL_DATA) $$file '$(DESTDIR)$(pkglibdir)/regress/'$$file || exit; \ done @@ -98,9 +99,9 @@ installdirs-tests: installdirs $(MKDIR_P) $(patsubst $(srcdir)/%/,'$(DESTDIR)$(pkglibdir)/regress/%',$(sort $(dir $(regress_data_files)))) -# Get some extra C modules from contrib/spi and contrib/dummy_seclabel... +# Get some extra C modules from contrib/spi, dummy_seclabel and ctidscan -all: refint$(DLSUFFIX) autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX) +all: refint$(DLSUFFIX) autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX) ctidscan$(DLSUFFIX) refint$(DLSUFFIX): $(top_builddir)/contrib/spi/refint$(DLSUFFIX) cp $< $@ @@ -111,19 +112,27 @@ autoinc$(DLSUFFIX): $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX) dummy_seclabel$(DLSUFFIX): $(top_builddir)/contrib/dummy_seclabel/dummy_seclabel$(DLSUFFIX) cp $< $@ +ctidscan$(DLSUFFIX): $(top_builddir)/contrib/ctidscan/ctidscan$(DLSUFFIX) + cp $< $@ + $(top_builddir)/contrib/spi/refint$(DLSUFFIX): | submake-contrib-spi ; $(top_builddir)/contrib/spi/autoinc$(DLSUFFIX): | submake-contrib-spi ; $(top_builddir)/contrib/dummy_seclabel/dummy_seclabel$(DLSUFFIX): | submake-contrib-dummy_seclabel ; +$(top_builddir)/contrib/ctidscan/ctidscan$(DLSUFFIX): | submake-contrib-ctidscan + submake-contrib-spi: $(MAKE) -C $(top_builddir)/contrib/spi submake-contrib-dummy_seclabel: $(MAKE) -C $(top_builddir)/contrib/dummy_seclabel -.PHONY: submake-contrib-spi submake-contrib-dummy_seclabel +submake-contrib-ctidscan: + $(MAKE) -C $(top_builddir)/contrib/ctidscan + +.PHONY: submake-contrib-spi submake-contrib-dummy_seclabel submake-contrib-ctidscan # Tablespace setup diff --git a/src/test/regress/input/custom_scan.source b/src/test/regress/input/custom_scan.source new file mode 100644 index 0000000..1ad0e7a --- /dev/null +++ b/src/test/regress/input/custom_scan.source @@ -0,0 +1,49 @@ +-- +-- Regression Tests for Custom Scan 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; + +LOAD '@libdir@/ctidscan@DLSUFFIX@'; +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; + +-- Test creanup +DROP SCHEMA regtest_custom_scan CASCADE; \ No newline at end of file diff --git a/src/test/regress/output/custom_scan.source b/src/test/regress/output/custom_scan.source new file mode 100644 index 0000000..09c1bda --- /dev/null +++ b/src/test/regress/output/custom_scan.source @@ -0,0 +1,290 @@ +-- +-- Regression Tests for Custom Scan 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) + +LOAD '@libdir@/ctidscan@DLSUFFIX@'; +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)) +(2 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) + -> Sort + Sort Key: t2.ctid + -> Custom Scan (ctidscan) on t2 + Filter: (ctid > '(1,75)'::tid) +(10 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) + +-- Test creanup +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/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 5758b07..bd6fc3f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -78,7 +78,7 @@ ignore: random # ---------- # Another group of parallel tests # ---------- -test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete +test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete custom_scan # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 78348f5..0e191a2 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -91,6 +91,7 @@ test: btree_index test: hash_index test: update test: delete +test: custom_scan test: namespace test: prepared_xacts test: privileges