contrib/Makefile | 1 + contrib/ctidscan/Makefile | 14 + contrib/ctidscan/ctidscan.c | 760 +++++++++++++++++++++++++++++ doc/src/sgml/contrib.sgml | 1 + doc/src/sgml/ctidscan.sgml | 108 ++++ doc/src/sgml/custom-scan.sgml | 8 +- doc/src/sgml/filelist.sgml | 1 + src/backend/optimizer/path/costsize.c | 7 +- 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 + 17 files changed, 1253 insertions(+), 14 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..72bbf17 --- /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 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) 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 chained with AND- + * boolean operator, so all the operator works for narrowing down the + * scope of custom tid scan. + */ +static List * +CTidQualFromExpr(Node *expr, int varno) +{ + if (is_opclause(expr)) + { + OpExpr *op = (OpExpr *) expr; + Node *arg1; + Node *arg2; + Node *other = NULL; + + /* only inequality operators are candidate */ + if (op->opno != TIDLessOperator && + op->opno != TIDLessEqualOperator && + op->opno != TIDGreaterOperator && + op->opno != TIDGreaterEqualOperator) + return NULL; + + if (list_length(op->args) != 2) + return false; + + arg1 = linitial(op->args); + arg2 = lsecond(op->args); + + if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + if (exprType(other) != TIDOID) + return NULL; /* probably can't happen */ + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL; + + return list_make1(copyObject(op)); + } + else if (and_clause(expr)) + { + List *rlst = NIL; + ListCell *lc; + + foreach(lc, ((BoolExpr *) expr)->args) + { + List *temp = CTidQualFromExpr((Node *) lfirst(lc), varno); + + rlst = list_concat(rlst, temp); + } + return rlst; + } + return NIL; +} + +/* + * CTidEstimateCosts + * + * It estimates cost to scan the target relation according to the given + * restriction clauses. Its logic to scan relations are almost same as + * SeqScan doing, because it uses regular heap_getnext(), except for + * the number of tuples to be scanned if restriction clauses work well. +*/ +static void +CTidEstimateCosts(PlannerInfo *root, + RelOptInfo *baserel, + 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 retrieved 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 chosen 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 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. + */ + 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 sought 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 sought. + * 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) + { + /* seek 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..d010d5c --- /dev/null +++ b/doc/src/sgml/ctidscan.sgml @@ -0,0 +1,108 @@ + + + + ctidscan + + + 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 sequential 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 chosen 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/custom-scan.sgml b/doc/src/sgml/custom-scan.sgml index b57d82f..f53902d 100644 --- a/doc/src/sgml/custom-scan.sgml +++ b/doc/src/sgml/custom-scan.sgml @@ -18,7 +18,7 @@ Overall, there are four major tasks that a custom-scan provider should implement. The first task is the registration of custom-scan provider itself. Usually, this needs to be done once at the _PG_init() - entrypoint when the module is loading. The remaing three tasks are all done + entrypoint when the module is loading. The reaming three tasks are all done when a query is planning and executing. The second task is the submission of candidate paths to either scan or join relations with an adequate cost for the core planner. Then, the planner will choose the cheapest path from all of @@ -50,7 +50,7 @@ This custom scan in this module replaces a local join of foreign tables managed by postgres_fdw with a scan that fetches - remotely joined relations. It demostrates the way to implement a custom + remotely joined relations. It demonstrates the way to implement a custom scan node that performs join nodes. @@ -145,7 +145,7 @@ typedef struct CustomPath Construction of custom plan node - Once CustomPath was choosen by the query planner, + Once CustomPath was chosen by the query planner, it calls back to its associated to the custom scan provider to complete setting up the CustomScan plan node according to the path information. @@ -160,7 +160,7 @@ InitCustomScanPlan(PlannerInfo *root, The query planner does basic initialization on the cscan_plan being allocated, then the custom scan provider can apply final initialization. cscan_path is the path node that was - constructed on the previous stage then was choosen. + constructed on the previous stage then was chosen. tlist is a list of TargetEntry to be assigned on the Plan portion in the cscan_plan. Also, scan_clauses is a list of RestrictInfo to 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 c6010d9..e55b16e 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); @@ -977,7 +974,7 @@ cost_tidscan(Path *path, PlannerInfo *root, /* * The TID qual expressions will be computed once, any other baserestrict - * quals once per retrived tuple. + * quals once per retrieved tuple. */ cost_qual_eval(&tid_qual_cost, tidquals, root); @@ -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 1af5469..630c8e7 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -1081,7 +1081,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 78efaa5..b040334 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..a5a205d --- /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 cleanup +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..fc13e9f --- /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 cleanup +DROP SCHEMA regtest_custom_scan CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table t1 +drop cascades to table t2 diff --git a/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