contrib/Makefile | 1 + contrib/ctidscan/Makefile | 14 + contrib/ctidscan/ctidscan.c | 761 +++++++++++++++++++++++++++++ doc/src/sgml/ctidscan.sgml | 107 ++++ src/backend/commands/explain.c | 78 +++ src/backend/executor/Makefile | 2 +- src/backend/executor/execAmi.c | 34 +- src/backend/executor/execProcnode.c | 14 + src/backend/executor/execQual.c | 10 +- src/backend/executor/execUtils.c | 4 +- src/backend/executor/nodeCustom.c | 252 ++++++++++ src/backend/nodes/bitmapset.c | 62 +++ src/backend/nodes/copyfuncs.c | 30 ++ src/backend/nodes/outfuncs.c | 19 + src/backend/nodes/print.c | 4 + src/backend/optimizer/path/allpaths.c | 23 + src/backend/optimizer/path/costsize.c | 7 +- src/backend/optimizer/path/joinpath.c | 18 + src/backend/optimizer/plan/createplan.c | 103 ++++ src/backend/optimizer/plan/setrefs.c | 27 +- src/backend/optimizer/plan/subselect.c | 10 + src/backend/optimizer/util/pathnode.c | 40 ++ src/backend/utils/adt/ruleutils.c | 44 +- src/include/catalog/pg_operator.h | 4 + src/include/executor/executor.h | 3 +- src/include/executor/nodeCustom.h | 94 ++++ src/include/nodes/bitmapset.h | 4 + src/include/nodes/execnodes.h | 17 + src/include/nodes/nodes.h | 3 + src/include/nodes/plannodes.h | 16 + src/include/nodes/primnodes.h | 1 + src/include/nodes/relation.h | 16 + src/include/optimizer/cost.h | 3 + src/include/optimizer/pathnode.h | 10 + src/include/optimizer/paths.h | 25 + 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 + 41 files changed, 2194 insertions(+), 24 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..c392051 --- /dev/null +++ b/contrib/ctidscan/ctidscan.c @@ -0,0 +1,761 @@ +/* + * 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) +{ + Index scan_relid = cscan_path->path.parent->relid; + List *ctidquals = cscan_path->custom_private; + + /* should be a base relation */ + Assert(scan_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/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/src/backend/commands/explain.c b/src/backend/commands/explain.c index 4e93df2..39d2c12 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -19,6 +19,7 @@ #include "commands/defrem.h" #include "commands/prepare.h" #include "executor/hashjoin.h" +#include "executor/nodeCustom.h" #include "foreign/fdwapi.h" #include "optimizer/clauses.h" #include "parser/parsetree.h" @@ -84,6 +85,7 @@ static void show_hash_info(HashState *hashstate, ExplainState *es); static void show_instrumentation_count(const char *qlabel, int which, PlanState *planstate, ExplainState *es); static void show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es); +static void show_customscan_info(CustomScanState *cstate, ExplainState *es); static const char *explain_get_index_name(Oid indexId); static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir, ExplainState *es); @@ -683,6 +685,11 @@ ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used) *rels_used = bms_add_member(*rels_used, ((Scan *) plan)->scanrelid); break; + case T_CustomScan: + if (((Scan *) plan)->scanrelid > 0) + *rels_used = bms_add_member(*rels_used, + ((Scan *) plan)->scanrelid); + break; case T_ModifyTable: /* cf ExplainModifyTarget */ *rels_used = bms_add_member(*rels_used, @@ -809,6 +816,7 @@ ExplainNode(PlanState *planstate, List *ancestors, const char *sname; /* node type name for non-text output */ const char *strategy = NULL; const char *operation = NULL; + char namebuf[NAMEDATALEN + 32]; int save_indent = es->indent; bool haschildren; @@ -897,6 +905,13 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_ForeignScan: pname = sname = "Foreign Scan"; break; + case T_CustomScan: + snprintf(namebuf, sizeof(namebuf), "Custom Scan (%s)", + ((CustomScan *) plan)->custom_name); + pname = pstrdup(namebuf); + sname = "Custom Scan"; + operation = ((CustomScan *) plan)->custom_name; + break; case T_Material: pname = sname = "Materialize"; break; @@ -1013,6 +1028,10 @@ ExplainNode(PlanState *planstate, List *ancestors, case T_ForeignScan: ExplainScanTarget((Scan *) plan, es); break; + case T_CustomScan: + if (((Scan *) plan)->scanrelid > 0) + ExplainScanTarget((Scan *) plan, es); + break; case T_IndexScan: { IndexScan *indexscan = (IndexScan *) plan; @@ -1291,6 +1310,17 @@ ExplainNode(PlanState *planstate, List *ancestors, planstate, es); show_foreignscan_info((ForeignScanState *) planstate, es); break; + case T_CustomScan: + if (((CustomScan *)plan)->funcexpr != NULL && es->verbose) + show_expression(((CustomScan *)plan)->funcexpr, + "Function Call", planstate, ancestors, + es->verbose, es); + show_scan_qual(plan->qual, "Filter", planstate, ancestors, es); + if (plan->qual) + show_instrumentation_count("Rows Removed by Filter", 1, + planstate, es); + show_customscan_info((CustomScanState *) planstate, es); + break; case T_NestLoop: show_upper_qual(((NestLoop *) plan)->join.joinqual, "Join Filter", planstate, ancestors, es); @@ -1858,6 +1888,19 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es) } /* + * Show extra information for a CustomScan node. + */ +static void +show_customscan_info(CustomScanState *cstate, ExplainState *es) +{ + CustomProvider *provider = cstate->custom_provider; + + /* Let custom scan provider emit whatever fields it wants */ + if (provider->ExplainCustomScan != NULL) + provider->ExplainCustomScan(cstate, es); +} + +/* * Fetch the name of an index in an EXPLAIN * * We allow plugins to get control here so that plans involving hypothetical @@ -2025,6 +2068,41 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es) objectname = rte->ctename; objecttag = "CTE Name"; break; + case T_CustomScan: + if (rte->rtekind == RTE_RELATION) + { + objectname = get_rel_name(rte->relid); + if (es->verbose) + namespace = + get_namespace_name(get_rel_namespace(rte->relid)); + objecttag = "Relation Name"; + } + else if (rte->rtekind == RTE_JOIN) + { + objectname = rte->eref->aliasname; + objecttag = "Join Alias"; + } + else if (rte->rtekind == RTE_FUNCTION) + { + Node *funcexpr = ((CustomScan *) plan)->funcexpr; + + if (funcexpr && IsA(funcexpr, FuncExpr)) + { + Oid funcid = ((FuncExpr *) funcexpr)->funcid; + + objectname = get_func_name(funcid); + if (es->verbose) + namespace = + get_namespace_name(get_func_namespace(funcid)); + } + objecttag = "Function Name"; + } + else if (rte->rtekind == RTE_CTE) + { + objectname = rte->ctename; + objecttag = "CTE Name"; + } + break; default: break; } diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index 6081b56..4dece5a 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -15,7 +15,7 @@ include $(top_builddir)/src/Makefile.global OBJS = execAmi.o execCurrent.o execGrouping.o execJunk.o execMain.o \ execProcnode.o execQual.o execScan.o execTuples.o \ execUtils.o functions.o instrument.o nodeAppend.o nodeAgg.o \ - nodeBitmapAnd.o nodeBitmapOr.o \ + nodeBitmapAnd.o nodeBitmapOr.o nodeCustom.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \ nodeHashjoin.o nodeIndexscan.o nodeIndexonlyscan.o \ nodeLimit.o nodeLockRows.o \ diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index a078104..f80e6c4 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -21,6 +21,7 @@ #include "executor/nodeBitmapIndexscan.h" #include "executor/nodeBitmapOr.h" #include "executor/nodeCtescan.h" +#include "executor/nodeCustom.h" #include "executor/nodeForeignscan.h" #include "executor/nodeFunctionscan.h" #include "executor/nodeGroup.h" @@ -197,6 +198,10 @@ ExecReScan(PlanState *node) ExecReScanForeignScan((ForeignScanState *) node); break; + case T_CustomScanState: + ExecReScanCustomScan((CustomScanState *) node); + break; + case T_NestLoopState: ExecReScanNestLoop((NestLoopState *) node); break; @@ -291,6 +296,10 @@ ExecMarkPos(PlanState *node) ExecValuesMarkPos((ValuesScanState *) node); break; + case T_CustomScanState: + ExecCustomMarkPos((CustomScanState *) node); + break; + case T_MaterialState: ExecMaterialMarkPos((MaterialState *) node); break; @@ -348,6 +357,10 @@ ExecRestrPos(PlanState *node) ExecValuesRestrPos((ValuesScanState *) node); break; + case T_CustomScanState: + ExecCustomRestrPos((CustomScanState *) node); + break; + case T_MaterialState: ExecMaterialRestrPos((MaterialState *) node); break; @@ -379,9 +392,9 @@ ExecRestrPos(PlanState *node) * and valuesscan support is actually useless code at present.) */ bool -ExecSupportsMarkRestore(NodeTag plantype) +ExecSupportsMarkRestore(Path *path) { - switch (plantype) + switch (path->pathtype) { case T_SeqScan: case T_IndexScan: @@ -392,6 +405,14 @@ ExecSupportsMarkRestore(NodeTag plantype) case T_Sort: return true; + case T_CustomPath: + { + int flags = ((CustomPath *) path)->custom_flags; + if (flags & CUSTOM__SUPPORT_MARK_RESTORE) + return true; + return false; + } + case T_Result: /* @@ -465,6 +486,15 @@ ExecSupportsBackwardScan(Plan *node) return ExecSupportsBackwardScan(((SubqueryScan *) node)->subplan) && TargetListSupportsBackwardScan(node->targetlist); + case T_CustomScan: + { + int flags = ((CustomScan *) node)->custom_flags; + + if (flags & CUSTOM__SUPPORT_BACKWARD_SCAN) + return TargetListSupportsBackwardScan(node->targetlist); + } + return false; + case T_Material: case T_Sort: /* these don't evaluate tlist */ diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 76dd62f..b1110b9 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -85,6 +85,7 @@ #include "executor/nodeBitmapIndexscan.h" #include "executor/nodeBitmapOr.h" #include "executor/nodeCtescan.h" +#include "executor/nodeCustom.h" #include "executor/nodeForeignscan.h" #include "executor/nodeFunctionscan.h" #include "executor/nodeGroup.h" @@ -244,6 +245,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_CustomScan: + result = (PlanState *) ExecInitCustomScan((CustomScan *) node, + estate, eflags); + break; + /* * join nodes */ @@ -442,6 +448,10 @@ ExecProcNode(PlanState *node) result = ExecForeignScan((ForeignScanState *) node); break; + case T_CustomScanState: + result = ExecCustomScan((CustomScanState *) node); + break; + /* * join nodes */ @@ -678,6 +688,10 @@ ExecEndNode(PlanState *node) ExecEndForeignScan((ForeignScanState *) node); break; + case T_CustomScanState: + ExecEndCustomScan((CustomScanState *) node); + break; + /* * join nodes */ diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 90c2753..e60ac67 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -592,7 +592,7 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ @@ -680,7 +680,7 @@ ExecEvalScalarVarFast(ExprState *exprstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ @@ -732,7 +732,7 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ @@ -915,7 +915,7 @@ ExecEvalWholeRowFast(WholeRowVarExprState *wrvstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ @@ -991,7 +991,7 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext, slot = econtext->ecxt_outertuple; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: /* get the tuple from the relation being * scanned */ diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 39e3b2e..df0d295 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -578,7 +578,7 @@ ExecBuildProjectionInfo(List *targetList, projInfo->pi_lastOuterVar = attnum; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: varSlotOffsets[numSimpleVars] = offsetof(ExprContext, @@ -638,7 +638,7 @@ get_last_attnums(Node *node, ProjectionInfo *projInfo) projInfo->pi_lastOuterVar = attnum; break; - /* INDEX_VAR is handled by default case */ + /* INDEX_VAR and CUSTOM_VAR are handled by default case */ default: if (projInfo->pi_lastScanVar < attnum) diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c new file mode 100644 index 0000000..61bca22 --- /dev/null +++ b/src/backend/executor/nodeCustom.c @@ -0,0 +1,252 @@ +/* ------------------------------------------------------------------------ + * + * nodeCustom.c + * Routines to handle execution of custom plan, scan and join node + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------ + */ +#include "postgres.h" + +#include "executor/nodeCustom.h" +#include "parser/parsetree.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/rel.h" + +/* static variables */ +static HTAB *custom_provider_hash = NULL; + +/* + * register_custom_provider + * + * It registers a custom execution provider; that consists of a set of + * callbacks and is identified with a unique name. + */ +void +register_custom_provider(const CustomProvider *provider) +{ + CustomProvider *entry; + bool found; + + if (!custom_provider_hash) + { + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.hcxt = CacheMemoryContext; + ctl.keysize = NAMEDATALEN; + ctl.entrysize = sizeof(CustomProvider); + + custom_provider_hash = hash_create("custom execution providers", + 32, + &ctl, + HASH_ELEM | HASH_CONTEXT); + } + + entry = hash_search(custom_provider_hash, + provider->name, + HASH_ENTER, &found); + if (found) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("duplicate custom execution provider \"%s\"", + provider->name))); + + Assert(strcmp(provider->name, entry->name) == 0); + memcpy(entry, provider, sizeof(CustomProvider)); +} + +/* + * get_custom_provider + * + * It finds a registered custom execution provide by its name + */ +CustomProvider * +get_custom_provider(const char *custom_name) +{ + CustomProvider *entry; + + /* lookup custom execution provider */ + if (!custom_provider_hash) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no custom execution provider was registered"))); + + entry = (CustomProvider *) hash_search(custom_provider_hash, + custom_name, HASH_FIND, NULL); + if (!entry) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("custom execution provider \"%s\" was not registered", + custom_name))); + + return entry; +} + +/* + * ExecInitCustomScan + * + * Allocation of CustomScanState and various initialization stuff. + * Note that some of initialization jobs are skipped if scanrelid is zero + * (that means this custom scan plan is not associated with a particular + * relation in range-table list.) + */ +CustomScanState * +ExecInitCustomScan(CustomScan *node, EState *estate, int eflags) +{ + CustomProvider *provider = get_custom_provider(node->custom_name); + CustomScanState *csstate; + Plan *plan = &node->scan.plan; + Index scanrelid = node->scan.scanrelid; + + /* + * Create state structure + */ + csstate = makeNode(CustomScanState); + csstate->ss.ps.plan = plan; + csstate->ss.ps.state = estate; + csstate->custom_provider = provider; + csstate->custom_flags = node->custom_flags; + csstate->custom_state = NULL; + + /* + * Miscellaneous initialization + */ + ExecAssignExprContext(estate, &csstate->ss.ps); + + /* + * Initialization of child expressions + */ + csstate->ss.ps.targetlist = + (List *) ExecInitExpr((Expr *) plan->targetlist, &csstate->ss.ps); + csstate->ss.ps.qual = + (List *) ExecInitExpr((Expr *) plan->qual, &csstate->ss.ps); + + /* + * tuple table initialization + * + * Note that ss_ScanTupleSlot is set only when scanrelid is associated + * with a particular relation. Elsewhere, it needs to be initialized by + * custom-scan provider itself if it internally uses ss_ScanTupleSlot. + * If it replaces varno of Var node by CUSTOM_VAR, it has to be set to + * reference underlying attribute name to generate EXPLAIN output. + */ + ExecInitResultTupleSlot(estate, &csstate->ss.ps); + if (scanrelid > 0) + ExecInitScanTupleSlot(estate, &csstate->ss); + + /* + * open the base relation and acquire appropriate lock on it, + * if this custom scan is connected with a particular relaion. + * Also, assign its scan type according to the table definition. + */ + if (scanrelid > 0) + { + Relation rel = ExecOpenScanRelation(estate, scanrelid, eflags); + + csstate->ss.ss_currentRelation = rel; + ExecAssignScanType(&csstate->ss, RelationGetDescr(rel)); + + csstate->ss.ps.ps_TupFromTlist = false; + } + + /* + * Initialize result tuple type and projection info. + */ + ExecAssignResultTypeFromTL(&csstate->ss.ps); + + if (scanrelid > 0) + ExecAssignScanProjectionInfo(&csstate->ss); + else + ExecAssignProjectionInfo(&csstate->ss.ps, NULL); + + /* + * Final initialization based on callback of BeginCustomScan method. + * Extension may be able to override initialization stuff above, if + * needed. + */ + csstate->custom_provider->BeginCustomScan(csstate, eflags); + + return csstate; +} + +/* + * ExecCustomScan + * + * Just an entrypoint of ExecCustomScan method. All the stuff to fetch + * a tuple is a job of custom-scan provider. + */ +TupleTableSlot * +ExecCustomScan(CustomScanState *csstate) +{ + return csstate->custom_provider->ExecCustomScan(csstate); +} + +/* + * MultiExecCustomScan + * + * Aldo, just an entrypoint of MultiExecCustomScan method. All the stuff + * to fetch multiple tuples (according to expectation of upper node) is + * a job of custom-scan provider. + */ +Node * +MultiExecCustomScan(CustomScanState *csstate) +{ + return csstate->custom_provider->MultiExecCustomScan(csstate); +} + +/* + * ExecEndCustomScan + * + * It releases all the resources allocated on this scan. + */ +void +ExecEndCustomScan(CustomScanState *csstate) +{ + /* Let the custom-exec shut down */ + csstate->custom_provider->EndCustomScan(csstate); + + /* Free the exprcontext */ + ExecFreeExprContext(&csstate->ss.ps); + + /* Clean out the tuple table, if exists */ + ExecClearTuple(csstate->ss.ps.ps_ResultTupleSlot); + if (csstate->ss.ss_ScanTupleSlot) + ExecClearTuple(csstate->ss.ss_ScanTupleSlot); + + /* close the relation, if opened */ + if (csstate->ss.ss_currentRelation) + ExecCloseScanRelation(csstate->ss.ss_currentRelation); +} + +/* + * ExecReScanCustomScan + */ +void +ExecReScanCustomScan(CustomScanState *csstate) +{ + csstate->custom_provider->ReScanCustomScan(csstate); +} + +/* + * ExecCustomMarkPos + */ +void +ExecCustomMarkPos(CustomScanState *csstate) +{ + Assert((csstate->custom_flags & CUSTOM__SUPPORT_MARK_RESTORE) != 0); + csstate->custom_provider->ExecMarkPosCustomScan(csstate); +} + +/* + * ExecCustomRestrPos + */ +void +ExecCustomRestrPos(CustomScanState *csstate) +{ + Assert((csstate->custom_flags & CUSTOM__SUPPORT_MARK_RESTORE) != 0); + csstate->custom_provider->ExecRestorePosCustom(csstate); +} diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c index 540db16..44f2236 100644 --- a/src/backend/nodes/bitmapset.c +++ b/src/backend/nodes/bitmapset.c @@ -865,3 +865,65 @@ bms_hash_value(const Bitmapset *a) return DatumGetUInt32(hash_any((const unsigned char *) a->words, (lastword + 1) * sizeof(bitmapword))); } + +/* + * bms_to_string / bms_from_string - transform bitmapset to/from text + * representation for portability purpose. + */ +char * +bms_to_string(Bitmapset *a) +{ + char *result; + char *pos; + int i; + + if (bms_is_empty(a)) + return NULL; + + result = palloc(a->nwords * (BITS_PER_BITMAPWORD / 4) + 1); + for (i = a->nwords, pos = result; i > 0; i--) + pos += sprintf(pos, "%08x", a->words[i - 1]); + + return result; +} + +Bitmapset * +bms_from_string(const char *a) +{ + Bitmapset *result; + Size len; + int nwords; + int i, offset = 0; + + if (a == NULL) + return NULL; + + len = strlen(a); + if (len % (BITS_PER_BITMAPWORD / 4) != 0) + elog(WARNING, "strange bitmapset text representation: %s", a); + + nwords = (len + BITS_PER_BITMAPWORD / 4 - 1) / (BITS_PER_BITMAPWORD / 4); + result = palloc(BITMAPSET_SIZE(nwords)); + result->nwords = nwords; + + for (i=result->nwords; i > 0; i--) + { + bitmapword word = 0; + + do { + int c = a[offset++]; + if (c >= '0' && c <= '9') + word = (word << 4) | (c - '0'); + else if (c >= 'a' && c <= 'f') + word = (word << 4) | (c - 'a'); + else if (c >= 'A' && c <= 'F') + word = (word << 4) | (c - 'A'); + else + elog(ERROR, "invalid hexadecimal digit"); + } while ((len - offset) % (BITS_PER_BITMAPWORD / 4) != 0); + + result->words[i - 1] = word; + } + + return result; +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 65f3b98..b56af14 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -602,6 +602,33 @@ _copyForeignScan(const ForeignScan *from) } /* + * _copyCustomScan + */ +static CustomScan * +_copyCustomScan(const CustomScan *from) +{ + CustomScan *newnode = makeNode(CustomScan); + + /* + * copy node superclass fields + */ + CopyScanFields((const Scan *) from, (Scan *) newnode); + + /* + * copy remainder of node + */ + COPY_STRING_FIELD(custom_name); + COPY_SCALAR_FIELD(custom_flags); + COPY_NODE_FIELD(custom_private); + COPY_NODE_FIELD(custom_exprs); + + COPY_NODE_FIELD(subqry_plan); + COPY_NODE_FIELD(funcexpr); + + return newnode; +} + +/* * CopyJoinFields * * This function copies the fields of the Join node. It is used by @@ -3929,6 +3956,9 @@ copyObject(const void *from) case T_ForeignScan: retval = _copyForeignScan(from); break; + case T_CustomScan: + retval = _copyCustomScan(from); + break; case T_Join: retval = _copyJoin(from); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 817b149..031112b 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -568,6 +568,22 @@ _outForeignScan(StringInfo str, const ForeignScan *node) } static void +_outCustomScan(StringInfo str, const CustomScan *node) +{ + WRITE_NODE_TYPE("CUSTOMSCAN"); + + _outScanInfo(str, (const Scan *) node); + + WRITE_STRING_FIELD(custom_name); + WRITE_INT_FIELD(custom_flags); + WRITE_NODE_FIELD(custom_private); + WRITE_NODE_FIELD(custom_exprs); + + WRITE_NODE_FIELD(subqry_plan); + WRITE_NODE_FIELD(funcexpr); +} + +static void _outJoin(StringInfo str, const Join *node) { WRITE_NODE_TYPE("JOIN"); @@ -2814,6 +2830,9 @@ _outNode(StringInfo str, const void *obj) case T_ForeignScan: _outForeignScan(str, obj); break; + case T_CustomScan: + _outCustomScan(str, obj); + break; case T_Join: _outJoin(str, obj); break; diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c index 48ef325..29fcba9 100644 --- a/src/backend/nodes/print.c +++ b/src/backend/nodes/print.c @@ -333,6 +333,10 @@ print_expr(const Node *expr, const List *rtable) relname = "INDEX"; attname = "?"; break; + case CUSTOM_VAR: + relname = "CUSTOM"; + attname = "?"; + break; default: { RangeTblEntry *rte; diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index bfd3809..9d0cbf5 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -46,6 +46,8 @@ int geqo_threshold; /* Hook for plugins to replace standard_join_search() */ join_search_hook_type join_search_hook = NULL; +/* Hook for plugins to add custom scan paths */ +add_scan_path_hook_type add_scan_path_hook = NULL; static void set_base_rel_sizes(PlannerInfo *root); static void set_base_rel_pathlists(PlannerInfo *root); @@ -399,6 +401,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Consider TID scans */ create_tidscan_paths(root, rel); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Now find the cheapest of the paths for this rel */ set_cheapest(rel); } @@ -427,6 +432,9 @@ set_foreign_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Call the FDW's GetForeignPaths function to generate path(s) */ rel->fdwroutine->GetForeignPaths(root, rel, rte->relid); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path */ set_cheapest(rel); } @@ -1246,6 +1254,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, /* Generate appropriate path */ add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } @@ -1269,6 +1280,9 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_functionscan_path(root, rel, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } @@ -1292,6 +1306,9 @@ set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_valuesscan_path(root, rel, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } @@ -1361,6 +1378,9 @@ set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_ctescan_path(root, rel, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } @@ -1414,6 +1434,9 @@ set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) /* Generate appropriate path */ add_path(rel, create_worktablescan_path(root, rel, required_outer)); + /* Consider Custom scans */ + add_custom_scan_paths(root,rel,rte); + /* Select cheapest path (pretty easy in this case...) */ set_cheapest(rel); } diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index e7f8cec..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); @@ -2312,7 +2309,7 @@ final_cost_mergejoin(PlannerInfo *root, MergePath *path, * it off does not entitle us to deliver an invalid plan. */ else if (innersortkeys == NIL && - !ExecSupportsMarkRestore(inner_path->pathtype)) + !ExecSupportsMarkRestore(inner_path)) path->materialize_inner = true; /* @@ -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/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index 5b477e5..9483614 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -21,6 +21,8 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" +/* Hook for plugins to add custom join paths */ +add_join_path_hook_type add_join_path_hook = NULL; #define PATH_PARAM_BY_REL(path, rel) \ ((path)->param_info && bms_overlap(PATH_REQ_OUTER(path), (rel)->relids)) @@ -259,6 +261,22 @@ add_paths_to_joinrel(PlannerInfo *root, restrictlist, jointype, sjinfo, &semifactors, param_source_rels, extra_lateral_rels); + + /* + * 5. Also consider paths being provided with custom execution provider. + */ + if (add_join_path_hook) + (*add_join_path_hook)(root, + joinrel, + outerrel, + innerrel, + jointype, + sjinfo, + restrictlist, + mergeclause_list, + &semifactors, + param_source_rels, + extra_lateral_rels); } /* diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 9b9eb2f..9626d08 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -21,6 +21,7 @@ #include "access/skey.h" #include "catalog/pg_class.h" +#include "executor/nodeCustom.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -77,6 +78,9 @@ static WorkTableScan *create_worktablescan_plan(PlannerInfo *root, Path *best_pa List *tlist, List *scan_clauses); static ForeignScan *create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, List *tlist, List *scan_clauses); +static CustomScan *create_customscan_plan(PlannerInfo *root, + CustomPath *best_path, + List *tlist, List *scan_clauses); static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, Plan *outer_plan, Plan *inner_plan); static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, @@ -235,6 +239,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path) case T_CteScan: case T_WorkTableScan: case T_ForeignScan: + case T_CustomScan: plan = create_scan_plan(root, best_path); break; case T_HashJoin: @@ -411,6 +416,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path) scan_clauses); break; + case T_CustomScan: + plan = (Plan *) create_customscan_plan(root, + (CustomPath *) best_path, + tlist, + scan_clauses); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) best_path->pathtype); @@ -2016,6 +2028,97 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path, return scan_plan; } +/* + * create_customscan_plan + * Returns a custom-scan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static CustomScan * +create_customscan_plan(PlannerInfo *root, + CustomPath *best_path, + List *tlist, + List *scan_clauses) +{ + CustomProvider *provider = get_custom_provider(best_path->custom_name); + CustomScan *scan_plan = makeNode(CustomScan); + RelOptKind reloptkind = best_path->path.parent->reloptkind; + RangeTblEntry *rte; + Index scan_relid; + + if (reloptkind == RELOPT_BASEREL || + reloptkind == RELOPT_OTHER_MEMBER_REL) + { + scan_relid = best_path->path.parent->relid; + + rte = planner_rt_fetch(scan_relid, root); + /* + * For EXPLAIN output, we save various information in CustomScan plan + * structure. Custom-scan provider can utilize them, but it is not + * recommendablt to adjust. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + if (best_path->path.param_info) + { + List *subplan_params + = best_path->path.parent->subplan_params; + process_subquery_nestloop_params(root, subplan_params); + } + scan_plan->subqry_plan = best_path->path.parent->subplan; + } + else if (rte->rtekind == RTE_FUNCTION) + { + Node *funcexpr = rte->funcexpr; + + if (best_path->path.param_info) + funcexpr = replace_nestloop_params(root, funcexpr); + scan_plan->funcexpr = funcexpr; + } + } + else if (reloptkind == RELOPT_JOINREL) + scan_relid = 0; + else + elog(ERROR, "unexpected reloptkind: %d", (int)reloptkind); + + scan_clauses = order_qual_clauses(root, scan_clauses); + scan_plan->scan.plan.targetlist = NULL; /* to be set by callback */ + scan_plan->scan.plan.qual = NULL; /* to be set by callback */ + scan_plan->scan.plan.lefttree = NULL; + scan_plan->scan.plan.righttree = NULL; + scan_plan->scan.scanrelid = scan_relid; + + scan_plan->custom_name = pstrdup(best_path->custom_name); + scan_plan->custom_flags = best_path->custom_flags; + scan_plan->custom_private = NIL; + scan_plan->custom_exprs = NULL; + + /* + * Let custom scan provider perform to set up this custom-scan plan + * according to the given path information. + */ + provider->InitCustomScanPlan(root, scan_plan, + best_path, tlist, scan_clauses); + + /* Copy cost data from Path to Plan; no need to make callback do this */ + copy_path_costsize(&scan_plan->scan.plan, &best_path->path); + + /* + * Replace any outer-relation variables with nestloop params in the qual + * and custom_exprs expressions. We do this last so that the FDW doesn't + * have to be involved. (Note that parts of custom_exprs could have come + * from join clauses, so doing this beforehand on the scan_clauses + * wouldn't work.) + */ + if (best_path->path.param_info) + { + scan_plan->scan.plan.qual = (List *) + replace_nestloop_params(root, (Node *) scan_plan->scan.plan.qual); + scan_plan->custom_exprs = (List *) + replace_nestloop_params(root, (Node *) scan_plan->custom_exprs); + } + + return scan_plan; +} /***************************************************************************** * diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index b78d727..30cf7e5 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -17,6 +17,7 @@ #include "access/transam.h" #include "catalog/pg_type.h" +#include "executor/nodeCustom.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/pathnode.h" @@ -578,6 +579,30 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) } break; + case T_CustomScan: + { + CustomScan *splan = (CustomScan *) plan; + CustomProvider *provider + = get_custom_provider(splan->custom_name); + + if (provider->SetPlanRefCustomScan) + provider->SetPlanRefCustomScan(root, splan, rtoffset); + else if (splan->scan.scanrelid > 0) + { + splan->scan.scanrelid += rtoffset; + splan->scan.plan.targetlist = + fix_scan_list(root, splan->scan.plan.targetlist, + rtoffset); + splan->scan.plan.qual = + fix_scan_list(root, splan->scan.plan.qual, rtoffset); + splan->custom_exprs = + fix_scan_list(root, splan->custom_exprs, rtoffset); + } + else + elog(ERROR, "No implementation to set plan reference"); + } + break; + case T_NestLoop: case T_MergeJoin: case T_HashJoin: @@ -1059,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/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 0df70c4..644a532 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2194,6 +2194,16 @@ finalize_plan(PlannerInfo *root, Plan *plan, Bitmapset *valid_params, context.paramids = bms_add_members(context.paramids, scan_params); break; + case T_CustomScan: + finalize_primnode((Node *) ((CustomScan *) plan)->custom_exprs, + &context); + context.paramids = bms_add_members(context.paramids, scan_params); + /* + * XXX - Is it sufficient to do? Don't we need something special + * if CustomScan override FunctionScan or SubqueryScan. + */ + break; + case T_ModifyTable: { ModifyTable *mtplan = (ModifyTable *) plan; diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 64b17051..46e814d 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -1738,6 +1738,46 @@ create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, } /* + * create_customscan_path + * Creates a path corresponding to a scan of a relation based on logic + * logic being provided by extensions. + * + * This function is never called from core PostgreSQL. An usual usage is + * invocation from callbacks on add_scan_path_hook. We don't have any + * assumption on the custom scan logic, thus, caller is responsible to + * set adequate cost estimation here. + */ +CustomPath * +create_customscan_path(PlannerInfo *root, + RelOptInfo *baserel, + double rows, + Cost startup_cost, + Cost total_cost, + List *pathkeys, + Relids required_outer, + const char *custom_name, + uint32 custom_flags, + List *custom_private) +{ + CustomPath *pathnode = makeNode(CustomPath); + + pathnode->path.pathtype = T_CustomScan; + pathnode->path.parent = baserel; + pathnode->path.param_info = get_baserel_parampathinfo(root, baserel, + required_outer); + pathnode->path.rows = rows; + pathnode->path.startup_cost = startup_cost; + pathnode->path.total_cost = total_cost; + pathnode->path.pathkeys = pathkeys; + + pathnode->custom_name = pstrdup(custom_name); + pathnode->custom_flags = custom_flags; + pathnode->custom_private = custom_private; + + return pathnode; +} + +/* * calc_nestloop_required_outer * Compute the required_outer set for a nestloop join path * diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 04b1c4f..eda53d6 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -143,6 +143,7 @@ typedef struct List *outer_tlist; /* referent for OUTER_VAR Vars */ List *inner_tlist; /* referent for INNER_VAR Vars */ List *index_tlist; /* referent for INDEX_VAR Vars */ + TupleDesc custom_tupdesc; /* referent for CUSTOM_VAR Vars */ } deparse_namespace; /* @@ -2362,14 +2363,19 @@ deparse_context_for(const char *aliasname, Oid relid) * deparse_context_for_planstate - Build deparse context for a plan * * When deparsing an expression in a Plan tree, we might have to resolve - * OUTER_VAR, INNER_VAR, or INDEX_VAR references. To do this, the caller must - * provide the parent PlanState node. Then OUTER_VAR and INNER_VAR references - * can be resolved by drilling down into the left and right child plans. + * special varno (OUTER_VAR, INNER_VAR, INDEX_VAR or CUSTOM_VAR) references. + * To do this, the caller must provide the parent PlanState node. Then + * OUTER_VAR and INNER_VAR references can be resolved by drilling down into + * the left and right child plans. * Similarly, INDEX_VAR references can be resolved by reference to the * indextlist given in the parent IndexOnlyScan node. (Note that we don't * currently support deparsing of indexquals in regular IndexScan or * BitmapIndexScan nodes; for those, we can only deparse the indexqualorig * fields, which won't contain INDEX_VAR Vars.) + * Also, CUSTOM_VAR references can be resolved by reference to the TupleDesc + * of ss_ScanTupleSlot in CustomScanState node. (Note that custom scan + * provider must be responsible to initialize the ss_ScanTupleSlot with + * appropriate TupleDesc; being likely constructed by ExecTypeFromTL). * * Note: planstate really ought to be declared as "PlanState *", but we use * "Node *" to avoid having to include execnodes.h in builtins.h. @@ -3627,6 +3633,14 @@ set_deparse_planstate(deparse_namespace *dpns, PlanState *ps) dpns->index_tlist = ((IndexOnlyScan *) ps->plan)->indextlist; else dpns->index_tlist = NIL; + + /* custom_tupdesc is set only if it's an CustomScan */ + if (IsA(ps, CustomScanState) && + ((CustomScanState *)ps)->ss.ss_ScanTupleSlot) + dpns->custom_tupdesc = + ((CustomScanState *)ps)->ss.ss_ScanTupleSlot->tts_tupleDescriptor; + else + dpns->custom_tupdesc = NULL; } /* @@ -5294,6 +5308,18 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context) return NULL; } + else if (var->varno == CUSTOM_VAR && dpns->custom_tupdesc) + { + TupleDesc tupdesc = dpns->custom_tupdesc; + + Assert(netlevelsup == 0); + Assert(var->varattno > 0 && var->varattno <= tupdesc->natts); + + attname = NameStr(tupdesc->attrs[var->varattno - 1]->attname); + appendStringInfoString(buf, quote_identifier(attname)); + + return attname; + } else { elog(ERROR, "bogus varno: %d", var->varno); @@ -5564,6 +5590,18 @@ get_name_for_var_field(Var *var, int fieldno, return result; } + else if (var->varno == CUSTOM_VAR && dpns->custom_tupdesc) + { + TupleDesc tupdesc = dpns->custom_tupdesc; + const char *result; + + Assert(netlevelsup == 0); + Assert(var->varattno > 0 && var->varattno <= tupdesc->natts); + + result = NameStr(tupdesc->attrs[var->varattno - 1]->attname); + + return result; + } else { elog(ERROR, "bogus varno: %d", var->varno); 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/executor/executor.h b/src/include/executor/executor.h index 75841c8..51537d2 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -16,6 +16,7 @@ #include "executor/execdesc.h" #include "nodes/parsenodes.h" +#include "nodes/relation.h" /* @@ -102,7 +103,7 @@ extern PGDLLIMPORT ExecutorCheckPerms_hook_type ExecutorCheckPerms_hook; extern void ExecReScan(PlanState *node); extern void ExecMarkPos(PlanState *node); extern void ExecRestrPos(PlanState *node); -extern bool ExecSupportsMarkRestore(NodeTag plantype); +extern bool ExecSupportsMarkRestore(Path *path); extern bool ExecSupportsBackwardScan(Plan *node); extern bool ExecMaterializesOutput(NodeTag plantype); diff --git a/src/include/executor/nodeCustom.h b/src/include/executor/nodeCustom.h new file mode 100644 index 0000000..8ea5693 --- /dev/null +++ b/src/include/executor/nodeCustom.h @@ -0,0 +1,94 @@ +/* ------------------------------------------------------------------------ + * + * nodeCustom.h + * + * prototypes for CustomScan nodes + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * ------------------------------------------------------------------------ + */ +#ifndef NODECUSTOM_H +#define NODECUSTOM_H +#include "commands/explain.h" +#include "nodes/plannodes.h" +#include "nodes/relation.h" + +/* + * Definition of the custom execution provider callbacks + */ +typedef void (*InitCustomScanPlan_function)(PlannerInfo *root, + CustomScan *cscan_plan, + CustomPath *cscan_path, + List *tlist, + List *scan_clauses); +typedef void (*SetPlanRefCustomScan_function)(PlannerInfo *root, + CustomScan *cscan_plan, + int rtoffset); +typedef void (*BeginCustomScan_function)(CustomScanState *csstate, int eflags); +typedef TupleTableSlot *(*ExecCustomScan_function)(CustomScanState *csstate); +typedef Node *(*MultiExecCustomScan_function)(CustomScanState *csstate); +typedef void (*EndCustomScan_function)(CustomScanState *csstate); + +typedef void (*ReScanCustomScan_function)(CustomScanState *csstate); +typedef void (*ExecMarkPosCustomScan_function)(CustomScanState *csstate); +typedef void (*ExecRestorePosCustom_function)(CustomScanState *csstate); + +typedef void (*ExplainCustomScan_function)(CustomScanState *csstate, + ExplainState *es); + +typedef struct CustomProvider +{ + char name[NAMEDATALEN]; + + InitCustomScanPlan_function InitCustomScanPlan; + SetPlanRefCustomScan_function SetPlanRefCustomScan; + + BeginCustomScan_function BeginCustomScan; + ExecCustomScan_function ExecCustomScan; + MultiExecCustomScan_function MultiExecCustomScan; + EndCustomScan_function EndCustomScan; + + ReScanCustomScan_function ReScanCustomScan; + ExecMarkPosCustomScan_function ExecMarkPosCustomScan; + ExecRestorePosCustom_function ExecRestorePosCustom; + + ExplainCustomScan_function ExplainCustomScan; +} CustomProvider; + +/* Flags of CustomScan */ + +/* + * CUSTOM__SUPPORT_MARK_RESTORE informs optimizer this custom scan provider + * support ExecCustomMarkPos and ExecCustomRestrPos callbacks. + */ +#define CUSTOM__SUPPORT_MARK_RESTORE 0x0001 + +/* + * CUSTOM__SUPPORT_BACKWARD_SCAN informs optimizer this custom scan provider + * is designed to support backward scan. + */ +#define CUSTOM__SUPPORT_BACKWARD_SCAN 0x0002 + +/* + * Registration and lookup custom execution provider + */ +extern void register_custom_provider(const CustomProvider *provider); + +extern CustomProvider *get_custom_provider(const char *custom_name); + +/* + * General executor code + */ +extern CustomScanState *ExecInitCustomScan(CustomScan *csstate, + EState *estate, int eflags); +extern TupleTableSlot *ExecCustomScan(CustomScanState *csstate); +extern Node *MultiExecCustomScan(CustomScanState *csstate); +extern void ExecEndCustomScan(CustomScanState *csstate); + +extern void ExecReScanCustomScan(CustomScanState *csstate); +extern void ExecCustomMarkPos(CustomScanState *csstate); +extern void ExecCustomRestrPos(CustomScanState *csstate); + +#endif /* NODECUSTOM_H */ diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h index 2a4b41d..73424f5 100644 --- a/src/include/nodes/bitmapset.h +++ b/src/include/nodes/bitmapset.h @@ -93,4 +93,8 @@ extern int bms_first_member(Bitmapset *a); /* support for hashtables using Bitmapsets as keys: */ extern uint32 bms_hash_value(const Bitmapset *a); +/* support for string representation */ +extern char *bms_to_string(Bitmapset *a); +extern Bitmapset *bms_from_string(const char *a); + #endif /* BITMAPSET_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 3b430e0..db4176c 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1494,6 +1494,23 @@ typedef struct ForeignScanState void *fdw_state; /* foreign-data wrapper can keep state here */ } ForeignScanState; +/* ---------------- + * CustomScanState information + * + * CustomScan nodes are used to scan various relations using custom + * logic. + * ---------------- + */ +typedef struct CustomScanState +{ + ScanState ss; + + /* use struct pointer to avoid including nodeCustom.h here */ + struct CustomProvider *custom_provider; + int custom_flags; + void *custom_state; +} CustomScanState; + /* ---------------------------------------------------------------- * Join State Information * ---------------------------------------------------------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 78368c6..8f00a6b 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -62,6 +62,7 @@ typedef enum NodeTag T_CteScan, T_WorkTableScan, T_ForeignScan, + T_CustomScan, T_Join, T_NestLoop, T_MergeJoin, @@ -107,6 +108,7 @@ typedef enum NodeTag T_CteScanState, T_WorkTableScanState, T_ForeignScanState, + T_CustomScanState, T_JoinState, T_NestLoopState, T_MergeJoinState, @@ -224,6 +226,7 @@ typedef enum NodeTag T_HashPath, T_TidPath, T_ForeignPath, + T_CustomPath, T_AppendPath, T_MergeAppendPath, T_ResultPath, diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 44ea0b7..936591b 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -483,6 +483,22 @@ typedef struct ForeignScan bool fsSystemCol; /* true if any "system column" is needed */ } ForeignScan; +/* ---------------- + * CustomScan node + * ---------------- + */ +typedef struct CustomScan +{ + Scan scan; + + const char *custom_name; /* name of custom scan provider */ + int custom_flags; /* a set of CUSTOM__* flags */ + List *custom_private; /* private data for CSP */ + List *custom_exprs; /* expressions that CSP may execute */ + + Plan *subqry_plan; /* valid, if RTE_SUBQUERY */ + Node *funcexpr; /* valid, if RTE_FUNCTION */ +} CustomScan; /* * ========== diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 7918537..b71c7ca 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -134,6 +134,7 @@ typedef struct Expr #define INNER_VAR 65000 /* reference to inner subplan */ #define OUTER_VAR 65001 /* reference to outer subplan */ #define INDEX_VAR 65002 /* reference to index column */ +#define CUSTOM_VAR 65003 /* reference to custom column */ #define IS_SPECIAL_VARNO(varno) ((varno) >= INNER_VAR) diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index a2853fb..55fa8aa 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -867,6 +867,22 @@ typedef struct ForeignPath } ForeignPath; /* + * CustomPath represents a scan using custom logic + * + * custom_name is the identifier of custom scan provider when it was + * registered. custom_flags is a set of CUSTOM__* bits to control its + * behavior. custom_private allows extension to store its private data + * but has to be safe for copyObject(). + */ +typedef struct CustomPath +{ + Path path; + const char *custom_name; /* name of custom scan provider */ + int custom_flags; /* CUSTOM__* flags in nodeCustom.h */ + List *custom_private; /* can be used for private data */ +} CustomPath; + +/* * AppendPath represents an Append plan, ie, successive execution of * several member plans. * 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/pathnode.h b/src/include/optimizer/pathnode.h index 9686229..1225970 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -82,6 +82,16 @@ extern ForeignPath *create_foreignscan_path(PlannerInfo *root, RelOptInfo *rel, List *pathkeys, Relids required_outer, List *fdw_private); +extern CustomPath *create_customscan_path(PlannerInfo *root, + RelOptInfo *baserel, + double rows, + Cost startup_cost, + Cost total_cost, + List *pathkeys, + Relids required_outer, + const char *custom_name, + uint32 custom_flags, + List *custom_private); extern Relids calc_nestloop_required_outer(Path *outer_path, Path *inner_path); extern Relids calc_non_nestloop_required_outer(Path *outer_path, Path *inner_path); diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 9ef93c7..882baf6 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -29,6 +29,31 @@ typedef RelOptInfo *(*join_search_hook_type) (PlannerInfo *root, List *initial_rels); extern PGDLLIMPORT join_search_hook_type join_search_hook; +/* Hook for plugins to add custom scan path, in addition to default ones */ +typedef void (*add_scan_path_hook_type)(PlannerInfo *root, + RelOptInfo *baserel, + RangeTblEntry *rte); +extern PGDLLIMPORT add_scan_path_hook_type add_scan_path_hook; + +#define add_custom_scan_paths(root,baserel,rte) \ + do { \ + if (add_scan_path_hook) \ + (*add_scan_path_hook)((root),(baserel),(rte)); \ + } while(0) + +/* Hook for plugins to add custom join path, in addition to default ones */ +typedef void (*add_join_path_hook_type)(PlannerInfo *root, + RelOptInfo *joinrel, + RelOptInfo *outerrel, + RelOptInfo *innerrel, + JoinType jointype, + SpecialJoinInfo *sjinfo, + List *restrictlist, + List *mergeclause_list, + SemiAntiJoinFactors *semifactors, + Relids param_source_rels, + Relids extra_lateral_rels); +extern PGDLLIMPORT add_join_path_hook_type add_join_path_hook; extern RelOptInfo *make_one_rel(PlannerInfo *root, List *joinlist); extern RelOptInfo *standard_join_search(PlannerInfo *root, int levels_needed, 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 1c1491c..fe81929 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 c4d451a..a2287d8 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