ctidscan as an example of custom-scan (Re: [v9.5] Custom Plan API)
This attached patch adds a code example of custom-scan interface.
This custom-scan provider ("ctidscan") performs almost same as
built-in SeqScan plan, but can produce same results with less
page scan in case when qualifier of relation scan has inequality
operators (>, >=, < or <=) on "ctid" system column, therefore,
range to be scanned is less than case of full-table scan.
Below is an example:
postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(10,0)'::tid AND b like '%abc%';
QUERY PLAN
-------------------------------------------------------------
Seq Scan on t1 (cost=0.00..10.00 rows=1 width=37)
Filter: ((ctid > '(10,0)'::tid) AND (b ~~ '%abc%'::text))
(2 rows)
Once ctidscan is loaded, it can provide cheaper cost to scan
the "t1" table than SeqScan, so planner chooses the custom logic.
postgres=# LOAD 'ctidscan';
LOAD
postgres=# EXPLAIN SELECT * FROM t1 WHERE ctid > '(10,0)'::tid AND b like '%abc%';
QUERY PLAN
-----------------------------------------------------------------
Custom Scan (ctidscan) on t1 (cost=0.00..5.00 rows=1 width=37)
Filter: ((ctid > '(10,0)'::tid) AND (b ~~ '%abc%'::text))
ctid quals: (ctid > '(10,0)'::tid)
(3 rows)
Like other query execution logic, it also provides "enable_ctidscan"
parameter to turn on/off this feature.
I'm not certain whether we should have this functionality in contrib
from the perspective of workload that can help, but its major worth is
for an example of custom-scan interface.
Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
Show quoted text
-----Original Message-----
From: Robert Haas [mailto:robertmhaas@gmail.com]
Sent: Thursday, December 11, 2014 11:58 AM
To: Simon Riggs
Cc: Kaigai Kouhei(海外 浩平); Thom Brown; Kohei KaiGai; Tom Lane; Alvaro
Herrera; Shigeru Hanada; Stephen Frost; Andres Freund; PgHacker; Jim
Mlodgenski; Peter Eisentraut
Subject: ##freemail## Re: [HACKERS] [v9.5] Custom Plan APIOn Tue, Dec 9, 2014 at 3:24 AM, Simon Riggs <simon@2ndquadrant.com> wrote:
Feedback I am receiving is that the API is unusable. That could be
because it is impenetrable, or because it is unusable. I'm not sure it
matters which.It would be nice to here what someone is trying to use it for and what problems
that person is encountering. Without that, it's pretty much impossible
for anyone to fix anything.As for sample code, KaiGai had a working example, which of course got broken
when Tom changed the API, but it didn't look to me like Tom's changes would
have made anything impossible that was possible before.
I'm frankly kind of astonished by the tenor of this entire conversation;
there is certainly plenty of code in the backend that is less
self-documenting than this is; and KaiGai did already put up a wiki page
with documentation as you requested. From his response, it sounds like
he has updated the ctidscan code, too.--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL
Company
Attachments:
pgsql-v9.5-contrib-ctidscan.v1.patchapplication/octet-stream; name=pgsql-v9.5-contrib-ctidscan.v1.patchDownload
contrib/ctidscan/Makefile | 16 +
contrib/ctidscan/ctidscan.c | 808 +++++++++++++++++++++++++++++++++
contrib/ctidscan/expected/ctidscan.out | 332 ++++++++++++++
contrib/ctidscan/sql/ctidscan.sql | 59 +++
doc/src/sgml/contrib.sgml | 1 +
doc/src/sgml/ctidscan.sgml | 52 +++
doc/src/sgml/filelist.sgml | 1 +
7 files changed, 1269 insertions(+)
diff --git a/contrib/ctidscan/Makefile b/contrib/ctidscan/Makefile
new file mode 100644
index 0000000..a6ed348
--- /dev/null
+++ b/contrib/ctidscan/Makefile
@@ -0,0 +1,16 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+REGRESS = ctidscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/ctidscan
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
\ No newline at end of file
diff --git a/contrib/ctidscan/ctidscan.c b/contrib/ctidscan/ctidscan.c
new file mode 100644
index 0000000..85b8304
--- /dev/null
+++ b/contrib/ctidscan/ctidscan.c
@@ -0,0 +1,808 @@
+/*
+ * ctidscan.c
+ *
+ * A custom-scan provide that utilizes ctid system column within
+ * inequality-operators, to skip block reads never referenced.
+ *
+ * It is designed to demonstrate Custom Scan APIs; that allows to override
+ * a part of executor node. This extension focus on a workload that tries
+ * to fetch records with tid larger or less than a particular value.
+ * In case when inequality operators were given, this module construct
+ * a custom scan path that enables to skip records not to be read. Then,
+ * if it was the cheapest one, it shall be used to run the query.
+ * Custom Scan APIs callbacks this extension when executor tries to fetch
+ * underlying records, then it utilizes existing heap_getnext() but seek
+ * the records to be read prior to fetching the first record.
+ *
+ * Portions Copyright (c) 2014, PostgreSQL Global Development Group
+ */
+#include "postgres.h"
+#include "access/relscan.h"
+#include "access/sysattr.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/explain.h"
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
+#include "optimizer/planmain.h"
+#include "optimizer/placeholder.h"
+#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
+#include "parser/parsetree.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/spccache.h"
+
+/* missing declaration in pg_proc.h */
+#ifndef TIDGreaterOperator
+#define TIDGreaterOperator 2800
+#endif
+#ifndef TIDLessEqualOperator
+#define TIDLessEqualOperator 2801
+#endif
+#ifndef TIDGreaterEqualOperator
+#define TIDGreaterEqualOperator 2802
+#endif
+
+PG_MODULE_MAGIC;
+
+/*
+ * NOTE: We don't use any special data type to save the private data.
+ * All we want to save in private fields is expression-list that shall
+ * be adjusted by setrefs.c/subselect.c, so we put it on the custom_exprs
+ * of CustomScan structure, not custom_private field.
+ * Due to the interface contract, only expression nodes are allowed to put
+ * on the custom_exprs, and we have to pay attention the core backend may
+ * adjust expression items.
+ */
+
+/*
+ * CtidScanState - state object of ctidscan on executor.
+ * It has few additional internal state. The 'ctid_quals' has list of
+ * ExprState for inequality operators that involve ctid system column.
+ */
+typedef struct {
+ CustomScanState css;
+ List *ctid_quals; /* list of ExprState for inequality ops */
+} CtidScanState;
+
+/* static variables */
+static bool enable_ctidscan;
+static set_rel_pathlist_hook_type set_rel_pathlist_next = NULL;
+
+/* function declarations */
+void _PG_init(void);
+
+static void SetCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ Index rti,
+ RangeTblEntry *rte);
+/* CustomPathMethods */
+static Plan *PlanCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ CustomPath *best_path,
+ List *tlist,
+ List *clauses);
+
+/* CustomScanMethods */
+static Node *CreateCtidScanState(CustomScan *custom_plan);
+
+/* CustomScanExecMethods */
+static void BeginCtidScan(CustomScanState *node, EState *estate, int eflags);
+static void ReScanCtidScan(CustomScanState *node);
+static TupleTableSlot *ExecCtidScan(CustomScanState *node);
+static void EndCtidScan(CustomScanState *node);
+static void ExplainCtidScan(CustomScanState *node, List *ancestors,
+ ExplainState *es);
+
+/* static table of custom-scan callbacks */
+static CustomPathMethods ctidscan_path_methods = {
+ "ctidscan", /* CustomName */
+ PlanCtidScanPath, /* PlanCustomPath */
+ NULL, /* TextOutCustomPath */
+};
+
+static CustomScanMethods ctidscan_scan_methods = {
+ "ctidscan", /* CustomName */
+ CreateCtidScanState, /* CreateCustomScanState */
+ NULL, /* TextOutCustomScan */
+};
+
+static CustomExecMethods ctidscan_exec_methods = {
+ "ctidscan", /* CustomName */
+ BeginCtidScan, /* BeginCustomScan */
+ ExecCtidScan, /* ExecCustomScan */
+ EndCtidScan, /* EndCustomScan */
+ ReScanCtidScan, /* ReScanCustomScan */
+ NULL, /* MarkPosCustomScan */
+ NULL, /* RestrPosCustomScan */
+ ExplainCtidScan, /* ExplainCustomScan */
+};
+
+#define IsCTIDVar(node,rtindex) \
+ ((node) != NULL && \
+ IsA((node), Var) && \
+ ((Var *) (node))->varno == (rtindex) && \
+ ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \
+ ((Var *) (node))->varlevelsup == 0)
+
+/*
+ * CTidQualFromExpr
+ *
+ * It checks whether the given restriction clauses enables to determine
+ * the zone to be scanned, or not. If one or more restriction clauses are
+ * available, it returns a list of them, or NIL elsewhere.
+ * The caller can consider all the conditions are chained with AND-
+ * boolean operator, so all the operator works for narrowing down the
+ * scope of custom tid scan.
+ */
+static List *
+CTidQualFromExpr(Node *expr, int varno)
+{
+ if (is_opclause(expr))
+ {
+ OpExpr *op = (OpExpr *) expr;
+ Node *arg1;
+ Node *arg2;
+ Node *other = NULL;
+
+ /* only inequality operators are candidate */
+ if (op->opno != TIDLessOperator &&
+ op->opno != TIDLessEqualOperator &&
+ op->opno != TIDGreaterOperator &&
+ op->opno != TIDGreaterEqualOperator)
+ return NULL;
+
+ if (list_length(op->args) != 2)
+ return false; /* should not happen */
+
+ 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; /* should not 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)
+{
+ Path *path = &cpath->path;
+ List *ctid_quals = cpath->custom_private;
+ ListCell *lc;
+ double ntuples;
+ ItemPointerData ip_min;
+ ItemPointerData ip_max;
+ bool has_min_val = false;
+ bool has_max_val = false;
+ BlockNumber num_pages;
+ Cost startup_cost = 0;
+ Cost run_cost = 0;
+ Cost cpu_per_tuple;
+ QualCost qpqual_cost;
+ QualCost ctid_qual_cost;
+ double spc_random_page_cost;
+
+ /* Should only be applied to base relations */
+ Assert(baserel->relid > 0);
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ /* Mark the path with the correct row estimate */
+ if (path->param_info)
+ path->rows = path->param_info->ppi_rows;
+ else
+ path->rows = baserel->rows;
+
+ /* Estimate how many tuples we may retrieve */
+ ItemPointerSet(&ip_min, 0, 0);
+ ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber);
+ foreach (lc, ctid_quals)
+ {
+ OpExpr *op = lfirst(lc);
+ Oid opno;
+ Node *other;
+
+ Assert(is_opclause(op));
+ if (IsCTIDVar(linitial(op->args), baserel->relid))
+ {
+ opno = op->opno;
+ other = lsecond(op->args);
+ }
+ else if (IsCTIDVar(lsecond(op->args), baserel->relid))
+ {
+ /* To simplifies, we assume as if Var node is 1st argument */
+ opno = get_commutator(op->opno);
+ other = linitial(op->args);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ if (IsA(other, Const))
+ {
+ ItemPointer ip = (ItemPointer)(((Const *) other)->constvalue);
+
+ /*
+ * Just an rough estimation, we don't distinct inequality and
+ * inequality-or-equal operator from scan-size estimation
+ * perspective.
+ */
+ switch (opno)
+ {
+ case TIDLessOperator:
+ case TIDLessEqualOperator:
+ if (ItemPointerCompare(ip, &ip_max) < 0)
+ ItemPointerCopy(ip, &ip_max);
+ has_max_val = true;
+ break;
+ case TIDGreaterOperator:
+ case TIDGreaterEqualOperator:
+ if (ItemPointerCompare(ip, &ip_min) > 0)
+ ItemPointerCopy(ip, &ip_min);
+ has_min_val = true;
+ break;
+ default:
+ elog(ERROR, "unexpected operator code: %u", op->opno);
+ break;
+ }
+ }
+ }
+
+ /* estimated number of tuples in this relation */
+ ntuples = baserel->pages * baserel->tuples;
+
+ if (has_min_val && has_max_val)
+ {
+ /* case of both side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_min_val)
+ {
+ /* case of only lower side being bounded */
+ BlockNumber bnum_max = baserel->pages;
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_max_val)
+ {
+ /* case of only upper side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = 0;
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else
+ {
+ /*
+ * Just a rough estimation. We assume half of records shall be
+ * read using this restriction clause, but indeterministic until
+ * executor run it actually.
+ */
+ num_pages = Max((baserel->pages + 1) / 2, 1);
+ }
+ ntuples *= ((double) num_pages) / ((double) baserel->pages);
+
+ /*
+ * The TID qual expressions will be computed once, any other baserestrict
+ * quals once per retrieved tuple.
+ */
+ cost_qual_eval(&ctid_qual_cost, ctid_quals, root);
+
+ /* fetch estimated page cost for tablespace containing table */
+ get_tablespace_page_costs(baserel->reltablespace,
+ &spc_random_page_cost,
+ NULL);
+
+ /* disk costs --- assume each tuple on a different page */
+ run_cost += spc_random_page_cost * ntuples;
+
+ /*
+ * Add scanning CPU costs
+ * (logic copied from get_restriction_qual_cost)
+ */
+ if (path->param_info)
+ {
+ /* Include costs of pushed-down clauses */
+ cost_qual_eval(&qpqual_cost, path->param_info->ppi_clauses, root);
+
+ qpqual_cost.startup += baserel->baserestrictcost.startup;
+ qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple;
+ }
+ else
+ qpqual_cost = baserel->baserestrictcost;
+
+ /*
+ * We don't decrease cost for the inequality operators, because
+ * it is subset of qpquals and still in.
+ */
+ startup_cost += qpqual_cost.startup + ctid_qual_cost.per_tuple;
+ cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple -
+ ctid_qual_cost.per_tuple;
+ run_cost = cpu_per_tuple * ntuples;
+
+ path->startup_cost = startup_cost;
+ path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * SetCtidScanPath - entrypoint of the series of custom-scan execution.
+ * It adds CustomPath if referenced relation has inequality expressions on
+ * the ctid system column.
+ */
+static void
+SetCtidScanPath(PlannerInfo *root, RelOptInfo *baserel,
+ Index rtindex, RangeTblEntry *rte)
+{
+ char relkind;
+ ListCell *lc;
+ List *ctid_quals = NIL;
+
+ /* only plain relations are supported */
+ if (rte->rtekind != RTE_RELATION)
+ return;
+ relkind = get_rel_relkind(rte->relid);
+ if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_MATVIEW &&
+ relkind != RELKIND_TOASTVALUE)
+ return;
+
+ /*
+ * NOTE: Unlike built-in execution path, always we can have core path
+ * even though ctid scan is not available. So, simply, we don't add
+ * any paths, instead of adding disable_cost.
+ */
+ if (!enable_ctidscan)
+ return;
+
+ /* walk on the restrict info */
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+ List *temp;
+
+ if (!IsA(rinfo, RestrictInfo))
+ continue; /* probably should never happen */
+ temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid);
+ ctid_quals = list_concat(ctid_quals, temp);
+ }
+
+ /*
+ * OK, it is case when a part of restriction clause makes sense to
+ * reduce number of tuples, so we will add a custom scan path being
+ * provided by this module.
+ */
+ if (ctid_quals != NIL)
+ {
+ CustomPath *cpath;
+ Relids required_outer;
+
+ /*
+ * We don't support pushing join clauses into the quals of a ctidscan,
+ * but it could still have required parameterization due to LATERAL
+ * refs in its tlist.
+ */
+ required_outer = baserel->lateral_relids;
+
+ cpath = palloc0(sizeof(CustomPath));
+ cpath->path.type = T_CustomPath;
+ cpath->path.pathtype = T_CustomScan;
+ cpath->path.parent = baserel;
+ cpath->path.param_info
+ = get_baserel_parampathinfo(root, baserel, required_outer);
+ cpath->flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN;
+ cpath->custom_private = ctid_quals;
+ cpath->methods = &ctidscan_path_methods;
+
+ CTidEstimateCosts(root, baserel, cpath);
+
+ add_path(baserel, &cpath->path);
+ }
+}
+
+/*
+ * PlanCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomScan type, according to the supplied
+ * CustomPath object.
+ */
+static Plan *
+PlanCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ CustomPath *best_path,
+ List *tlist,
+ List *clauses)
+{
+ List *ctid_quals = best_path->custom_private;
+ CustomScan *cscan = makeNode(CustomScan);
+
+ cscan->flags = best_path->flags;
+ cscan->methods = &ctidscan_scan_methods;
+
+ /* set scanrelid */
+ cscan->scan.scanrelid = rel->relid;
+ /* set targetlist as is */
+ cscan->scan.plan.targetlist = tlist;
+ /* reduce RestrictInfo list to bare expressions */
+ cscan->scan.plan.qual = extract_actual_clauses(clauses, false);
+ /* set ctid related quals */
+ cscan->custom_exprs = ctid_quals;
+
+ return &cscan->scan.plan;
+}
+
+/*
+ * CreateCtidScanState - A method of CustomScan; that populate a custom
+ * object being delivered from CustomScanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomScan *custom_plan)
+{
+ CtidScanState *ctss = palloc0(sizeof(CtidScanState));
+
+ NodeSetTag(ctss, T_CustomScanState);
+ ctss->css.flags = custom_plan->flags;
+ ctss->css.methods = &ctidscan_exec_methods;
+
+ return (Node *)&ctss->css;
+}
+
+/*
+ * BeginCtidScan - A method of CustomScanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomScanState *node, EState *estate, int eflags)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CustomScan *cscan = (CustomScan *) node->ss.ps.plan;
+
+ /*
+ * In case of custom-scan provider that offers an alternative way
+ * to scan a particular relation, most of the needed initialization,
+ * like relation open or assignment of scan tuple-slot or projection
+ * info, shall be done by the core implementation. So, all we need
+ * to have is initialization of own local properties.
+ */
+ ctss->ctid_quals = (List *)
+ ExecInitExpr((Expr *)cscan->custom_exprs, &node->ss.ps);
+}
+
+/*
+ * ReScanCtidScan - A method of CustomScanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *)node;
+ HeapScanDesc scan = ctss->css.ss.ss_currentScanDesc;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ Relation relation = ctss->css.ss.ss_currentRelation;
+ ExprContext *econtext = ctss->css.ss.ps.ps_ExprContext;
+ ScanKeyData keys[2];
+ bool has_ubound = false;
+ bool has_lbound = false;
+ ItemPointerData ip_max;
+ ItemPointerData ip_min;
+ ListCell *lc;
+
+ /* once close the existing scandesc, if any */
+ if (scan)
+ {
+ heap_endscan(scan);
+ scan = ctss->css.ss.ss_currentScanDesc = NULL;
+ }
+
+ /* walks on the inequality operators */
+ foreach (lc, ctss->ctid_quals)
+ {
+ FuncExprState *fexstate = (FuncExprState *) lfirst(lc);
+ OpExpr *op = (OpExpr *)fexstate->xprstate.expr;
+ Node *arg1 = linitial(op->args);
+ Node *arg2 = lsecond(op->args);
+ Index scanrelid;
+ Oid opno;
+ ExprState *exstate;
+ ItemPointer itemptr;
+ bool isnull;
+
+ scanrelid = ((Scan *)ctss->css.ss.ps.plan)->scanrelid;
+ if (IsCTIDVar(arg1, scanrelid))
+ {
+ exstate = (ExprState *) lsecond(fexstate->args);
+ opno = op->opno;
+ }
+ else if (IsCTIDVar(arg2, scanrelid))
+ {
+ exstate = (ExprState *) linitial(fexstate->args);
+ opno = get_commutator(op->opno);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ itemptr = (ItemPointer)
+ DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+ econtext,
+ &isnull,
+ NULL));
+ if (isnull)
+ {
+ /*
+ * Whole of the restriction clauses chained with AND- boolean
+ * operators because false, if one of the clauses has NULL result.
+ * So, we can immediately break the evaluation to inform caller
+ * it does not make sense to scan any more.
+ * In this case, scandesc is kept to NULL.
+ */
+ return;
+ }
+
+ switch (opno)
+ {
+ case TIDLessOperator:
+ if (!has_ubound ||
+ ItemPointerCompare(itemptr, &ip_max) <= 0)
+ {
+ ScanKeyInit(&keys[0],
+ SelfItemPointerAttributeNumber,
+ BTLessStrategyNumber,
+ F_TIDLT,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_max);
+ has_ubound = true;
+ }
+ break;
+
+ case TIDLessEqualOperator:
+ if (!has_ubound ||
+ ItemPointerCompare(itemptr, &ip_max) < 0)
+ {
+ ScanKeyInit(&keys[0],
+ SelfItemPointerAttributeNumber,
+ BTLessEqualStrategyNumber,
+ F_TIDLE,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_max);
+ has_ubound = true;
+ }
+ break;
+
+ case TIDGreaterOperator:
+ if (!has_lbound ||
+ ItemPointerCompare(itemptr, &ip_min) >= 0)
+ {
+ ScanKeyInit(&keys[1],
+ SelfItemPointerAttributeNumber,
+ BTGreaterStrategyNumber,
+ F_TIDGT,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_min);
+ has_lbound = true;
+ }
+ break;
+
+ case TIDGreaterEqualOperator:
+ if (!has_lbound ||
+ ItemPointerCompare(itemptr, &ip_min) > 0)
+ {
+ ScanKeyInit(&keys[1],
+ SelfItemPointerAttributeNumber,
+ BTGreaterEqualStrategyNumber,
+ F_TIDGE,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_min);
+ has_lbound = true;
+ }
+ break;
+
+ default:
+ elog(ERROR, "unsupported operator");
+ break;
+ }
+ }
+
+ /* begin heapscan with the key above */
+ if (has_ubound && has_lbound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 2, &keys[0]);
+ else if (has_ubound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[0]);
+ else if (has_lbound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[1]);
+ else
+ scan = heap_beginscan(relation, estate->es_snapshot, 0, NULL);
+
+ /* Seek the starting position, if possible */
+ if (direction == ForwardScanDirection && has_lbound)
+ {
+ BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_min.ip_blkid),
+ scan->rs_nblocks - 1);
+ scan->rs_startblock = blknum;
+ }
+ else if (direction == BackwardScanDirection && has_ubound)
+ {
+ BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid),
+ scan->rs_nblocks - 1);
+ scan->rs_startblock = blknum;
+ }
+ ctss->css.ss.ss_currentScanDesc = scan;
+}
+
+/*
+ * CTidAccessCustomScan
+ *
+ * Access method of ExecCtidScan below. It fetches a tuple from the underlying
+ * heap scan that was started from the point according to the tid clauses.
+ */
+static TupleTableSlot *
+CTidAccessCustomScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ HeapScanDesc scan;
+ TupleTableSlot *slot;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ HeapTuple tuple;
+
+ if (!ctss->css.ss.ss_currentScanDesc)
+ ReScanCtidScan(node);
+ scan = ctss->css.ss.ss_currentScanDesc;
+ Assert(scan != NULL);
+
+ /*
+ * get the next tuple from the table
+ */
+ tuple = heap_getnext(scan, direction);
+ if (!HeapTupleIsValid(tuple))
+ return NULL;
+
+ slot = ctss->css.ss.ss_ScanTupleSlot;
+ ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+ return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomScanState *node, TupleTableSlot *slot)
+{
+ return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomScanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomScanState *node)
+{
+ return ExecScan(&node->ss,
+ (ExecScanAccessMtd) CTidAccessCustomScan,
+ (ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomScanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *)node;
+
+ if (ctss->css.ss.ss_currentScanDesc)
+ heap_endscan(ctss->css.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplainCtidScan - A method of CustomScanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomScanState *node, List *ancestors, ExplainState *es)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CustomScan *cscan = (CustomScan *) ctss->css.ss.ps.plan;
+
+ /* logic copied from show_qual and show_expression */
+ if (cscan->custom_exprs)
+ {
+ bool useprefix = es->verbose;
+ Node *qual;
+ List *context;
+ char *exprstr;
+
+ /* Convert AND list to explicit AND */
+ qual = (Node *) make_ands_explicit(cscan->custom_exprs);
+
+ /* Set up deparsing context */
+ context = deparse_context_for_planstate((Node *)&node->ss.ps,
+ ancestors,
+ es->rtable,
+ es->rtable_names);
+
+ /* Deparse the expression */
+ exprstr = deparse_expression(qual, context, useprefix, false);
+
+ /* And add to es->str */
+ ExplainPropertyText("ctid quals", exprstr, es);
+ }
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("enable_ctidscan",
+ "Enables the planner's use of ctid-scan plans.",
+ NULL,
+ &enable_ctidscan,
+ true,
+ PGC_USERSET,
+ GUC_NOT_IN_SAMPLE,
+ NULL, NULL, NULL);
+
+ /* registration of the hook to add alternative path */
+ set_rel_pathlist_next = set_rel_pathlist_hook;
+ set_rel_pathlist_hook = SetCtidScanPath;
+}
diff --git a/contrib/ctidscan/expected/ctidscan.out b/contrib/ctidscan/expected/ctidscan.out
new file mode 100644
index 0000000..5b28924
--- /dev/null
+++ b/contrib/ctidscan/expected/ctidscan.out
@@ -0,0 +1,332 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+-- construction of test data
+SET client_min_messages TO 'warning';
+CREATE SCHEMA regtest_custom_scan;
+SET search_path TO regtest_custom_scan, public;
+CREATE TABLE t1 (
+ a int primary key,
+ b text
+);
+INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t1;
+CREATE TABLE t2 (
+ x int primary key,
+ y text
+);
+INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t2;
+RESET client_min_messages;
+--
+-- Check Plans if no special extension is loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+ QUERY PLAN
+--------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+ QUERY PLAN
+--------------------------------
+ Seq Scan on t1
+ Filter: (b ~~ '%789%'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+ QUERY PLAN
+--------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+ QUERY PLAN
+--------------------------------
+ Seq Scan on t1
+ Filter: (b ~~ '%789%'::text)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+ ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+ QUERY PLAN
+--------------------------------------------------
+ Merge Join
+ Merge Cond: (t1.ctid = t2.ctid)
+ -> Sort
+ Sort Key: t1.ctid
+ -> Custom Scan (ctidscan) on t1
+ Filter: (ctid < '(2,10)'::tid)
+ ctid quals: (ctid < '(2,10)'::tid)
+ -> Sort
+ Sort Key: t2.ctid
+ -> Custom Scan (ctidscan) on t2
+ Filter: (ctid > '(1,75)'::tid)
+ ctid quals: (ctid > '(1,75)'::tid)
+(12 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+ ctid | a | b
+---------+-----+----------------------------------
+ (0,1) | 1 | c4ca4238a0b923820dcc509a6f75849b
+ (0,2) | 2 | c81e728d9d4c2f636f067f89cc14862c
+ (0,3) | 3 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ (0,4) | 4 | a87ff679a2f3e71d9181a67b7542122c
+ (0,5) | 5 | e4da3b7fbbce2345d7772b0674a318d5
+ (0,6) | 6 | 1679091c5a880faf6fb5e6087eb1b2dc
+ (0,7) | 7 | 8f14e45fceea167a5a36dedd4bea2543
+ (0,8) | 8 | c9f0f895fb98ab9159f51fd0297e236d
+ (0,9) | 9 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ (0,10) | 10 | d3d9446802a44259755d38e6d163e820
+ (0,11) | 11 | 6512bd43d9caa6e02c990b0a82652dca
+ (0,12) | 12 | c20ad4d76fe97759aa27a0c99bff6710
+ (0,13) | 13 | c51ce410c124a10e0db5e4b97fc2af39
+ (0,14) | 14 | aab3238922bcc25a6f606eb525ffdc56
+ (0,15) | 15 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ (0,16) | 16 | c74d97b01eae257e44aa9d5bade97baf
+ (0,17) | 17 | 70efdf2ec9b086079795c442636b55fb
+ (0,18) | 18 | 6f4922f45568161a8cdf4ad2299f6d23
+ (0,19) | 19 | 1f0e3dad99908345f7439f8ffabdffc4
+ (0,20) | 20 | 98f13708210194c475687be6106a3b84
+ (0,21) | 21 | 3c59dc048e8850243be8079a5c74d079
+ (0,22) | 22 | b6d767d2f8ed5d21a44b0e5886680cb9
+ (0,23) | 23 | 37693cfc748049e45d87b8c7d8b9aacd
+ (0,24) | 24 | 1ff1de774005f8da13f42943881c655f
+ (0,25) | 25 | 8e296a067a37563370ded05f5a3bf3ec
+ (0,26) | 26 | 4e732ced3463d06de0ca9a15b6153677
+ (0,27) | 27 | 02e74f10e0327ad868d138f2b4fdd6f0
+ (0,28) | 28 | 33e75ff09dd601bbe69f351039152189
+ (0,29) | 29 | 6ea9ab1baa0efb9e19094440c317e21b
+ (0,30) | 30 | 34173cb38f07f89ddbebc2ac9128303f
+ (0,31) | 31 | c16a5320fa475530d9583c34fd356ef5
+ (0,32) | 32 | 6364d3f0f495b6ab9dcf8d3b5c6e0b01
+ (0,33) | 33 | 182be0c5cdcd5072bb1864cdee4d3d6e
+ (0,34) | 34 | e369853df766fa44e1ed0ff613f563bd
+ (0,35) | 35 | 1c383cd30b7c298ab50293adfecb7b18
+ (0,36) | 36 | 19ca14e7ea6328a42e0eb13d585e4c22
+ (0,37) | 37 | a5bfc9e07964f8dddeb95fc584cd965d
+ (0,38) | 38 | a5771bce93e200c36f7cd9dfd0e5deaa
+ (0,39) | 39 | d67d8ab4f4c10bf22aa353e27879133c
+ (0,40) | 40 | d645920e395fedad7bbbed0eca3fe2e0
+ (0,41) | 41 | 3416a75f4cea9109507cacd8e2f2aefc
+ (0,42) | 42 | a1d0c6e83f027327d8461063f4ac58a6
+ (0,43) | 43 | 17e62166fc8586dfa4d1bc0e1742c08b
+ (0,44) | 44 | f7177163c833dff4b38fc8d2872f1ec6
+ (0,45) | 45 | 6c8349cc7260ae62e3b1396831a8398f
+ (0,46) | 46 | d9d4f495e875a2e075a1a4a6e1b9770f
+ (0,47) | 47 | 67c6a1e7ce56d3d6fa748ab6d9af3fd7
+ (0,48) | 48 | 642e92efb79421734881b53e1e1b18b6
+ (0,49) | 49 | f457c545a9ded88f18ecee47145a72c0
+ (0,50) | 50 | c0c7c76d30bd3dcaefc96f40275bdc0a
+ (0,51) | 51 | 2838023a778dfaecdc212708f721b788
+ (0,52) | 52 | 9a1158154dfa42caddbd0694a4e9bdc8
+ (0,53) | 53 | d82c8d1619ad8176d665453cfb2e55f0
+ (0,54) | 54 | a684eceee76fc522773286a895bc8436
+ (0,55) | 55 | b53b3a3d6ab90ce0268229151c9bde11
+ (0,56) | 56 | 9f61408e3afb633e50cdf1b20de6f466
+ (0,57) | 57 | 72b32a1f754ba1c09b3695e0cb6cde7f
+ (0,58) | 58 | 66f041e16a60928b05a7e228a89c3799
+ (0,59) | 59 | 093f65e080a295f8076b1c5722a46aa2
+ (0,60) | 60 | 072b030ba126b2f4b2374f342be9ed44
+ (0,61) | 61 | 7f39f8317fbdb1988ef4c628eba02591
+ (0,62) | 62 | 44f683a84163b3523afe57c2e008bc8c
+ (0,63) | 63 | 03afdbd66e7929b125f8597834fa83a4
+ (0,64) | 64 | ea5d2f1c4608232e07d3aa3d998e5135
+ (0,65) | 65 | fc490ca45c00b1249bbe3554a4fdf6fb
+ (0,66) | 66 | 3295c76acbf4caaed33c36b1b5fc2cb1
+ (0,67) | 67 | 735b90b4568125ed6c3f678819b6e058
+ (0,68) | 68 | a3f390d88e4c41f2747bfa2f1b5f87db
+ (0,69) | 69 | 14bfa6bb14875e45bba028a21ed38046
+ (0,70) | 70 | 7cbbc409ec990f19c78c75bd1e06f215
+ (0,71) | 71 | e2c420d928d4bf8ce0ff2ec19b371514
+ (0,72) | 72 | 32bb90e8976aab5298d5da10fe66f21d
+ (0,73) | 73 | d2ddea18f00665ce8623e36bd4e3c7c5
+ (0,74) | 74 | ad61ab143223efbc24c7d2583be69251
+ (0,75) | 75 | d09bf41544a3365a46c9077ebb5e35c3
+ (0,76) | 76 | fbd7939d674997cdb4692d34de8633c4
+ (0,77) | 77 | 28dd2c7955ce926456240b2ff0100bde
+ (0,78) | 78 | 35f4a8d465e6e1edc05f3d8ab658c551
+ (0,79) | 79 | d1fe173d08e959397adf34b1d77e88d7
+ (0,80) | 80 | f033ab37c30201f73f142449d037028d
+ (0,81) | 81 | 43ec517d68b6edd3015b3edc9a11367b
+ (0,82) | 82 | 9778d5d219c5080b9a6a17bef029331c
+ (0,83) | 83 | fe9fc289c3ff0af142b6d3bead98a923
+ (0,84) | 84 | 68d30a9594728bc39aa24be94b319d21
+ (0,85) | 85 | 3ef815416f775098fe977004015c6193
+ (0,86) | 86 | 93db85ed909c13838ff95ccfa94cebd9
+ (0,87) | 87 | c7e1249ffc03eb9ded908c236bd1996d
+ (0,88) | 88 | 2a38a4a9316c49e5a833517c45d31070
+ (0,89) | 89 | 7647966b7343c29048673252e490f736
+ (0,90) | 90 | 8613985ec49eb8f757ae6439e879bb2a
+ (0,91) | 91 | 54229abfcfa5649e7003b83dd4755294
+ (0,92) | 92 | 92cc227532d17e56e07902b254dfad10
+ (0,93) | 93 | 98dce83da57b0395e163467c9dae521b
+ (0,94) | 94 | f4b9ec30ad9f68f89b29639786cb62ef
+ (0,95) | 95 | 812b4ba287f5ee0bc9d43bbf5bbe87fb
+ (0,96) | 96 | 26657d5ff9020d2abefe558796b99584
+ (0,97) | 97 | e2ef524fbf3d9fe611d5a8e90fefdc9c
+ (0,98) | 98 | ed3d2c21991e3bef5e069713af9fa6ca
+ (0,99) | 99 | ac627ab1ccbdb62ec96e702f07f6425b
+ (0,100) | 100 | f899139df5e1059396431415e770c6dd
+ (0,101) | 101 | 38b3eff8baf56627478ec76a704e9b52
+ (0,102) | 102 | ec8956637a99787bd197eacd77acce5e
+ (0,103) | 103 | 6974ce5ac660610b44d9b9fed0ff9548
+ (0,104) | 104 | c9e1074f5b3f9fc8ea15d152add07294
+ (0,105) | 105 | 65b9eea6e1cc6bb9f0cd2a47751a186f
+ (0,106) | 106 | f0935e4cd5920aa6c7c996a5ee53a70f
+ (0,107) | 107 | a97da629b098b75c294dffdc3e463904
+ (0,108) | 108 | a3c65c2974270fd093ee8a9bf8ae7d0b
+ (0,109) | 109 | 2723d092b63885e0d7c260cc007e8b9d
+ (0,110) | 110 | 5f93f983524def3dca464469d2cf9f3e
+ (0,111) | 111 | 698d51a19d8a121ce581499d7b701668
+ (0,112) | 112 | 7f6ffaa6bb0b408017b62254211691b5
+ (0,113) | 113 | 73278a4a86960eeb576a8fd4c9ec6997
+ (0,114) | 114 | 5fd0b37cd7dbbb00f97ba6ce92bf5add
+ (0,115) | 115 | 2b44928ae11fb9384c4cf38708677c48
+ (0,116) | 116 | c45147dee729311ef5b5c3003946c48f
+ (0,117) | 117 | eb160de1de89d9058fcb0b968dbbbd68
+ (0,118) | 118 | 5ef059938ba799aaa845e1c2e8a762bd
+ (0,119) | 119 | 07e1cd7dca89a1678042477183b7ac3f
+ (0,120) | 120 | da4fb5c6e93e74d3df8527599fa62642
+ (1,1) | 121 | 4c56ff4ce4aaf9573aa5dff913df997a
+ (1,2) | 122 | a0a080f42e6f13b3a2df133f073095dd
+ (1,3) | 123 | 202cb962ac59075b964b07152d234b70
+ (1,4) | 124 | c8ffe9a587b126f152ed3d89a146b445
+ (1,5) | 125 | 3def184ad8f4755ff269862ea77393dd
+ (1,6) | 126 | 069059b7ef840f0c74a814ec9237b6ec
+ (1,7) | 127 | ec5decca5ed3d6b8079e2e7e7bacc9f2
+ (1,8) | 128 | 76dc611d6ebaafc66cc0879c71b5db5c
+ (1,9) | 129 | d1f491a404d6854880943e5c3cd9ca25
+ (1,10) | 130 | 9b8619251a19057cff70779273e95aa6
+ (1,11) | 131 | 1afa34a7f984eeabdbb0a7d494132ee5
+ (1,12) | 132 | 65ded5353c5ee48d0b7d48c591b8f430
+ (1,13) | 133 | 9fc3d7152ba9336a670e36d0ed79bc43
+ (1,14) | 134 | 02522a2b2726fb0a03bb19f2d8d9524d
+ (1,15) | 135 | 7f1de29e6da19d22b51c68001e7e0e54
+ (1,16) | 136 | 42a0e188f5033bc65bf8d78622277c4e
+ (1,17) | 137 | 3988c7f88ebcb58c6ce932b957b6f332
+ (1,18) | 138 | 013d407166ec4fa56eb1e1f8cbe183b9
+ (1,19) | 139 | e00da03b685a0dd18fb6a08af0923de0
+(139 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+ ctid | a | b
+------+---+---
+(0 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ ctid | a | b
+---------+-----+----------------------------------
+ (2,115) | 355 | 82cec96096d4281b7c95cd7e74623496
+ (2,116) | 356 | 6c524f9d5d7027454a783c841250ba71
+ (2,117) | 357 | fb7b9ffa5462084c5f4e7e85a093e6d7
+ (2,118) | 358 | aa942ab2bfa6ebda4840e7360ce6e7ef
+ (2,119) | 359 | c058f544c737782deacefa532d9add4c
+ (2,120) | 360 | e7b24b112a44fdd9ee93bdf998c6ca0e
+ (3,1) | 361 | 52720e003547c70561bf5e03b95aa99f
+ (3,2) | 362 | c3e878e27f52e2a57ace4d9a76fd9acf
+ (3,3) | 363 | 00411460f7c92d2124a67ea0f4cb5f85
+ (3,4) | 364 | bac9162b47c56fc8a4d2a519803d51b3
+ (3,5) | 365 | 9be40cee5b0eee1462c82c6964087ff9
+ (3,6) | 366 | 5ef698cd9fe650923ea331c15af3b160
+ (3,7) | 367 | 05049e90fa4f5039a8cadc6acbb4b2cc
+ (3,8) | 368 | cf004fdc76fa1a4f25f62e0eb5261ca3
+ (3,9) | 369 | 0c74b7f78409a4022a2c4c5a5ca3ee19
+ (3,10) | 370 | d709f38ef758b5066ef31b18039b8ce5
+(16 rows)
+
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+ ctid | a | b | x | y
+--------+-----+----------------------------------+-----+------------------------------------------------------------------
+ (1,76) | 196 | 084b6fbb10729ed4da8c3d3f5a3ae7c9 | 157 | 6c4b761a28b734fe93831e3fb400ce876c4b761a28b734fe93831e3fb400ce87
+ (1,77) | 197 | 85d8ce590ad8981ca2c8286f79f59954 | 158 | 06409663226af2f3114485aa4e0a23b406409663226af2f3114485aa4e0a23b4
+ (1,78) | 198 | 0e65972dce68dad4d52d063967f0a705 | 159 | 140f6969d5213fd0ece03148e62e461e140f6969d5213fd0ece03148e62e461e
+ (1,79) | 199 | 84d9ee44e457ddef7f2c4f25dc8fa865 | 160 | b73ce398c39f506af761d2277d853a92b73ce398c39f506af761d2277d853a92
+ (1,80) | 200 | 3644a684f98ea8fe223c713b77189a77 | 161 | bd4c9ab730f5513206b999ec0d90d1fbbd4c9ab730f5513206b999ec0d90d1fb
+ (1,81) | 201 | 757b505cfd34c64c85ca5b5690ee5293 | 162 | 82aa4b0af34c2313a562076992e50aa382aa4b0af34c2313a562076992e50aa3
+ (2,1) | 241 | f340f1b1f65b6df5b5e3f94d95b11daf | 163 | 0777d5c17d4066b82ab86dff8a46af6f0777d5c17d4066b82ab86dff8a46af6f
+ (2,2) | 242 | e4a6222cdb5b34375400904f03d8e6a5 | 164 | fa7cdfad1a5aaf8370ebeda47a1ff1c3fa7cdfad1a5aaf8370ebeda47a1ff1c3
+ (2,3) | 243 | cb70ab375662576bd1ac5aaf16b3fca4 | 165 | 9766527f2b5d3e95d4a733fcfb77bd7e9766527f2b5d3e95d4a733fcfb77bd7e
+ (2,4) | 244 | 9188905e74c28e489b44e954ec0b9bca | 166 | 7e7757b1e12abcb736ab9a754ffb617a7e7757b1e12abcb736ab9a754ffb617a
+ (2,5) | 245 | 0266e33d3f546cb5436a10798e657d97 | 167 | 5878a7ab84fb43402106c575658472fa5878a7ab84fb43402106c575658472fa
+ (2,6) | 246 | 38db3aed920cf82ab059bfccbd02be6a | 168 | 006f52e9102a8d3be2fe5614f42ba989006f52e9102a8d3be2fe5614f42ba989
+ (2,7) | 247 | 3cec07e9ba5f5bb252d13f5f431e4bbb | 169 | 3636638817772e42b59d74cff571fbb33636638817772e42b59d74cff571fbb3
+ (2,8) | 248 | 621bf66ddb7c962aa0d22ac97d69b793 | 170 | 149e9677a5989fd342ae44213df68868149e9677a5989fd342ae44213df68868
+ (2,9) | 249 | 077e29b11be80ab57e1a2ecabb7da330 | 171 | a4a042cf4fd6bfb47701cbc8a1653adaa4a042cf4fd6bfb47701cbc8a1653ada
+(15 rows)
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+ WHERE b like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+ ctid quals: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: ((b ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+ ctid quals: ((ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+(3 rows)
+
+-- Also, EXPLAIN with none-text format
+EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------
+ <explain xmlns="http://www.postgresql.org/2009/explain"> +
+ <Query> +
+ <Plan> +
+ <Node-Type>Custom Scan</Node-Type> +
+ <Custom-Plan-Provider>ctidscan</Custom-Plan-Provider> +
+ <Relation-Name>t1</Relation-Name> +
+ <Alias>t1</Alias> +
+ <Filter>((b ~~ '%abc%'::text) AND (ctid >= '(0,0)'::tid) AND (ctid <= '(5,0)'::tid))</Filter>+
+ <ctid-quals>((ctid >= '(0,0)'::tid) AND (ctid <= '(5,0)'::tid))</ctid-quals> +
+ </Plan> +
+ </Query> +
+ </explain>
+(1 row)
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to table t1
+drop cascades to table t2
diff --git a/contrib/ctidscan/sql/ctidscan.sql b/contrib/ctidscan/sql/ctidscan.sql
new file mode 100644
index 0000000..26c22c2
--- /dev/null
+++ b/contrib/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,59 @@
+--
+-- Regression Tests for Custom Plan APIs
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+ a int primary key,
+ b text
+);
+INSERT INTO t1 (SELECT s, md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+ x int primary key,
+ y text
+);
+INSERT INTO t2 (SELECT s, md5(s::text)||md5(s::text) FROM generate_series(1,400) s);
+VACUUM ANALYZE t2;
+
+RESET client_min_messages;
+--
+-- Check Plans if no special extension is loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+SELECT ctid,* FROM t1 WHERE ctid < '(1,20)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid > '(4,0)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT t1.ctid,* FROM t1 JOIN t2 ON t1.ctid = t2.ctid WHERE t1.ctid < '(2,10)'::tid AND t2.ctid > '(1,75)'::tid;
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+ WHERE b like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+
+-- Also, EXPLAIN with none-text format
+EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid);
+
+-- Test cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..59eab97 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -109,6 +109,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
&btree-gist;
&chkpass;
&citext;
+ &ctidscan;
&cube;
&dblink;
&dict-int;
diff --git a/doc/src/sgml/ctidscan.sgml b/doc/src/sgml/ctidscan.sgml
new file mode 100644
index 0000000..fa21b0f
--- /dev/null
+++ b/doc/src/sgml/ctidscan.sgml
@@ -0,0 +1,52 @@
+<!-- doc/src/sgml/ctidscan.sgml -->
+
+<sect1 id="ctidscan" xreflabel="ctidscan">
+ <title>ctidscan</title>
+
+ <indexterm zone="ctidscan">
+ <primary>ctidscan</primary>
+ </indexterm>
+
+ <para>
+ This module implements a custom-scan provider that utilizes inequality
+ operator that involves the <literal>ctid</literal> system column.
+ </para>
+
+ <para>
+ This module provides no SQL accessible interface. For installation,
+ all you need to do is just load the module to the server.
+
+ You can load it an individual session using:
+<programlisting>
+LOAD 'ctidscan';
+</programlisting>
+
+ or, you can also take more typical usage with extension preloading
+ using <xref linkend="guc-session-preload-libraries"> or
+ <xref linkend="guc-shared-preload-libraries"> in
+ <filename>postgresql.conf</>.
+
+ Then, planner may consider more cheap execution path if supplied query
+ involves above operators.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <varname>enable_ctidscan</varname> (<type>bool</type>)
+ <indexterm>
+ <primary><varname>enable_ctidscan</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ <varname>enable_ctidscan</varname> turns on/off functionality of
+ ctidscan custom-scan provider.
+ If turned off, it does not offer alternative scan path even if
+ supplied query is sufficient to run by ctidscan plan.
+ Its default is <literal>true</>.
+ Anybody can change using <command>SET</command> command.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</sect1>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..15d569e 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -107,6 +107,7 @@
<!ENTITY btree-gist SYSTEM "btree-gist.sgml">
<!ENTITY chkpass SYSTEM "chkpass.sgml">
<!ENTITY citext SYSTEM "citext.sgml">
+<!ENTITY ctidscan SYSTEM "ctidscan.sgml">
<!ENTITY cube SYSTEM "cube.sgml">
<!ENTITY dblink SYSTEM "dblink.sgml">
<!ENTITY dict-int SYSTEM "dict-int.sgml">
On Mon, Dec 15, 2014 at 4:55 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:
I'm not certain whether we should have this functionality in contrib
from the perspective of workload that can help, but its major worth is
for an example of custom-scan interface.
worker_spi is now in src/test/modules. We may add it there as well, no?
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Dec 15, 2014 at 4:55 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:
I'm not certain whether we should have this functionality in contrib
from the perspective of workload that can help, but its major worth is
for an example of custom-scan interface.worker_spi is now in src/test/modules. We may add it there as well, no?
Hmm, it makes sense for me. Does it also make sense to add a test-case to
the core regression test cases?
Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Dec 15, 2014 at 4:55 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com>
wrote:
I'm not certain whether we should have this functionality in contrib
from the perspective of workload that can help, but its major worth
is for an example of custom-scan interface.worker_spi is now in src/test/modules. We may add it there as well, no?
Hmm, it makes sense for me. Does it also make sense to add a test-case to
the core regression test cases?
The attached patch adds ctidscan module at test/module instead of contrib.
Basic portion is not changed from the previous post, but file locations
and test cases in regression test are changed.
Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
Attachments:
pgsql-v9.5-test-ctidscan.v2.patchapplication/octet-stream; name=pgsql-v9.5-test-ctidscan.v2.patchDownload
doc/src/sgml/contrib.sgml | 1 +
doc/src/sgml/ctidscan.sgml | 52 ++
doc/src/sgml/filelist.sgml | 1 +
src/test/modules/Makefile | 1 +
src/test/modules/ctidscan/Makefile | 16 +
src/test/modules/ctidscan/ctidscan.c | 808 ++++++++++++++++
src/test/modules/ctidscan/expected/ctidscan.out | 1119 +++++++++++++++++++++++
src/test/modules/ctidscan/sql/ctidscan.sql | 99 ++
src/test/regress/expected/custom_scan.out | 0
src/test/regress/sql/custom_scan.sql | 53 ++
10 files changed, 2150 insertions(+)
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..59eab97 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -109,6 +109,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
&btree-gist;
&chkpass;
&citext;
+ &ctidscan;
&cube;
&dblink;
&dict-int;
diff --git a/doc/src/sgml/ctidscan.sgml b/doc/src/sgml/ctidscan.sgml
new file mode 100644
index 0000000..fa21b0f
--- /dev/null
+++ b/doc/src/sgml/ctidscan.sgml
@@ -0,0 +1,52 @@
+<!-- doc/src/sgml/ctidscan.sgml -->
+
+<sect1 id="ctidscan" xreflabel="ctidscan">
+ <title>ctidscan</title>
+
+ <indexterm zone="ctidscan">
+ <primary>ctidscan</primary>
+ </indexterm>
+
+ <para>
+ This module implements a custom-scan provider that utilizes inequality
+ operator that involves the <literal>ctid</literal> system column.
+ </para>
+
+ <para>
+ This module provides no SQL accessible interface. For installation,
+ all you need to do is just load the module to the server.
+
+ You can load it an individual session using:
+<programlisting>
+LOAD 'ctidscan';
+</programlisting>
+
+ or, you can also take more typical usage with extension preloading
+ using <xref linkend="guc-session-preload-libraries"> or
+ <xref linkend="guc-shared-preload-libraries"> in
+ <filename>postgresql.conf</>.
+
+ Then, planner may consider more cheap execution path if supplied query
+ involves above operators.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <varname>enable_ctidscan</varname> (<type>bool</type>)
+ <indexterm>
+ <primary><varname>enable_ctidscan</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ <varname>enable_ctidscan</varname> turns on/off functionality of
+ ctidscan custom-scan provider.
+ If turned off, it does not offer alternative scan path even if
+ supplied query is sufficient to run by ctidscan plan.
+ Its default is <literal>true</>.
+ Anybody can change using <command>SET</command> command.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</sect1>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..15d569e 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -107,6 +107,7 @@
<!ENTITY btree-gist SYSTEM "btree-gist.sgml">
<!ENTITY chkpass SYSTEM "chkpass.sgml">
<!ENTITY citext SYSTEM "citext.sgml">
+<!ENTITY ctidscan SYSTEM "ctidscan.sgml">
<!ENTITY cube SYSTEM "cube.sgml">
<!ENTITY dblink SYSTEM "dblink.sgml">
<!ENTITY dict-int SYSTEM "dict-int.sgml">
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 93d93af..c288276 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -6,6 +6,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = \
commit_ts \
+ ctidscan \
worker_spi \
dummy_seclabel \
test_shm_mq \
diff --git a/src/test/modules/ctidscan/Makefile b/src/test/modules/ctidscan/Makefile
new file mode 100644
index 0000000..bbe280a
--- /dev/null
+++ b/src/test/modules/ctidscan/Makefile
@@ -0,0 +1,16 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+REGRESS = ctidscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/ctidscan
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/ctidscan/ctidscan.c b/src/test/modules/ctidscan/ctidscan.c
new file mode 100644
index 0000000..85b8304
--- /dev/null
+++ b/src/test/modules/ctidscan/ctidscan.c
@@ -0,0 +1,808 @@
+/*
+ * ctidscan.c
+ *
+ * A custom-scan provide that utilizes ctid system column within
+ * inequality-operators, to skip block reads never referenced.
+ *
+ * It is designed to demonstrate Custom Scan APIs; that allows to override
+ * a part of executor node. This extension focus on a workload that tries
+ * to fetch records with tid larger or less than a particular value.
+ * In case when inequality operators were given, this module construct
+ * a custom scan path that enables to skip records not to be read. Then,
+ * if it was the cheapest one, it shall be used to run the query.
+ * Custom Scan APIs callbacks this extension when executor tries to fetch
+ * underlying records, then it utilizes existing heap_getnext() but seek
+ * the records to be read prior to fetching the first record.
+ *
+ * Portions Copyright (c) 2014, PostgreSQL Global Development Group
+ */
+#include "postgres.h"
+#include "access/relscan.h"
+#include "access/sysattr.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/explain.h"
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
+#include "optimizer/planmain.h"
+#include "optimizer/placeholder.h"
+#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
+#include "parser/parsetree.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/spccache.h"
+
+/* missing declaration in pg_proc.h */
+#ifndef TIDGreaterOperator
+#define TIDGreaterOperator 2800
+#endif
+#ifndef TIDLessEqualOperator
+#define TIDLessEqualOperator 2801
+#endif
+#ifndef TIDGreaterEqualOperator
+#define TIDGreaterEqualOperator 2802
+#endif
+
+PG_MODULE_MAGIC;
+
+/*
+ * NOTE: We don't use any special data type to save the private data.
+ * All we want to save in private fields is expression-list that shall
+ * be adjusted by setrefs.c/subselect.c, so we put it on the custom_exprs
+ * of CustomScan structure, not custom_private field.
+ * Due to the interface contract, only expression nodes are allowed to put
+ * on the custom_exprs, and we have to pay attention the core backend may
+ * adjust expression items.
+ */
+
+/*
+ * CtidScanState - state object of ctidscan on executor.
+ * It has few additional internal state. The 'ctid_quals' has list of
+ * ExprState for inequality operators that involve ctid system column.
+ */
+typedef struct {
+ CustomScanState css;
+ List *ctid_quals; /* list of ExprState for inequality ops */
+} CtidScanState;
+
+/* static variables */
+static bool enable_ctidscan;
+static set_rel_pathlist_hook_type set_rel_pathlist_next = NULL;
+
+/* function declarations */
+void _PG_init(void);
+
+static void SetCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ Index rti,
+ RangeTblEntry *rte);
+/* CustomPathMethods */
+static Plan *PlanCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ CustomPath *best_path,
+ List *tlist,
+ List *clauses);
+
+/* CustomScanMethods */
+static Node *CreateCtidScanState(CustomScan *custom_plan);
+
+/* CustomScanExecMethods */
+static void BeginCtidScan(CustomScanState *node, EState *estate, int eflags);
+static void ReScanCtidScan(CustomScanState *node);
+static TupleTableSlot *ExecCtidScan(CustomScanState *node);
+static void EndCtidScan(CustomScanState *node);
+static void ExplainCtidScan(CustomScanState *node, List *ancestors,
+ ExplainState *es);
+
+/* static table of custom-scan callbacks */
+static CustomPathMethods ctidscan_path_methods = {
+ "ctidscan", /* CustomName */
+ PlanCtidScanPath, /* PlanCustomPath */
+ NULL, /* TextOutCustomPath */
+};
+
+static CustomScanMethods ctidscan_scan_methods = {
+ "ctidscan", /* CustomName */
+ CreateCtidScanState, /* CreateCustomScanState */
+ NULL, /* TextOutCustomScan */
+};
+
+static CustomExecMethods ctidscan_exec_methods = {
+ "ctidscan", /* CustomName */
+ BeginCtidScan, /* BeginCustomScan */
+ ExecCtidScan, /* ExecCustomScan */
+ EndCtidScan, /* EndCustomScan */
+ ReScanCtidScan, /* ReScanCustomScan */
+ NULL, /* MarkPosCustomScan */
+ NULL, /* RestrPosCustomScan */
+ ExplainCtidScan, /* ExplainCustomScan */
+};
+
+#define IsCTIDVar(node,rtindex) \
+ ((node) != NULL && \
+ IsA((node), Var) && \
+ ((Var *) (node))->varno == (rtindex) && \
+ ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \
+ ((Var *) (node))->varlevelsup == 0)
+
+/*
+ * CTidQualFromExpr
+ *
+ * It checks whether the given restriction clauses enables to determine
+ * the zone to be scanned, or not. If one or more restriction clauses are
+ * available, it returns a list of them, or NIL elsewhere.
+ * The caller can consider all the conditions are chained with AND-
+ * boolean operator, so all the operator works for narrowing down the
+ * scope of custom tid scan.
+ */
+static List *
+CTidQualFromExpr(Node *expr, int varno)
+{
+ if (is_opclause(expr))
+ {
+ OpExpr *op = (OpExpr *) expr;
+ Node *arg1;
+ Node *arg2;
+ Node *other = NULL;
+
+ /* only inequality operators are candidate */
+ if (op->opno != TIDLessOperator &&
+ op->opno != TIDLessEqualOperator &&
+ op->opno != TIDGreaterOperator &&
+ op->opno != TIDGreaterEqualOperator)
+ return NULL;
+
+ if (list_length(op->args) != 2)
+ return false; /* should not happen */
+
+ 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; /* should not 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)
+{
+ Path *path = &cpath->path;
+ List *ctid_quals = cpath->custom_private;
+ ListCell *lc;
+ double ntuples;
+ ItemPointerData ip_min;
+ ItemPointerData ip_max;
+ bool has_min_val = false;
+ bool has_max_val = false;
+ BlockNumber num_pages;
+ Cost startup_cost = 0;
+ Cost run_cost = 0;
+ Cost cpu_per_tuple;
+ QualCost qpqual_cost;
+ QualCost ctid_qual_cost;
+ double spc_random_page_cost;
+
+ /* Should only be applied to base relations */
+ Assert(baserel->relid > 0);
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ /* Mark the path with the correct row estimate */
+ if (path->param_info)
+ path->rows = path->param_info->ppi_rows;
+ else
+ path->rows = baserel->rows;
+
+ /* Estimate how many tuples we may retrieve */
+ ItemPointerSet(&ip_min, 0, 0);
+ ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber);
+ foreach (lc, ctid_quals)
+ {
+ OpExpr *op = lfirst(lc);
+ Oid opno;
+ Node *other;
+
+ Assert(is_opclause(op));
+ if (IsCTIDVar(linitial(op->args), baserel->relid))
+ {
+ opno = op->opno;
+ other = lsecond(op->args);
+ }
+ else if (IsCTIDVar(lsecond(op->args), baserel->relid))
+ {
+ /* To simplifies, we assume as if Var node is 1st argument */
+ opno = get_commutator(op->opno);
+ other = linitial(op->args);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ if (IsA(other, Const))
+ {
+ ItemPointer ip = (ItemPointer)(((Const *) other)->constvalue);
+
+ /*
+ * Just an rough estimation, we don't distinct inequality and
+ * inequality-or-equal operator from scan-size estimation
+ * perspective.
+ */
+ switch (opno)
+ {
+ case TIDLessOperator:
+ case TIDLessEqualOperator:
+ if (ItemPointerCompare(ip, &ip_max) < 0)
+ ItemPointerCopy(ip, &ip_max);
+ has_max_val = true;
+ break;
+ case TIDGreaterOperator:
+ case TIDGreaterEqualOperator:
+ if (ItemPointerCompare(ip, &ip_min) > 0)
+ ItemPointerCopy(ip, &ip_min);
+ has_min_val = true;
+ break;
+ default:
+ elog(ERROR, "unexpected operator code: %u", op->opno);
+ break;
+ }
+ }
+ }
+
+ /* estimated number of tuples in this relation */
+ ntuples = baserel->pages * baserel->tuples;
+
+ if (has_min_val && has_max_val)
+ {
+ /* case of both side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_min_val)
+ {
+ /* case of only lower side being bounded */
+ BlockNumber bnum_max = baserel->pages;
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_max_val)
+ {
+ /* case of only upper side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = 0;
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ num_pages = Min(bnum_max - bnum_min + 1, 1);
+ }
+ else
+ {
+ /*
+ * Just a rough estimation. We assume half of records shall be
+ * read using this restriction clause, but indeterministic until
+ * executor run it actually.
+ */
+ num_pages = Max((baserel->pages + 1) / 2, 1);
+ }
+ ntuples *= ((double) num_pages) / ((double) baserel->pages);
+
+ /*
+ * The TID qual expressions will be computed once, any other baserestrict
+ * quals once per retrieved tuple.
+ */
+ cost_qual_eval(&ctid_qual_cost, ctid_quals, root);
+
+ /* fetch estimated page cost for tablespace containing table */
+ get_tablespace_page_costs(baserel->reltablespace,
+ &spc_random_page_cost,
+ NULL);
+
+ /* disk costs --- assume each tuple on a different page */
+ run_cost += spc_random_page_cost * ntuples;
+
+ /*
+ * Add scanning CPU costs
+ * (logic copied from get_restriction_qual_cost)
+ */
+ if (path->param_info)
+ {
+ /* Include costs of pushed-down clauses */
+ cost_qual_eval(&qpqual_cost, path->param_info->ppi_clauses, root);
+
+ qpqual_cost.startup += baserel->baserestrictcost.startup;
+ qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple;
+ }
+ else
+ qpqual_cost = baserel->baserestrictcost;
+
+ /*
+ * We don't decrease cost for the inequality operators, because
+ * it is subset of qpquals and still in.
+ */
+ startup_cost += qpqual_cost.startup + ctid_qual_cost.per_tuple;
+ cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple -
+ ctid_qual_cost.per_tuple;
+ run_cost = cpu_per_tuple * ntuples;
+
+ path->startup_cost = startup_cost;
+ path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * SetCtidScanPath - entrypoint of the series of custom-scan execution.
+ * It adds CustomPath if referenced relation has inequality expressions on
+ * the ctid system column.
+ */
+static void
+SetCtidScanPath(PlannerInfo *root, RelOptInfo *baserel,
+ Index rtindex, RangeTblEntry *rte)
+{
+ char relkind;
+ ListCell *lc;
+ List *ctid_quals = NIL;
+
+ /* only plain relations are supported */
+ if (rte->rtekind != RTE_RELATION)
+ return;
+ relkind = get_rel_relkind(rte->relid);
+ if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_MATVIEW &&
+ relkind != RELKIND_TOASTVALUE)
+ return;
+
+ /*
+ * NOTE: Unlike built-in execution path, always we can have core path
+ * even though ctid scan is not available. So, simply, we don't add
+ * any paths, instead of adding disable_cost.
+ */
+ if (!enable_ctidscan)
+ return;
+
+ /* walk on the restrict info */
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+ List *temp;
+
+ if (!IsA(rinfo, RestrictInfo))
+ continue; /* probably should never happen */
+ temp = CTidQualFromExpr((Node *) rinfo->clause, baserel->relid);
+ ctid_quals = list_concat(ctid_quals, temp);
+ }
+
+ /*
+ * OK, it is case when a part of restriction clause makes sense to
+ * reduce number of tuples, so we will add a custom scan path being
+ * provided by this module.
+ */
+ if (ctid_quals != NIL)
+ {
+ CustomPath *cpath;
+ Relids required_outer;
+
+ /*
+ * We don't support pushing join clauses into the quals of a ctidscan,
+ * but it could still have required parameterization due to LATERAL
+ * refs in its tlist.
+ */
+ required_outer = baserel->lateral_relids;
+
+ cpath = palloc0(sizeof(CustomPath));
+ cpath->path.type = T_CustomPath;
+ cpath->path.pathtype = T_CustomScan;
+ cpath->path.parent = baserel;
+ cpath->path.param_info
+ = get_baserel_parampathinfo(root, baserel, required_outer);
+ cpath->flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN;
+ cpath->custom_private = ctid_quals;
+ cpath->methods = &ctidscan_path_methods;
+
+ CTidEstimateCosts(root, baserel, cpath);
+
+ add_path(baserel, &cpath->path);
+ }
+}
+
+/*
+ * PlanCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomScan type, according to the supplied
+ * CustomPath object.
+ */
+static Plan *
+PlanCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ CustomPath *best_path,
+ List *tlist,
+ List *clauses)
+{
+ List *ctid_quals = best_path->custom_private;
+ CustomScan *cscan = makeNode(CustomScan);
+
+ cscan->flags = best_path->flags;
+ cscan->methods = &ctidscan_scan_methods;
+
+ /* set scanrelid */
+ cscan->scan.scanrelid = rel->relid;
+ /* set targetlist as is */
+ cscan->scan.plan.targetlist = tlist;
+ /* reduce RestrictInfo list to bare expressions */
+ cscan->scan.plan.qual = extract_actual_clauses(clauses, false);
+ /* set ctid related quals */
+ cscan->custom_exprs = ctid_quals;
+
+ return &cscan->scan.plan;
+}
+
+/*
+ * CreateCtidScanState - A method of CustomScan; that populate a custom
+ * object being delivered from CustomScanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomScan *custom_plan)
+{
+ CtidScanState *ctss = palloc0(sizeof(CtidScanState));
+
+ NodeSetTag(ctss, T_CustomScanState);
+ ctss->css.flags = custom_plan->flags;
+ ctss->css.methods = &ctidscan_exec_methods;
+
+ return (Node *)&ctss->css;
+}
+
+/*
+ * BeginCtidScan - A method of CustomScanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomScanState *node, EState *estate, int eflags)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CustomScan *cscan = (CustomScan *) node->ss.ps.plan;
+
+ /*
+ * In case of custom-scan provider that offers an alternative way
+ * to scan a particular relation, most of the needed initialization,
+ * like relation open or assignment of scan tuple-slot or projection
+ * info, shall be done by the core implementation. So, all we need
+ * to have is initialization of own local properties.
+ */
+ ctss->ctid_quals = (List *)
+ ExecInitExpr((Expr *)cscan->custom_exprs, &node->ss.ps);
+}
+
+/*
+ * ReScanCtidScan - A method of CustomScanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *)node;
+ HeapScanDesc scan = ctss->css.ss.ss_currentScanDesc;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ Relation relation = ctss->css.ss.ss_currentRelation;
+ ExprContext *econtext = ctss->css.ss.ps.ps_ExprContext;
+ ScanKeyData keys[2];
+ bool has_ubound = false;
+ bool has_lbound = false;
+ ItemPointerData ip_max;
+ ItemPointerData ip_min;
+ ListCell *lc;
+
+ /* once close the existing scandesc, if any */
+ if (scan)
+ {
+ heap_endscan(scan);
+ scan = ctss->css.ss.ss_currentScanDesc = NULL;
+ }
+
+ /* walks on the inequality operators */
+ foreach (lc, ctss->ctid_quals)
+ {
+ FuncExprState *fexstate = (FuncExprState *) lfirst(lc);
+ OpExpr *op = (OpExpr *)fexstate->xprstate.expr;
+ Node *arg1 = linitial(op->args);
+ Node *arg2 = lsecond(op->args);
+ Index scanrelid;
+ Oid opno;
+ ExprState *exstate;
+ ItemPointer itemptr;
+ bool isnull;
+
+ scanrelid = ((Scan *)ctss->css.ss.ps.plan)->scanrelid;
+ if (IsCTIDVar(arg1, scanrelid))
+ {
+ exstate = (ExprState *) lsecond(fexstate->args);
+ opno = op->opno;
+ }
+ else if (IsCTIDVar(arg2, scanrelid))
+ {
+ exstate = (ExprState *) linitial(fexstate->args);
+ opno = get_commutator(op->opno);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ itemptr = (ItemPointer)
+ DatumGetPointer(ExecEvalExprSwitchContext(exstate,
+ econtext,
+ &isnull,
+ NULL));
+ if (isnull)
+ {
+ /*
+ * Whole of the restriction clauses chained with AND- boolean
+ * operators because false, if one of the clauses has NULL result.
+ * So, we can immediately break the evaluation to inform caller
+ * it does not make sense to scan any more.
+ * In this case, scandesc is kept to NULL.
+ */
+ return;
+ }
+
+ switch (opno)
+ {
+ case TIDLessOperator:
+ if (!has_ubound ||
+ ItemPointerCompare(itemptr, &ip_max) <= 0)
+ {
+ ScanKeyInit(&keys[0],
+ SelfItemPointerAttributeNumber,
+ BTLessStrategyNumber,
+ F_TIDLT,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_max);
+ has_ubound = true;
+ }
+ break;
+
+ case TIDLessEqualOperator:
+ if (!has_ubound ||
+ ItemPointerCompare(itemptr, &ip_max) < 0)
+ {
+ ScanKeyInit(&keys[0],
+ SelfItemPointerAttributeNumber,
+ BTLessEqualStrategyNumber,
+ F_TIDLE,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_max);
+ has_ubound = true;
+ }
+ break;
+
+ case TIDGreaterOperator:
+ if (!has_lbound ||
+ ItemPointerCompare(itemptr, &ip_min) >= 0)
+ {
+ ScanKeyInit(&keys[1],
+ SelfItemPointerAttributeNumber,
+ BTGreaterStrategyNumber,
+ F_TIDGT,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_min);
+ has_lbound = true;
+ }
+ break;
+
+ case TIDGreaterEqualOperator:
+ if (!has_lbound ||
+ ItemPointerCompare(itemptr, &ip_min) > 0)
+ {
+ ScanKeyInit(&keys[1],
+ SelfItemPointerAttributeNumber,
+ BTGreaterEqualStrategyNumber,
+ F_TIDGE,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_min);
+ has_lbound = true;
+ }
+ break;
+
+ default:
+ elog(ERROR, "unsupported operator");
+ break;
+ }
+ }
+
+ /* begin heapscan with the key above */
+ if (has_ubound && has_lbound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 2, &keys[0]);
+ else if (has_ubound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[0]);
+ else if (has_lbound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[1]);
+ else
+ scan = heap_beginscan(relation, estate->es_snapshot, 0, NULL);
+
+ /* Seek the starting position, if possible */
+ if (direction == ForwardScanDirection && has_lbound)
+ {
+ BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_min.ip_blkid),
+ scan->rs_nblocks - 1);
+ scan->rs_startblock = blknum;
+ }
+ else if (direction == BackwardScanDirection && has_ubound)
+ {
+ BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid),
+ scan->rs_nblocks - 1);
+ scan->rs_startblock = blknum;
+ }
+ ctss->css.ss.ss_currentScanDesc = scan;
+}
+
+/*
+ * CTidAccessCustomScan
+ *
+ * Access method of ExecCtidScan below. It fetches a tuple from the underlying
+ * heap scan that was started from the point according to the tid clauses.
+ */
+static TupleTableSlot *
+CTidAccessCustomScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ HeapScanDesc scan;
+ TupleTableSlot *slot;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ HeapTuple tuple;
+
+ if (!ctss->css.ss.ss_currentScanDesc)
+ ReScanCtidScan(node);
+ scan = ctss->css.ss.ss_currentScanDesc;
+ Assert(scan != NULL);
+
+ /*
+ * get the next tuple from the table
+ */
+ tuple = heap_getnext(scan, direction);
+ if (!HeapTupleIsValid(tuple))
+ return NULL;
+
+ slot = ctss->css.ss.ss_ScanTupleSlot;
+ ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+ return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomScanState *node, TupleTableSlot *slot)
+{
+ return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomScanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomScanState *node)
+{
+ return ExecScan(&node->ss,
+ (ExecScanAccessMtd) CTidAccessCustomScan,
+ (ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomScanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *)node;
+
+ if (ctss->css.ss.ss_currentScanDesc)
+ heap_endscan(ctss->css.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplainCtidScan - A method of CustomScanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomScanState *node, List *ancestors, ExplainState *es)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CustomScan *cscan = (CustomScan *) ctss->css.ss.ps.plan;
+
+ /* logic copied from show_qual and show_expression */
+ if (cscan->custom_exprs)
+ {
+ bool useprefix = es->verbose;
+ Node *qual;
+ List *context;
+ char *exprstr;
+
+ /* Convert AND list to explicit AND */
+ qual = (Node *) make_ands_explicit(cscan->custom_exprs);
+
+ /* Set up deparsing context */
+ context = deparse_context_for_planstate((Node *)&node->ss.ps,
+ ancestors,
+ es->rtable,
+ es->rtable_names);
+
+ /* Deparse the expression */
+ exprstr = deparse_expression(qual, context, useprefix, false);
+
+ /* And add to es->str */
+ ExplainPropertyText("ctid quals", exprstr, es);
+ }
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("enable_ctidscan",
+ "Enables the planner's use of ctid-scan plans.",
+ NULL,
+ &enable_ctidscan,
+ true,
+ PGC_USERSET,
+ GUC_NOT_IN_SAMPLE,
+ NULL, NULL, NULL);
+
+ /* registration of the hook to add alternative path */
+ set_rel_pathlist_next = set_rel_pathlist_hook;
+ set_rel_pathlist_hook = SetCtidScanPath;
+}
diff --git a/src/test/modules/ctidscan/expected/ctidscan.out b/src/test/modules/ctidscan/expected/ctidscan.out
new file mode 100644
index 0000000..90db9aa
--- /dev/null
+++ b/src/test/modules/ctidscan/expected/ctidscan.out
@@ -0,0 +1,1119 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+-- construction of test data
+SET client_min_messages TO 'warning';
+SET SEED TO 0.20140702;
+CREATE SCHEMA regtest_custom_scan;
+SET search_path TO regtest_custom_scan, public;
+CREATE TABLE t1 (
+ a int primary key,
+ b float,
+ c text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*10000.0) / 1000.0, md5(i::text) FROM generate_series(1,1000) i);
+VACUUM ANALYZE t1;
+CREATE TABLE t2 (
+ x int primary key,
+ y float,
+ z text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*10000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,1200) i);
+VACUUM ANALYZE t2;
+CREATE TABLE t3 (
+ a int references t1(a),
+ x int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+RESET client_min_messages;
+--
+-- Check Plans if no special extensions are loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+ QUERY PLAN
+---------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+ Filter: (ctid < '(6,0)'::tid)
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+ QUERY PLAN
+------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((c ~~ '%789%'::text) AND (ctid < '(5,0)'::tid))
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+EXPLAIN (costs off)
+ SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+ QUERY PLAN
+--------------------------------------------------------------------------------------
+ Nested Loop
+ -> Hash Join
+ Hash Cond: (t3.a = t1.a)
+ -> Seq Scan on t3
+ Filter: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid))
+ -> Hash
+ -> Seq Scan on t1
+ Filter: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid))
+ -> Index Scan using t2_pkey on t2
+ Index Cond: (x = t3.x)
+ Filter: ((ctid >= '(4,9999)'::tid) AND (ctid <= '(8,0)'::tid))
+(11 rows)
+
+EXPLAIN (costs off, verbose)
+ SELECT count(*), ceil(b)
+ FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+ GROUP BY ceil(b);
+ QUERY PLAN
+----------------------------------------------------------------------------
+ HashAggregate
+ Output: count(*), (ceil(b))
+ Group Key: ceil(t1.b)
+ -> Seq Scan on regtest_custom_scan.t1
+ Output: ceil(b)
+ Filter: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid))
+(6 rows)
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+ QUERY PLAN
+---------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+ Filter: (ctid < '(6,0)'::tid)
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+ QUERY PLAN
+------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: ((c ~~ '%789%'::text) AND (ctid < '(5,0)'::tid))
+ ctid quals: (ctid < '(5,0)'::tid)
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+----------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+ ctid quals: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(3 rows)
+
+EXPLAIN (costs off)
+ SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------------------------------
+ Nested Loop
+ -> Hash Join
+ Hash Cond: (t3.a = t1.a)
+ -> Custom Scan (ctidscan) on t3
+ Filter: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid))
+ ctid quals: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid))
+ -> Hash
+ -> Custom Scan (ctidscan) on t1
+ Filter: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid))
+ ctid quals: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid))
+ -> Index Scan using t2_pkey on t2
+ Index Cond: (x = t3.x)
+ Filter: ((ctid >= '(4,9999)'::tid) AND (ctid <= '(8,0)'::tid))
+(13 rows)
+
+EXPLAIN (costs off, verbose)
+ SELECT count(*), ceil(b)
+ FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+ GROUP BY ceil(b);
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ HashAggregate
+ Output: count(*), (ceil(b))
+ Group Key: ceil(t1.b)
+ -> Custom Scan (ctidscan) on regtest_custom_scan.t1
+ Output: ceil(b)
+ Filter: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid))
+ ctid quals: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid))
+(7 rows)
+
+--
+-- Run the query without EXPLAIN
+--
+SELECT ctid,* FROM t1 WHERE ctid <= '(1,20)'::tid;
+ ctid | a | b | c
+--------+-----+-------+----------------------------------
+ (0,1) | 1 | 2.727 | c4ca4238a0b923820dcc509a6f75849b
+ (0,2) | 2 | 7.903 | c81e728d9d4c2f636f067f89cc14862c
+ (0,3) | 3 | 9.098 | eccbc87e4b5ce2fe28308fd9f2a7baf3
+ (0,4) | 4 | 0.488 | a87ff679a2f3e71d9181a67b7542122c
+ (0,5) | 5 | 4.796 | e4da3b7fbbce2345d7772b0674a318d5
+ (0,6) | 6 | 1.83 | 1679091c5a880faf6fb5e6087eb1b2dc
+ (0,7) | 7 | 5.628 | 8f14e45fceea167a5a36dedd4bea2543
+ (0,8) | 8 | 8.416 | c9f0f895fb98ab9159f51fd0297e236d
+ (0,9) | 9 | 5.311 | 45c48cce2e2d7fbdea1afc51c7c6ad26
+ (0,10) | 10 | 1.431 | d3d9446802a44259755d38e6d163e820
+ (0,11) | 11 | 9.431 | 6512bd43d9caa6e02c990b0a82652dca
+ (0,12) | 12 | 6.578 | c20ad4d76fe97759aa27a0c99bff6710
+ (0,13) | 13 | 0.996 | c51ce410c124a10e0db5e4b97fc2af39
+ (0,14) | 14 | 2.613 | aab3238922bcc25a6f606eb525ffdc56
+ (0,15) | 15 | 8.43 | 9bf31c7ff062936a96d3c8bd1f8f2ff3
+ (0,16) | 16 | 2.64 | c74d97b01eae257e44aa9d5bade97baf
+ (0,17) | 17 | 7.358 | 70efdf2ec9b086079795c442636b55fb
+ (0,18) | 18 | 1.535 | 6f4922f45568161a8cdf4ad2299f6d23
+ (0,19) | 19 | 5.218 | 1f0e3dad99908345f7439f8ffabdffc4
+ (0,20) | 20 | 7.869 | 98f13708210194c475687be6106a3b84
+ (0,21) | 21 | 1.498 | 3c59dc048e8850243be8079a5c74d079
+ (0,22) | 22 | 1.717 | b6d767d2f8ed5d21a44b0e5886680cb9
+ (0,23) | 23 | 9.137 | 37693cfc748049e45d87b8c7d8b9aacd
+ (0,24) | 24 | 6.755 | 1ff1de774005f8da13f42943881c655f
+ (0,25) | 25 | 7.485 | 8e296a067a37563370ded05f5a3bf3ec
+ (0,26) | 26 | 3.362 | 4e732ced3463d06de0ca9a15b6153677
+ (0,27) | 27 | 9.81 | 02e74f10e0327ad868d138f2b4fdd6f0
+ (0,28) | 28 | 0.117 | 33e75ff09dd601bbe69f351039152189
+ (0,29) | 29 | 5.164 | 6ea9ab1baa0efb9e19094440c317e21b
+ (0,30) | 30 | 1.124 | 34173cb38f07f89ddbebc2ac9128303f
+ (0,31) | 31 | 3.356 | c16a5320fa475530d9583c34fd356ef5
+ (0,32) | 32 | 7.89 | 6364d3f0f495b6ab9dcf8d3b5c6e0b01
+ (0,33) | 33 | 9.026 | 182be0c5cdcd5072bb1864cdee4d3d6e
+ (0,34) | 34 | 2.454 | e369853df766fa44e1ed0ff613f563bd
+ (0,35) | 35 | 8.377 | 1c383cd30b7c298ab50293adfecb7b18
+ (0,36) | 36 | 3.822 | 19ca14e7ea6328a42e0eb13d585e4c22
+ (0,37) | 37 | 4.284 | a5bfc9e07964f8dddeb95fc584cd965d
+ (0,38) | 38 | 4.004 | a5771bce93e200c36f7cd9dfd0e5deaa
+ (0,39) | 39 | 2.237 | d67d8ab4f4c10bf22aa353e27879133c
+ (0,40) | 40 | 9.594 | d645920e395fedad7bbbed0eca3fe2e0
+ (0,41) | 41 | 5.435 | 3416a75f4cea9109507cacd8e2f2aefc
+ (0,42) | 42 | 1.668 | a1d0c6e83f027327d8461063f4ac58a6
+ (0,43) | 43 | 6.172 | 17e62166fc8586dfa4d1bc0e1742c08b
+ (0,44) | 44 | 6.43 | f7177163c833dff4b38fc8d2872f1ec6
+ (0,45) | 45 | 4.28 | 6c8349cc7260ae62e3b1396831a8398f
+ (0,46) | 46 | 4.601 | d9d4f495e875a2e075a1a4a6e1b9770f
+ (0,47) | 47 | 9.07 | 67c6a1e7ce56d3d6fa748ab6d9af3fd7
+ (0,48) | 48 | 1.638 | 642e92efb79421734881b53e1e1b18b6
+ (0,49) | 49 | 6.136 | f457c545a9ded88f18ecee47145a72c0
+ (0,50) | 50 | 4.288 | c0c7c76d30bd3dcaefc96f40275bdc0a
+ (0,51) | 51 | 9.506 | 2838023a778dfaecdc212708f721b788
+ (0,52) | 52 | 7.633 | 9a1158154dfa42caddbd0694a4e9bdc8
+ (0,53) | 53 | 6.005 | d82c8d1619ad8176d665453cfb2e55f0
+ (0,54) | 54 | 8.643 | a684eceee76fc522773286a895bc8436
+ (0,55) | 55 | 4.387 | b53b3a3d6ab90ce0268229151c9bde11
+ (0,56) | 56 | 3.489 | 9f61408e3afb633e50cdf1b20de6f466
+ (0,57) | 57 | 2.005 | 72b32a1f754ba1c09b3695e0cb6cde7f
+ (0,58) | 58 | 4.196 | 66f041e16a60928b05a7e228a89c3799
+ (0,59) | 59 | 3.606 | 093f65e080a295f8076b1c5722a46aa2
+ (0,60) | 60 | 7.168 | 072b030ba126b2f4b2374f342be9ed44
+ (0,61) | 61 | 5.32 | 7f39f8317fbdb1988ef4c628eba02591
+ (0,62) | 62 | 6.961 | 44f683a84163b3523afe57c2e008bc8c
+ (0,63) | 63 | 5.058 | 03afdbd66e7929b125f8597834fa83a4
+ (0,64) | 64 | 4.345 | ea5d2f1c4608232e07d3aa3d998e5135
+ (0,65) | 65 | 9.415 | fc490ca45c00b1249bbe3554a4fdf6fb
+ (0,66) | 66 | 3.435 | 3295c76acbf4caaed33c36b1b5fc2cb1
+ (0,67) | 67 | 8.166 | 735b90b4568125ed6c3f678819b6e058
+ (0,68) | 68 | 3.698 | a3f390d88e4c41f2747bfa2f1b5f87db
+ (0,69) | 69 | 7.438 | 14bfa6bb14875e45bba028a21ed38046
+ (0,70) | 70 | 0.403 | 7cbbc409ec990f19c78c75bd1e06f215
+ (0,71) | 71 | 3.292 | e2c420d928d4bf8ce0ff2ec19b371514
+ (0,72) | 72 | 2.873 | 32bb90e8976aab5298d5da10fe66f21d
+ (0,73) | 73 | 2.07 | d2ddea18f00665ce8623e36bd4e3c7c5
+ (0,74) | 74 | 9.463 | ad61ab143223efbc24c7d2583be69251
+ (0,75) | 75 | 9.303 | d09bf41544a3365a46c9077ebb5e35c3
+ (0,76) | 76 | 6.35 | fbd7939d674997cdb4692d34de8633c4
+ (0,77) | 77 | 4.064 | 28dd2c7955ce926456240b2ff0100bde
+ (0,78) | 78 | 8.373 | 35f4a8d465e6e1edc05f3d8ab658c551
+ (0,79) | 79 | 7.987 | d1fe173d08e959397adf34b1d77e88d7
+ (0,80) | 80 | 0.199 | f033ab37c30201f73f142449d037028d
+ (0,81) | 81 | 2.66 | 43ec517d68b6edd3015b3edc9a11367b
+ (0,82) | 82 | 7.493 | 9778d5d219c5080b9a6a17bef029331c
+ (0,83) | 83 | 7.831 | fe9fc289c3ff0af142b6d3bead98a923
+ (0,84) | 84 | 8.664 | 68d30a9594728bc39aa24be94b319d21
+ (0,85) | 85 | 6.136 | 3ef815416f775098fe977004015c6193
+ (0,86) | 86 | 2.218 | 93db85ed909c13838ff95ccfa94cebd9
+ (0,87) | 87 | 2.152 | c7e1249ffc03eb9ded908c236bd1996d
+ (0,88) | 88 | 8.14 | 2a38a4a9316c49e5a833517c45d31070
+ (0,89) | 89 | 6.414 | 7647966b7343c29048673252e490f736
+ (0,90) | 90 | 5.757 | 8613985ec49eb8f757ae6439e879bb2a
+ (0,91) | 91 | 5.307 | 54229abfcfa5649e7003b83dd4755294
+ (0,92) | 92 | 1.733 | 92cc227532d17e56e07902b254dfad10
+ (0,93) | 93 | 2.718 | 98dce83da57b0395e163467c9dae521b
+ (0,94) | 94 | 0.365 | f4b9ec30ad9f68f89b29639786cb62ef
+ (0,95) | 95 | 6.077 | 812b4ba287f5ee0bc9d43bbf5bbe87fb
+ (0,96) | 96 | 2.132 | 26657d5ff9020d2abefe558796b99584
+ (0,97) | 97 | 3.799 | e2ef524fbf3d9fe611d5a8e90fefdc9c
+ (1,1) | 98 | 4.243 | ed3d2c21991e3bef5e069713af9fa6ca
+ (1,2) | 99 | 5.83 | ac627ab1ccbdb62ec96e702f07f6425b
+ (1,3) | 100 | 1.237 | f899139df5e1059396431415e770c6dd
+ (1,4) | 101 | 4.645 | 38b3eff8baf56627478ec76a704e9b52
+ (1,5) | 102 | 9.121 | ec8956637a99787bd197eacd77acce5e
+ (1,6) | 103 | 4.109 | 6974ce5ac660610b44d9b9fed0ff9548
+ (1,7) | 104 | 6.715 | c9e1074f5b3f9fc8ea15d152add07294
+ (1,8) | 105 | 8.583 | 65b9eea6e1cc6bb9f0cd2a47751a186f
+ (1,9) | 106 | 3.412 | f0935e4cd5920aa6c7c996a5ee53a70f
+ (1,10) | 107 | 3.065 | a97da629b098b75c294dffdc3e463904
+ (1,11) | 108 | 2.646 | a3c65c2974270fd093ee8a9bf8ae7d0b
+ (1,12) | 109 | 1.784 | 2723d092b63885e0d7c260cc007e8b9d
+ (1,13) | 110 | 1.052 | 5f93f983524def3dca464469d2cf9f3e
+ (1,14) | 111 | 2.845 | 698d51a19d8a121ce581499d7b701668
+ (1,15) | 112 | 4.443 | 7f6ffaa6bb0b408017b62254211691b5
+ (1,16) | 113 | 8.544 | 73278a4a86960eeb576a8fd4c9ec6997
+ (1,17) | 114 | 0.675 | 5fd0b37cd7dbbb00f97ba6ce92bf5add
+ (1,18) | 115 | 3.107 | 2b44928ae11fb9384c4cf38708677c48
+ (1,19) | 116 | 4.68 | c45147dee729311ef5b5c3003946c48f
+ (1,20) | 117 | 2.893 | eb160de1de89d9058fcb0b968dbbbd68
+(117 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid > '(10,0)'::tid;
+ ctid | a | b | c
+---------+------+-------+----------------------------------
+ (10,1) | 971 | 3.963 | 6602294be910b1e3c4571bd98c4d5484
+ (10,2) | 972 | 7.836 | c22abfa379f38b5b0411bc11fa9bf92f
+ (10,3) | 973 | 2.017 | ca75910166da03ff9d4655a0338e6b09
+ (10,4) | 974 | 3.717 | 4311359ed4969e8401880e3c1836fbe1
+ (10,5) | 975 | 6.5 | 92977ae4d2ba21425a59afb269c2a14e
+ (10,6) | 976 | 2.317 | 9c01802ddb981e6bcfbec0f0516b8e35
+ (10,7) | 977 | 0.112 | cc1aa436277138f61cda703991069eaf
+ (10,8) | 978 | 8.832 | 2ab56412b1163ee131e1246da0955bd1
+ (10,9) | 979 | 4.022 | c32d9bf27a3da7ec8163957080c8628e
+ (10,10) | 980 | 6.216 | d79aac075930c83c2f1e369a511148fe
+ (10,11) | 981 | 2.501 | 287e03db1d99e0ec2edb90d079e142f3
+ (10,12) | 982 | 1.32 | fec8d47d412bcbeece3d9128ae855a7a
+ (10,13) | 983 | 7.567 | 6aab1270668d8cac7cef2566a1c5f569
+ (10,14) | 984 | 7.962 | d93ed5b6db83be78efb0d05ae420158e
+ (10,15) | 985 | 6.823 | 54a367d629152b720749e187b3eaa11b
+ (10,16) | 986 | 5.97 | fe7ee8fc1959cc7214fa21c4840dff0a
+ (10,17) | 987 | 1.539 | df6d2338b2b8fce1ec2f6dda0a630eb0
+ (10,18) | 988 | 5.619 | 9908279ebbf1f9b250ba689db6a0222b
+ (10,19) | 989 | 7.603 | a1140a3d0df1c81e24ae954d935e8926
+ (10,20) | 990 | 8.716 | 4fac9ba115140ac4f1c22da82aa0bc7f
+ (10,21) | 991 | 2.868 | 692f93be8c7a41525c0baf2076aecfb4
+ (10,22) | 992 | 7.804 | 860320be12a1c050cd7731794e231bd3
+ (10,23) | 993 | 9.428 | 7b13b2203029ed80337f27127a9f1d28
+ (10,24) | 994 | 7.015 | 934815ad542a4a7c5e8a2dfa04fea9f5
+ (10,25) | 995 | 2.438 | 2bcab9d935d219641434683dd9d18a03
+ (10,26) | 996 | 7.22 | 0b8aff0438617c055eb55f0ba5d226fa
+ (10,27) | 997 | 5.012 | ec5aa0b7846082a2415f0902f0da88f2
+ (10,28) | 998 | 2.945 | 9ab0d88431732957a618d4a469a0d4c3
+ (10,29) | 999 | 2.569 | b706835de79a2b4e80506f582af3676a
+ (10,30) | 1000 | 2.776 | a9b7ba70783b617e9998dc4dd82eb3c5
+(30 rows)
+
+SELECT ctid,* FROM t1 WHERE c like '%678%' AND ctid >= '(3,50)'::tid;
+ ctid | a | b | c
+--------+-----+-------+----------------------------------
+ (6,78) | 660 | 2.946 | 68264bdb65b97eeae6788aa3348e553c
+ (7,43) | 722 | 1.612 | c8ed21db4f678f3b13b9d5ee16489088
+ (8,36) | 812 | 3.673 | 81e74d678581a3bb7a720b019f4f1a93
+ (9,97) | 970 | 7.977 | 89fcd07f20b6785b92134bd6c1d0fa42
+(4 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ ctid | a | b | c
+--------+-----+-------+----------------------------------
+ (3,1) | 292 | 3.11 | 1700002963a49da13542e0726b7bb758
+ (3,2) | 293 | 4.932 | 53c3bce66e43be4f209556518c2fcb54
+ (3,3) | 294 | 3.851 | 6883966fd8f918a4aa29be29d2c386fb
+ (3,4) | 295 | 9.821 | 49182f81e6a13cf5eaa496d51fea6406
+ (3,5) | 296 | 1.623 | d296c101daa88a51f6ca8cfc1ac79b50
+ (3,6) | 297 | 9.248 | 9fd81843ad7f202f26c1a174c7357585
+ (3,7) | 298 | 8.073 | 26e359e83860db1d11b6acca57d8ea88
+ (3,8) | 299 | 5.224 | ef0d3930a7b6c95bd2b32ed45989c61f
+ (3,9) | 300 | 4.252 | 94f6d7e04a4d452035300f18b984988c
+ (3,10) | 301 | 4.599 | 34ed066df378efacc9b924ec161e7639
+(10 rows)
+
+SELECT t1.ctid,* FROM t1 NATURAL JOIN t3 WHERE t3.ctid IN (
+ SELECT t3.ctid FROM t2 NATURAL JOIN t3
+ WHERE t2.ctid BETWEEN '(4,0)'::tid AND '(5,0)'::tid);
+ ctid | a | b | c | x
+---------+------+-------+----------------------------------+-----
+ (5,43) | 528 | 6.581 | f4be00279ee2e0a53eafdaa94a151e2c | 646
+ (4,67) | 455 | 4.602 | 821fa74b50ba3f7cba1e6c53e8fa6845 | 682
+ (4,96) | 484 | 5.29 | eba0dc302bcd9a273f8bbb72be3a687b | 632
+ (3,82) | 373 | 4.013 | ffd52f3c7e12435a724a8f30fddadd9c | 647
+ (0,95) | 95 | 6.077 | 812b4ba287f5ee0bc9d43bbf5bbe87fb | 625
+ (2,48) | 242 | 5.099 | e4a6222cdb5b34375400904f03d8e6a5 | 600
+ (0,79) | 79 | 7.987 | d1fe173d08e959397adf34b1d77e88d7 | 685
+ (9,91) | 964 | 4.635 | 8065d07da4a77621450aa84fee5656d9 | 659
+ (4,68) | 456 | 7.784 | 250cf8b51c773f3f8dc8b4be867a9a02 | 606
+ (3,7) | 298 | 8.073 | 26e359e83860db1d11b6acca57d8ea88 | 630
+ (3,95) | 386 | 8.354 | 39461a19e9eddfb385ea76b26521ea48 | 653
+ (4,36) | 424 | 2.163 | 3c7781a36bcd6cf08c11a970fbe0e2a6 | 682
+ (3,53) | 344 | 6.972 | b3967a0e938dc2a6340e258630febd5a | 598
+ (5,90) | 575 | 5.791 | ffeabd223de0d4eacb9a3e6e53e5448d | 647
+ (5,70) | 555 | 7.829 | 15de21c670ae7c3f6f3f1f37029303c9 | 675
+ (8,39) | 815 | 9.544 | 71ad16ad2c4d81f348082ff6c4b20768 | 679
+ (2,81) | 275 | 0.633 | 63923f49e5241343aa7acb6a06a751e7 | 665
+ (9,79) | 952 | 1.352 | e6cb2a3c14431b55aa50c06529eaa21b | 674
+ (2,71) | 265 | 6.691 | e56954b4f6347e897f954495eab16a88 | 645
+ (2,78) | 272 | 7.8 | 7a614fd06c325499f1680b9896beedeb | 676
+ (8,92) | 868 | 9.159 | dd45045f8c68db9f54e70c67048d32e8 | 660
+ (5,61) | 546 | 8.622 | ed265bc903a5a097f61d3ec064d96d2e | 677
+ (6,53) | 635 | 1.307 | 6a10bbd480e4c5573d8f3af73ae0454b | 678
+ (7,7) | 686 | 9.864 | 109a0ca3bc27f3e96597370d5c8cf03d | 675
+ (0,30) | 30 | 1.124 | 34173cb38f07f89ddbebc2ac9128303f | 684
+ (2,80) | 274 | 3.661 | d947bf06a885db0d477d707121934ff8 | 660
+ (9,57) | 930 | 8.568 | 1cc3633c579a90cfdd895e64021e2163 | 676
+ (5,59) | 544 | 1.784 | 97e8527feaf77a97fc38f34216141515 | 628
+ (5,34) | 519 | 8.643 | 63538fe6ef330c13a05a3ed7e599d5f7 | 644
+ (0,41) | 41 | 5.435 | 3416a75f4cea9109507cacd8e2f2aefc | 616
+ (6,9) | 591 | 4.95 | 3493894fa4ea036cfc6433c3e2ee63b0 | 624
+ (5,68) | 553 | 3.933 | f387624df552cea2f369918c5e1e12bc | 656
+ (6,13) | 595 | 4.497 | 04ecb1fa28506ccb6f72b12c0245ddbc | 640
+ (5,92) | 577 | 0.858 | fde9264cf376fffe2ee4ddf4a988880d | 595
+ (8,96) | 872 | 3.613 | 43feaeeecd7b2fe2ae2e26d917b6477d | 675
+ (0,14) | 14 | 2.613 | aab3238922bcc25a6f606eb525ffdc56 | 627
+ (6,13) | 595 | 4.497 | 04ecb1fa28506ccb6f72b12c0245ddbc | 675
+ (7,44) | 723 | 7.51 | 08419be897405321542838d77f855226 | 601
+ (7,12) | 691 | 1.983 | 10a5ab2db37feedfdeaab192ead4ac0e | 633
+ (9,66) | 939 | 7.47 | 3df1d4b96d8976ff5986393e8767f5b2 | 601
+ (5,65) | 550 | 1.335 | 01f78be6f7cad02658508fe4616098a9 | 674
+ (10,13) | 983 | 7.567 | 6aab1270668d8cac7cef2566a1c5f569 | 640
+ (4,69) | 457 | 6.687 | 42998cf32d552343bc8e460416382dca | 655
+ (6,46) | 628 | 4.6 | 42e77b63637ab381e8be5f8318cc28a2 | 596
+ (9,95) | 968 | 5.349 | 8f468c873a32bb0619eaeb2050ba45d1 | 634
+ (6,36) | 618 | 4.784 | eb6fdc36b281b7d5eabf33396c2683a2 | 597
+ (1,92) | 189 | 3.371 | a2557a7b2e94197ff767970b67041697 | 645
+ (10,23) | 993 | 9.428 | 7b13b2203029ed80337f27127a9f1d28 | 670
+ (7,62) | 741 | 9.252 | 2e65f2f2fdaf6c699b223c61b1b5ab89 | 609
+ (0,8) | 8 | 8.416 | c9f0f895fb98ab9159f51fd0297e236d | 636
+ (8,33) | 809 | 6.587 | 32b30a250abd6331e03a2a1f16466346 | 624
+ (9,76) | 949 | 6.105 | 3cef96dcc9b8035d23f69e30bb19218a | 656
+ (9,50) | 923 | 8.205 | c4015b7f368e6b4871809f49debe0579 | 685
+ (7,11) | 690 | 1.344 | c06d06da9666a219db15cf575aff2824 | 664
+ (9,68) | 941 | 0.072 | 92262bf907af914b95a0fc33c3f33bf6 | 638
+ (3,46) | 337 | 3.233 | 357a6fdf7642bf815a88822c447d9dc4 | 657
+ (1,53) | 150 | 2.874 | 7ef605fc8dba5425d6965fbd4c8fbe1f | 684
+ (0,24) | 24 | 6.755 | 1ff1de774005f8da13f42943881c655f | 626
+ (8,76) | 852 | 4.716 | 22ac3c5a5bf0b520d281c122d1490650 | 593
+ (2,91) | 285 | 1.867 | 0e01938fc48a2cfb5f2217fbfb00722d | 603
+ (0,68) | 68 | 3.698 | a3f390d88e4c41f2747bfa2f1b5f87db | 659
+ (5,89) | 574 | 2.237 | f0e52b27a7a5d6a1a87373dffa53dbe5 | 657
+ (8,6) | 782 | 0.276 | 72da7fd6d1302c0a159f6436d01e9eb0 | 625
+ (9,89) | 962 | 0.712 | 5c936263f3428a40227908d5a3847c0b | 660
+ (3,44) | 335 | 5.056 | f9b902fc3289af4dd08de5d1de54f68f | 600
+ (7,86) | 765 | 2.005 | d840cc5d906c3e9c84374c8919d2074e | 679
+ (7,28) | 707 | 9.357 | 500e75a036dc2d7d2fec5da1b71d36cc | 624
+ (1,90) | 187 | 7.392 | 31fefc0e570cb3860f2a6d4b38c6490d | 604
+ (9,60) | 933 | 4.434 | 043c3d7e489c69b48737cc0c92d0f3a2 | 658
+ (8,40) | 816 | 0.334 | 43fa7f58b7eac7ac872209342e62e8f1 | 613
+ (3,66) | 357 | 7.196 | fb7b9ffa5462084c5f4e7e85a093e6d7 | 675
+ (2,6) | 200 | 9.843 | 3644a684f98ea8fe223c713b77189a77 | 630
+ (5,29) | 514 | 2 | 59b90e1005a220e2ebc542eb9d950b1e | 662
+ (0,35) | 35 | 8.377 | 1c383cd30b7c298ab50293adfecb7b18 | 617
+ (5,8) | 493 | 9.349 | 2f55707d4193dc27118a0f19a1985716 | 678
+ (9,76) | 949 | 6.105 | 3cef96dcc9b8035d23f69e30bb19218a | 613
+ (7,58) | 737 | 0.95 | a5cdd4aa0048b187f7182f1b9ce7a6a7 | 671
+ (3,28) | 319 | 7.195 | 8d3bba7425e7c98c50f52ca1b52d3735 | 627
+ (0,85) | 85 | 6.136 | 3ef815416f775098fe977004015c6193 | 598
+ (9,26) | 899 | 5.338 | 01882513d5fa7c329e940dda99b12147 | 623
+ (2,45) | 239 | 8.274 | 555d6702c950ecb729a966504af0a635 | 609
+ (4,34) | 422 | 1.542 | f85454e8279be180185cac7d243c5eb3 | 618
+ (2,36) | 230 | 1.073 | 6da9003b743b65f4c0ccd295cc484e57 | 646
+ (8,46) | 822 | 5.911 | afda332245e2af431fb7b672a68b659d | 668
+ (9,53) | 926 | 3.294 | cbb6a3b884f4f88b3a8e3d44c636cbd8 | 611
+ (8,13) | 789 | 3.511 | 68053af2923e00204c3ca7c6a3150cf7 | 668
+ (3,60) | 351 | 4.695 | efe937780e95574250dabe07151bdc23 | 596
+ (9,56) | 929 | 8.454 | 0d0871f0806eae32d30983b62252da50 | 620
+ (8,48) | 824 | 0.742 | 677e09724f0e2df9b6c000b75b5da10d | 679
+ (0,21) | 21 | 1.498 | 3c59dc048e8850243be8079a5c74d079 | 607
+ (2,36) | 230 | 1.073 | 6da9003b743b65f4c0ccd295cc484e57 | 658
+ (3,12) | 303 | 2.051 | 11b9842e0a271ff252c1903e7132cd68 | 640
+ (8,38) | 814 | 5.229 | 96b9bff013acedfb1d140579e2fbeb63 | 659
+ (10,28) | 998 | 2.945 | 9ab0d88431732957a618d4a469a0d4c3 | 619
+ (7,81) | 760 | 4.54 | 2ca65f58e35d9ad45bf7f3ae5cfd08f1 | 671
+ (0,5) | 5 | 4.796 | e4da3b7fbbce2345d7772b0674a318d5 | 593
+ (6,81) | 663 | 2.433 | 8757150decbd89b0f5442ca3db4d0e0e | 646
+ (3,2) | 293 | 4.932 | 53c3bce66e43be4f209556518c2fcb54 | 682
+ (7,69) | 748 | 0.086 | e49b8b4053df9505e1f48c3a701c0682 | 617
+ (3,47) | 338 | 1.571 | 819f46e52c25763a55cc642422644317 | 617
+ (8,71) | 847 | 3.652 | f4552671f8909587cf485ea990207f3b | 637
+ (1,40) | 137 | 1.045 | 3988c7f88ebcb58c6ce932b957b6f332 | 600
+ (8,56) | 832 | 3.089 | 7250eb93b3c18cc9daa29cf58af7a004 | 606
+ (0,50) | 50 | 4.288 | c0c7c76d30bd3dcaefc96f40275bdc0a | 601
+ (8,25) | 801 | 2.903 | 1905aedab9bf2477edc068a355bba31a | 642
+ (0,87) | 87 | 2.152 | c7e1249ffc03eb9ded908c236bd1996d | 631
+ (8,61) | 837 | 4.784 | b0b183c207f46f0cca7dc63b2604f5cc | 669
+ (0,73) | 73 | 2.07 | d2ddea18f00665ce8623e36bd4e3c7c5 | 663
+ (9,61) | 934 | 7.081 | 4daa3db355ef2b0e64b472968cb70f0d | 684
+ (3,79) | 370 | 8.387 | d709f38ef758b5066ef31b18039b8ce5 | 674
+ (10,4) | 974 | 3.717 | 4311359ed4969e8401880e3c1836fbe1 | 670
+ (5,83) | 568 | 8.956 | dd458505749b2941217ddd59394240e8 | 683
+ (9,50) | 923 | 8.205 | c4015b7f368e6b4871809f49debe0579 | 663
+ (8,79) | 855 | 5.458 | addfa9b7e234254d26e9c7f2af1005cb | 628
+ (1,41) | 138 | 5.781 | 013d407166ec4fa56eb1e1f8cbe183b9 | 669
+ (7,3) | 682 | 8.117 | 08d98638c6fcd194a4b1e6992063e944 | 610
+ (10,3) | 973 | 2.017 | ca75910166da03ff9d4655a0338e6b09 | 640
+ (9,58) | 931 | 3.535 | 9f53d83ec0691550f7d2507d57f4f5a2 | 634
+ (1,42) | 139 | 2.042 | e00da03b685a0dd18fb6a08af0923de0 | 618
+ (3,16) | 307 | 6.515 | 8e98d81f8217304975ccb23337bb5761 | 631
+ (3,9) | 300 | 4.252 | 94f6d7e04a4d452035300f18b984988c | 653
+ (3,86) | 377 | 9.915 | d34ab169b70c9dcd35e62896010cd9ff | 599
+ (8,37) | 813 | 4.154 | e0cf1f47118daebc5b16269099ad7347 | 627
+ (3,29) | 320 | 3.232 | 320722549d1751cf3f247855f937b982 | 657
+ (6,31) | 613 | 9.934 | f29c21d4897f78948b91f03172341b7b | 640
+ (9,51) | 924 | 7.052 | bea5955b308361a1b07bc55042e25e54 | 649
+ (2,38) | 232 | 2.033 | be83ab3ecd0db773eb2dc1b0a17836a1 | 659
+ (7,84) | 763 | 6.188 | eefc9e10ebdc4a2333b42b2dbb8f27b6 | 669
+ (6,91) | 673 | 2.212 | 9f396fe44e7c05c16873b05ec425cbad | 596
+ (1,17) | 114 | 0.675 | 5fd0b37cd7dbbb00f97ba6ce92bf5add | 673
+ (7,58) | 737 | 0.95 | a5cdd4aa0048b187f7182f1b9ce7a6a7 | 635
+ (10,30) | 1000 | 2.776 | a9b7ba70783b617e9998dc4dd82eb3c5 | 674
+ (9,40) | 913 | 8.593 | 8b5040a8a5baf3e0e67386c2e3a9b903 | 654
+ (0,35) | 35 | 8.377 | 1c383cd30b7c298ab50293adfecb7b18 | 597
+ (2,48) | 242 | 5.099 | e4a6222cdb5b34375400904f03d8e6a5 | 671
+ (5,75) | 560 | 6.777 | a9a6653e48976138166de32772b1bf40 | 623
+ (1,23) | 120 | 9.306 | da4fb5c6e93e74d3df8527599fa62642 | 637
+ (6,90) | 672 | 0.675 | 2dea61eed4bceec564a00115c4d21334 | 608
+ (4,42) | 430 | 4.709 | f74909ace68e51891440e4da0b65a70c | 667
+ (10,1) | 971 | 3.963 | 6602294be910b1e3c4571bd98c4d5484 | 683
+ (2,57) | 251 | 3.423 | 19f3cd308f1455b3fa09a282e0d496f4 | 641
+ (2,66) | 260 | 7.864 | a4f23670e1833f3fdb077ca70bbd5d66 | 677
+ (7,32) | 711 | 1.699 | 6081594975a764c8e3a691fa2b3a321d | 682
+ (8,2) | 778 | 9.912 | e07413354875be01a996dc560274708e | 607
+ (7,89) | 768 | 2.954 | 3a835d3215755c435ef4fe9965a3f2a0 | 644
+ (9,50) | 923 | 8.205 | c4015b7f368e6b4871809f49debe0579 | 625
+ (6,31) | 613 | 9.934 | f29c21d4897f78948b91f03172341b7b | 633
+ (8,95) | 871 | 0.53 | aeb3135b436aa55373822c010763dd54 | 636
+ (0,64) | 64 | 4.345 | ea5d2f1c4608232e07d3aa3d998e5135 | 652
+ (8,82) | 858 | 9.043 | a67f096809415ca1c9f112d96d27689b | 660
+ (4,42) | 430 | 4.709 | f74909ace68e51891440e4da0b65a70c | 657
+ (4,84) | 472 | 2.045 | ef575e8837d065a1683c022d2077d342 | 672
+ (4,79) | 467 | 7.696 | ab817c9349cf9c4f6877e1894a1faa00 | 612
+ (4,63) | 451 | 9.186 | 941e1aaaba585b952b62c14a3a175a61 | 618
+ (2,71) | 265 | 6.691 | e56954b4f6347e897f954495eab16a88 | 653
+ (7,12) | 691 | 1.983 | 10a5ab2db37feedfdeaab192ead4ac0e | 598
+ (6,4) | 586 | 0.092 | 605ff764c617d3cd28dbbdd72be8f9a2 | 661
+ (1,28) | 125 | 8.49 | 3def184ad8f4755ff269862ea77393dd | 637
+ (2,94) | 288 | 6.092 | 48aedb8880cab8c45637abc7493ecddd | 676
+ (8,40) | 816 | 0.334 | 43fa7f58b7eac7ac872209342e62e8f1 | 605
+ (0,38) | 38 | 4.004 | a5771bce93e200c36f7cd9dfd0e5deaa | 662
+ (2,16) | 210 | 3.191 | 6f3ef77ac0e3619e98159e9b6febf557 | 645
+ (3,51) | 342 | 5.627 | 58238e9ae2dd305d79c2ebc8c1883422 | 635
+ (0,64) | 64 | 4.345 | ea5d2f1c4608232e07d3aa3d998e5135 | 644
+ (2,31) | 225 | 8.138 | d1c38a09acc34845c6be3a127a5aacaf | 630
+ (0,65) | 65 | 9.415 | fc490ca45c00b1249bbe3554a4fdf6fb | 612
+ (6,37) | 619 | 0.825 | cdc0d6e63aa8e41c89689f54970bb35f | 638
+ (0,6) | 6 | 1.83 | 1679091c5a880faf6fb5e6087eb1b2dc | 661
+ (1,13) | 110 | 1.052 | 5f93f983524def3dca464469d2cf9f3e | 608
+ (3,74) | 365 | 4.71 | 9be40cee5b0eee1462c82c6964087ff9 | 591
+ (5,47) | 532 | 1.293 | 298f95e1bf9136124592c8d4825a06fc | 649
+ (0,39) | 39 | 2.237 | d67d8ab4f4c10bf22aa353e27879133c | 676
+ (2,69) | 263 | 9.896 | 8c19f571e251e61cb8dd3612f26d5ecf | 652
+ (8,33) | 809 | 6.587 | 32b30a250abd6331e03a2a1f16466346 | 608
+ (8,47) | 823 | 2.887 | 632cee946db83e7a52ce5e8d6f0fed35 | 592
+ (8,62) | 838 | 5.287 | f9028faec74be6ec9b852b0a542e2f39 | 620
+ (2,12) | 206 | 2.416 | 7eabe3a1649ffa2b3ff8c02ebfd5659f | 627
+ (0,10) | 10 | 1.431 | d3d9446802a44259755d38e6d163e820 | 655
+ (7,15) | 694 | 4.415 | 5487315b1286f907165907aa8fc96619 | 600
+ (0,39) | 39 | 2.237 | d67d8ab4f4c10bf22aa353e27879133c | 646
+ (7,63) | 742 | 8.773 | e94550c93cd70fe748e6982b3439ad3b | 624
+ (8,57) | 833 | 4.053 | 013a006f03dbc5392effeb8f18fda755 | 670
+ (6,34) | 616 | 2.651 | 7750ca3559e5b8e1f44210283368fc16 | 617
+ (4,74) | 462 | 3.655 | 51d92be1c60d1db1d2e5e7a07da55b26 | 606
+ (7,46) | 725 | 6.027 | 82f2b308c3b01637c607ce05f52a2fed | 684
+ (0,88) | 88 | 8.14 | 2a38a4a9316c49e5a833517c45d31070 | 594
+ (3,73) | 364 | 5.908 | bac9162b47c56fc8a4d2a519803d51b3 | 646
+ (2,48) | 242 | 5.099 | e4a6222cdb5b34375400904f03d8e6a5 | 637
+ (2,30) | 224 | 2.281 | 13fe9d84310e77f13a6d184dbf1232f3 | 615
+ (6,18) | 600 | 7.89 | d490d7b4576290fa60eb31b5fc917ad1 | 612
+ (7,32) | 711 | 1.699 | 6081594975a764c8e3a691fa2b3a321d | 651
+ (10,15) | 985 | 6.823 | 54a367d629152b720749e187b3eaa11b | 609
+ (7,65) | 744 | 6.956 | 0537fb40a68c18da59a35c2bfe1ca554 | 605
+ (6,22) | 604 | 1.82 | 9cf81d8026a9018052c429cc4e56739b | 608
+ (0,26) | 26 | 3.362 | 4e732ced3463d06de0ca9a15b6153677 | 649
+ (3,89) | 380 | 5.038 | bca82e41ee7b0833588399b1fcd177c7 | 631
+ (7,39) | 718 | 1.134 | 50c3d7614917b24303ee6a220679dab3 | 668
+ (0,30) | 30 | 1.124 | 34173cb38f07f89ddbebc2ac9128303f | 669
+ (7,51) | 730 | 7.128 | d5cfead94f5350c12c322b5b664544c1 | 592
+ (5,56) | 541 | 1.924 | 16c222aa19898e5058938167c8ab6c57 | 605
+ (8,20) | 796 | 2.844 | 35cf8659cfcb13224cbd47863a34fc58 | 600
+ (0,22) | 22 | 1.717 | b6d767d2f8ed5d21a44b0e5886680cb9 | 654
+ (2,78) | 272 | 7.8 | 7a614fd06c325499f1680b9896beedeb | 643
+ (8,23) | 799 | 5.797 | 28267ab848bcf807b2ed53c3a8f8fc8a | 658
+ (10,11) | 981 | 2.501 | 287e03db1d99e0ec2edb90d079e142f3 | 615
+ (2,75) | 269 | 5.004 | 06138bc5af6023646ede0e1f7c1eac75 | 642
+ (2,57) | 251 | 3.423 | 19f3cd308f1455b3fa09a282e0d496f4 | 616
+ (1,22) | 119 | 2.819 | 07e1cd7dca89a1678042477183b7ac3f | 607
+ (9,8) | 881 | 0.085 | 7504adad8bb96320eb3afdd4df6e1f60 | 607
+ (3,27) | 318 | 9.977 | 432aca3a1e345e339f35a30c8f65edce | 648
+ (9,23) | 896 | 6.179 | 061412e4a03c02f9902576ec55ebbe77 | 652
+ (0,55) | 55 | 4.387 | b53b3a3d6ab90ce0268229151c9bde11 | 641
+ (9,45) | 918 | 9.71 | 1e056d2b0ebd5c878c550da6ac5d3724 | 601
+ (5,90) | 575 | 5.791 | ffeabd223de0d4eacb9a3e6e53e5448d | 612
+ (8,55) | 831 | 3.798 | e0ec453e28e061cc58ac43f91dc2f3f0 | 637
+ (0,31) | 31 | 3.356 | c16a5320fa475530d9583c34fd356ef5 | 609
+ (6,3) | 585 | 2.718 | a9a1d5317a33ae8cef33961c34144f84 | 613
+ (2,47) | 241 | 2.797 | f340f1b1f65b6df5b5e3f94d95b11daf | 671
+ (5,88) | 573 | 9.128 | e5f6ad6ce374177eef023bf5d0c018b6 | 628
+ (0,95) | 95 | 6.077 | 812b4ba287f5ee0bc9d43bbf5bbe87fb | 625
+ (3,22) | 313 | 9.237 | 158f3069a435b314a80bdcb024f8e422 | 636
+ (5,4) | 489 | 5.183 | 854d9fca60b4bd07f9bb215d59ef5561 | 596
+ (5,54) | 539 | 6.363 | 5737034557ef5b8c02c0e46513b98f90 | 657
+ (6,12) | 594 | 3.02 | 076a0c97d09cf1a0ec3e19c7f2529f2b | 647
+ (2,72) | 266 | 5.398 | f7664060cc52bc6f3d620bcedc94a4b6 | 602
+ (7,24) | 703 | 8.668 | d6c651ddcd97183b2e40bc464231c962 | 621
+ (3,81) | 372 | 4.623 | 24b16fede9a67c9251d3e7c7161c83ac | 615
+ (0,35) | 35 | 8.377 | 1c383cd30b7c298ab50293adfecb7b18 | 644
+ (2,24) | 218 | 8.642 | e96ed478dab8595a7dbda4cbcbee168f | 604
+ (0,8) | 8 | 8.416 | c9f0f895fb98ab9159f51fd0297e236d | 671
+ (3,21) | 312 | 6.709 | 950a4152c2b4aa3ad78bdd6b366cc179 | 643
+ (8,49) | 825 | 2.157 | d554f7bb7be44a7267068a7df88ddd20 | 676
+ (3,49) | 340 | 5.059 | 40008b9a5380fcacce3976bf7c08af5b | 653
+ (6,56) | 638 | 6.102 | 4c27cea8526af8cfee3be5e183ac9605 | 617
+ (4,37) | 425 | 5.531 | 25b2822c2f5a3230abfadd476e8b04c9 | 621
+ (0,43) | 43 | 6.172 | 17e62166fc8586dfa4d1bc0e1742c08b | 634
+ (8,47) | 823 | 2.887 | 632cee946db83e7a52ce5e8d6f0fed35 | 619
+ (6,89) | 671 | 0.604 | 5dd9db5e033da9c6fb5ba83c7a7ebea9 | 639
+ (4,92) | 480 | 4.075 | 6ea2ef7311b482724a9b7b0bc0dd85c6 | 620
+ (3,92) | 383 | 2.874 | beed13602b9b0e6ecb5b568ff5058f07 | 633
+ (2,95) | 289 | 5.666 | 839ab46820b524afda05122893c2fe8e | 629
+ (7,62) | 741 | 9.252 | 2e65f2f2fdaf6c699b223c61b1b5ab89 | 654
+ (2,45) | 239 | 8.274 | 555d6702c950ecb729a966504af0a635 | 629
+ (4,72) | 460 | 6.222 | 98b297950041a42470269d56260243a1 | 672
+ (6,32) | 614 | 0.956 | 851ddf5058cf22df63d3344ad89919cf | 677
+ (2,77) | 271 | 6.958 | 7f100b7b36092fb9b06dfb4fac360931 | 679
+ (8,60) | 836 | 7.597 | ab88b15733f543179858600245108dd8 | 643
+ (7,4) | 683 | 4.026 | 24681928425f5a9133504de568f5f6df | 639
+ (4,21) | 409 | 2.081 | a96b65a721e561e1e3de768ac819ffbb | 649
+ (1,86) | 183 | 6.743 | cedebb6e872f539bef8c3f919874e9d7 | 605
+ (7,35) | 714 | 5.725 | d14220ee66aeec73c49038385428ec4c | 618
+ (4,37) | 425 | 5.531 | 25b2822c2f5a3230abfadd476e8b04c9 | 656
+ (8,8) | 784 | 5.872 | fc8001f834f6a5f0561080d134d53d29 | 669
+ (2,73) | 267 | 8.253 | eda80a3d5b344bc40f3bc04f65b7a357 | 615
+ (8,75) | 851 | 3.901 | 92fb0c6d1758261f10d052e6e2c1123c | 607
+ (0,45) | 45 | 4.28 | 6c8349cc7260ae62e3b1396831a8398f | 670
+ (2,50) | 244 | 2.833 | 9188905e74c28e489b44e954ec0b9bca | 637
+ (6,18) | 600 | 7.89 | d490d7b4576290fa60eb31b5fc917ad1 | 664
+ (0,55) | 55 | 4.387 | b53b3a3d6ab90ce0268229151c9bde11 | 607
+ (3,34) | 325 | 4.999 | 89f0fd5c927d466d6ec9a21b9ac34ffa | 641
+ (8,52) | 828 | 4.898 | c2626d850c80ea07e7511bbae4c76f4b | 622
+ (7,93) | 772 | 5.411 | e57c6b956a6521b28495f2886ca0977a | 685
+ (8,19) | 795 | 2.923 | 7c590f01490190db0ed02a5070e20f01 | 671
+ (0,84) | 84 | 8.664 | 68d30a9594728bc39aa24be94b319d21 | 604
+ (3,26) | 317 | 7.566 | 5b8add2a5d98b1a652ea7fd72d942dac | 604
+ (8,3) | 779 | 1.881 | 67d96d458abdef21792e6d8e590244e7 | 636
+ (5,52) | 537 | 2.986 | 5ea1649a31336092c05438df996a3e59 | 655
+ (0,73) | 73 | 2.07 | d2ddea18f00665ce8623e36bd4e3c7c5 | 595
+ (5,59) | 544 | 1.784 | 97e8527feaf77a97fc38f34216141515 | 645
+ (2,92) | 286 | 9.295 | 16a5cdae362b8d27a1d8f8c7b78b4330 | 614
+ (7,6) | 685 | 2.887 | 3328bdf9a4b9504b9398284244fe97c2 | 594
+ (5,41) | 526 | 7.94 | 85422afb467e9456013a2a51d4dff702 | 606
+ (0,29) | 29 | 5.164 | 6ea9ab1baa0efb9e19094440c317e21b | 640
+ (6,92) | 674 | 4.837 | 0d7de1aca9299fe63f3e0041f02638a3 | 632
+ (2,87) | 281 | 4.883 | e3796ae838835da0b6f6ea37bcf8bcb7 | 652
+ (6,70) | 652 | 6.461 | 30ef30b64204a3088a26bc2e6ecf7602 | 609
+ (2,74) | 268 | 3.602 | 8f121ce07d74717e0b1f21d122e04521 | 631
+ (8,25) | 801 | 2.903 | 1905aedab9bf2477edc068a355bba31a | 636
+ (5,85) | 570 | 7.279 | a86c450b76fb8c371afead6410d55534 | 653
+ (5,30) | 515 | 6.417 | 2b8a61594b1f4c4db0902a8a395ced93 | 647
+ (2,33) | 227 | 9.687 | 705f2172834666788607efbfca35afb3 | 666
+ (4,88) | 476 | 1.154 | 598b3e71ec378bd83e0a727608b5db01 | 649
+ (8,85) | 861 | 8.424 | f9a40a4780f5e1306c46f1c8daecee3b | 637
+ (9,75) | 948 | 1.705 | 58e4d44e550d0f7ee0a23d6b02d9b0db | 605
+ (9,13) | 886 | 5.146 | 704afe073992cbe4813cae2f7715336f | 638
+ (3,60) | 351 | 4.695 | efe937780e95574250dabe07151bdc23 | 607
+ (2,62) | 256 | 9.927 | f718499c1c8cef6730f9fd03c8125cab | 675
+ (10,6) | 976 | 2.317 | 9c01802ddb981e6bcfbec0f0516b8e35 | 664
+ (7,55) | 734 | 0.84 | e995f98d56967d946471af29d7bf99f1 | 598
+ (5,78) | 563 | 8.07 | 8eefcfdf5990e441f0fb6f3fad709e21 | 656
+ (8,51) | 827 | 3.585 | fa3a3c407f82377f55c19c5d403335c7 | 643
+ (6,56) | 638 | 6.102 | 4c27cea8526af8cfee3be5e183ac9605 | 658
+ (2,67) | 261 | 7.444 | b1a59b315fc9a3002ce38bbe070ec3f5 | 653
+ (7,59) | 738 | 9.665 | 217eedd1ba8c592db97d0dbe54c7adfc | 604
+ (3,13) | 304 | 6.224 | 37bc2f75bf1bcfe8450a1a41c200364c | 648
+ (9,2) | 875 | 6.93 | 4b0a59ddf11c58e7446c9df0da541a84 | 597
+ (4,87) | 475 | 9.427 | 5ef0b4eba35ab2d6180b0bca7e46b6f9 | 645
+ (4,94) | 482 | 7.234 | f770b62bc8f42a0b66751fe636fc6eb0 | 643
+ (6,55) | 637 | 2.075 | a532400ed62e772b9dc0b86f46e583ff | 615
+ (2,86) | 280 | 8.207 | 92c8c96e4c37100777c7190b76d28233 | 614
+ (3,6) | 297 | 9.248 | 9fd81843ad7f202f26c1a174c7357585 | 594
+ (0,58) | 58 | 4.196 | 66f041e16a60928b05a7e228a89c3799 | 609
+ (8,8) | 784 | 5.872 | fc8001f834f6a5f0561080d134d53d29 | 602
+ (8,12) | 788 | 5.519 | c15da1f2b5e5ed6e6837a3802f0d1593 | 649
+ (10,4) | 974 | 3.717 | 4311359ed4969e8401880e3c1836fbe1 | 667
+ (9,54) | 927 | 3.231 | 1f4477bad7af3616c1f933a02bfabe4e | 597
+ (4,13) | 401 | 5.05 | 816b112c6105b3ebd537828a39af4818 | 631
+ (9,45) | 918 | 9.71 | 1e056d2b0ebd5c878c550da6ac5d3724 | 655
+ (2,14) | 208 | 6.732 | 091d584fced301b442654dd8c23b3fc9 | 592
+ (4,90) | 478 | 2.325 | cfee398643cbc3dc5eefc89334cacdc1 | 616
+ (0,29) | 29 | 5.164 | 6ea9ab1baa0efb9e19094440c317e21b | 632
+ (4,59) | 447 | 2.898 | 9a96876e2f8f3dc4f3cf45f02c61c0c1 | 629
+ (1,64) | 161 | 7.939 | bd4c9ab730f5513206b999ec0d90d1fb | 668
+ (5,9) | 494 | 4.764 | 1be3bc32e6564055d5ca3e5a354acbef | 601
+ (6,73) | 655 | 5.838 | 3d2d8ccb37df977cb6d9da15b76c3f3a | 626
+ (9,10) | 883 | 9.689 | 210f760a89db30aa72ca258a3483cc7f | 602
+ (8,97) | 873 | 8.51 | 98d6f58ab0dafbb86b083a001561bb34 | 657
+ (6,95) | 677 | 8.81 | 71a3cb155f8dc89bf3d0365288219936 | 608
+ (0,14) | 14 | 2.613 | aab3238922bcc25a6f606eb525ffdc56 | 669
+ (8,38) | 814 | 5.229 | 96b9bff013acedfb1d140579e2fbeb63 | 611
+ (2,23) | 217 | 0.693 | 63dc7ed1010d3c3b8269faf0ba7491d4 | 620
+ (8,62) | 838 | 5.287 | f9028faec74be6ec9b852b0a542e2f39 | 615
+ (3,48) | 339 | 9.967 | 04025959b191f8f9de3f924f0940515f | 619
+ (7,13) | 692 | 9.199 | e555ebe0ce426f7f9b2bef0706315e0c | 593
+ (3,73) | 364 | 5.908 | bac9162b47c56fc8a4d2a519803d51b3 | 604
+ (4,16) | 404 | 9.063 | 4f4adcbf8c6f66dcfc8a3282ac2bf10a | 611
+ (7,47) | 726 | 3.435 | 0d3180d672e08b4c5312dcdafdf6ef36 | 589
+ (6,76) | 658 | 2.859 | 2f37d10131f2a483a8dd005b3d14b0d9 | 657
+ (9,72) | 945 | 0.3 | 4b6538a44a1dfdc2b83477cd76dee98e | 652
+ (4,70) | 458 | 4.067 | d07e70efcfab08731a97e7b91be644de | 648
+ (6,87) | 669 | 9.84 | 5c04925674920eb58467fb52ce4ef728 | 673
+ (6,64) | 646 | 3.973 | 0ff39bbbf981ac0151d340c9aa40e63e | 655
+ (0,78) | 78 | 8.373 | 35f4a8d465e6e1edc05f3d8ab658c551 | 670
+ (8,56) | 832 | 3.089 | 7250eb93b3c18cc9daa29cf58af7a004 | 666
+ (8,60) | 836 | 7.597 | ab88b15733f543179858600245108dd8 | 669
+ (6,15) | 597 | 5.807 | 08c5433a60135c32e34f46a71175850c | 622
+ (1,64) | 161 | 7.939 | bd4c9ab730f5513206b999ec0d90d1fb | 592
+ (4,6) | 394 | 3.99 | 28f0b864598a1291557bed248a998d4e | 664
+ (5,38) | 523 | 2.255 | 2bb232c0b13c774965ef8558f0fbd615 | 653
+ (2,50) | 244 | 2.833 | 9188905e74c28e489b44e954ec0b9bca | 630
+ (3,34) | 325 | 4.999 | 89f0fd5c927d466d6ec9a21b9ac34ffa | 614
+ (3,95) | 386 | 8.354 | 39461a19e9eddfb385ea76b26521ea48 | 594
+ (0,38) | 38 | 4.004 | a5771bce93e200c36f7cd9dfd0e5deaa | 601
+ (5,27) | 512 | 1.128 | 10a7cdd970fe135cf4f7bb55c0e3b59f | 657
+ (6,73) | 655 | 5.838 | 3d2d8ccb37df977cb6d9da15b76c3f3a | 628
+ (3,85) | 376 | 7.911 | 142949df56ea8ae0be8b5306971900a4 | 646
+ (10,6) | 976 | 2.317 | 9c01802ddb981e6bcfbec0f0516b8e35 | 654
+ (7,36) | 715 | 3.201 | 8df707a948fac1b4a0f97aa554886ec8 | 595
+ (6,48) | 630 | 3.619 | 9cc138f8dc04cbf16240daa92d8d50e2 | 657
+ (1,40) | 137 | 1.045 | 3988c7f88ebcb58c6ce932b957b6f332 | 629
+ (5,91) | 576 | 2.975 | a7aeed74714116f3b292a982238f83d2 | 661
+ (7,39) | 718 | 1.134 | 50c3d7614917b24303ee6a220679dab3 | 623
+ (6,62) | 644 | 0.769 | 8c7bbbba95c1025975e548cee86dfadc | 674
+ (8,75) | 851 | 3.901 | 92fb0c6d1758261f10d052e6e2c1123c | 664
+ (3,89) | 380 | 5.038 | bca82e41ee7b0833588399b1fcd177c7 | 685
+ (0,97) | 97 | 3.799 | e2ef524fbf3d9fe611d5a8e90fefdc9c | 621
+ (8,90) | 866 | 9.162 | ca8155f4d27f205953f9d3d7974bdd70 | 678
+ (8,38) | 814 | 5.229 | 96b9bff013acedfb1d140579e2fbeb63 | 613
+ (3,35) | 326 | 6.161 | a666587afda6e89aec274a3657558a27 | 676
+ (3,86) | 377 | 9.915 | d34ab169b70c9dcd35e62896010cd9ff | 644
+ (5,46) | 531 | 0.877 | 0fcbc61acd0479dc77e3cccc0f5ffca7 | 599
+ (2,22) | 216 | 5.678 | 45fbc6d3e05ebd93369ce542e8f2322d | 591
+ (7,62) | 741 | 9.252 | 2e65f2f2fdaf6c699b223c61b1b5ab89 | 657
+ (7,73) | 752 | 6.81 | a1d33d0dfec820b41b54430b50e96b5c | 660
+ (8,41) | 817 | 4.968 | 31839b036f63806cba3f47b93af8ccb5 | 678
+ (1,58) | 155 | 0.382 | 2a79ea27c279e471f4d180b08d62b00a | 679
+ (3,10) | 301 | 4.599 | 34ed066df378efacc9b924ec161e7639 | 606
+ (4,40) | 428 | 4.838 | 8d7d8ee069cb0cbbf816bbb65d56947e | 602
+ (6,50) | 632 | 9.487 | abd815286ba1007abfbb8415b83ae2cf | 641
+ (4,67) | 455 | 4.602 | 821fa74b50ba3f7cba1e6c53e8fa6845 | 665
+ (5,62) | 547 | 2.692 | c75b6f114c23a4d7ea11331e7c00e73c | 640
+ (6,7) | 589 | 7.307 | 30bb3825e8f631cc6075c0f87bb4978c | 665
+ (2,38) | 232 | 2.033 | be83ab3ecd0db773eb2dc1b0a17836a1 | 607
+ (0,34) | 34 | 2.454 | e369853df766fa44e1ed0ff613f563bd | 639
+ (8,22) | 798 | 4.377 | 9e3cfc48eccf81a0d57663e129aef3cb | 601
+ (9,48) | 921 | 5.248 | 430c3626b879b4005d41b8a46172e0c0 | 615
+ (8,60) | 836 | 7.597 | ab88b15733f543179858600245108dd8 | 658
+ (2,31) | 225 | 8.138 | d1c38a09acc34845c6be3a127a5aacaf | 633
+ (0,56) | 56 | 3.489 | 9f61408e3afb633e50cdf1b20de6f466 | 651
+ (0,18) | 18 | 1.535 | 6f4922f45568161a8cdf4ad2299f6d23 | 643
+ (3,20) | 311 | 5.661 | 9dfcd5e558dfa04aaf37f137a1d9d3e5 | 682
+ (6,35) | 617 | 1.047 | 5d44ee6f2c3f71b73125876103c8f6c4 | 621
+ (2,80) | 274 | 3.661 | d947bf06a885db0d477d707121934ff8 | 647
+ (5,68) | 553 | 3.933 | f387624df552cea2f369918c5e1e12bc | 623
+ (3,97) | 388 | 2.299 | d9fc5b73a8d78fad3d6dffe419384e70 | 672
+ (7,38) | 717 | 5.589 | 788d986905533aba051261497ecffcbb | 669
+ (5,67) | 552 | 3.296 | 94c7bb58efc3b337800875b5d382a072 | 642
+ (8,56) | 832 | 3.089 | 7250eb93b3c18cc9daa29cf58af7a004 | 612
+ (0,25) | 25 | 7.485 | 8e296a067a37563370ded05f5a3bf3ec | 659
+ (10,12) | 982 | 1.32 | fec8d47d412bcbeece3d9128ae855a7a | 642
+ (7,74) | 753 | 8.786 | 6f2268bd1d3d3ebaabb04d6b5d099425 | 631
+ (0,45) | 45 | 4.28 | 6c8349cc7260ae62e3b1396831a8398f | 612
+ (0,84) | 84 | 8.664 | 68d30a9594728bc39aa24be94b319d21 | 600
+ (3,97) | 388 | 2.299 | d9fc5b73a8d78fad3d6dffe419384e70 | 593
+ (3,75) | 366 | 5.318 | 5ef698cd9fe650923ea331c15af3b160 | 667
+ (1,59) | 156 | 9.49 | 1c9ac0159c94d8d0cbedc973445af2da | 601
+ (5,10) | 495 | 5.686 | 35051070e572e47d2c26c241ab88307f | 662
+ (4,25) | 413 | 0.414 | 0deb1c54814305ca9ad266f53bc82511 | 675
+ (3,26) | 317 | 7.566 | 5b8add2a5d98b1a652ea7fd72d942dac | 654
+ (1,24) | 121 | 1.015 | 4c56ff4ce4aaf9573aa5dff913df997a | 630
+ (7,91) | 770 | 8.527 | 4ea06fbc83cdd0a06020c35d50e1e89a | 681
+ (7,50) | 729 | 5.201 | 5751ec3e9a4feab575962e78e006250d | 685
+ (8,21) | 797 | 2.742 | beb22fb694d513edcf5533cf006dfeae | 648
+ (4,26) | 414 | 0.84 | 66808e327dc79d135ba18e051673d906 | 612
+ (0,23) | 23 | 9.137 | 37693cfc748049e45d87b8c7d8b9aacd | 628
+ (2,19) | 213 | 0.037 | 979d472a84804b9f647bc185a877a8b5 | 683
+ (7,34) | 713 | 7.704 | 07c5807d0d927dcd0980f86024e5208b | 608
+ (2,59) | 253 | 1.789 | c24cd76e1ce41366a4bbe8a49b02a028 | 602
+ (6,21) | 603 | 1.898 | d86ea612dec96096c5e0fcc8dd42ab6d | 603
+ (9,79) | 952 | 1.352 | e6cb2a3c14431b55aa50c06529eaa21b | 594
+ (3,70) | 361 | 9.237 | 52720e003547c70561bf5e03b95aa99f | 654
+ (6,19) | 601 | 2.693 | b2f627fff19fda463cb386442eac2b3d | 616
+ (5,49) | 534 | 7.218 | c399862d3b9d6b76c8436e924a68c45b | 601
+ (9,55) | 928 | 3.601 | d045c59a90d7587d8d671b5f5aec4e7c | 613
+ (1,77) | 174 | 4.08 | bf8229696f7a3bb4700cfddef19fa23f | 644
+ (1,28) | 125 | 8.49 | 3def184ad8f4755ff269862ea77393dd | 659
+ (7,84) | 763 | 6.188 | eefc9e10ebdc4a2333b42b2dbb8f27b6 | 601
+ (2,35) | 229 | 3.638 | 57aeee35c98205091e18d1140e9f38cf | 598
+ (6,20) | 602 | 6.631 | c3992e9a68c5ae12bd18488bc579b30d | 643
+ (5,5) | 490 | 5.695 | c410003ef13d451727aeff9082c29a5c | 627
+ (3,96) | 387 | 2.769 | 8efb100a295c0c690931222ff4467bb8 | 641
+ (4,50) | 438 | 0.622 | 1651cf0d2f737d7adeab84d339dbabd3 | 601
+ (9,91) | 964 | 4.635 | 8065d07da4a77621450aa84fee5656d9 | 654
+ (1,44) | 141 | 6.832 | 0f28b5d49b3020afeecd95b4009adf4c | 594
+ (6,81) | 663 | 2.433 | 8757150decbd89b0f5442ca3db4d0e0e | 637
+ (4,14) | 402 | 8.246 | 69cb3ea317a32c4e6143e665fdb20b14 | 598
+ (8,68) | 844 | 3.318 | e97ee2054defb209c35fe4dc94599061 | 589
+ (3,66) | 357 | 7.196 | fb7b9ffa5462084c5f4e7e85a093e6d7 | 626
+ (9,55) | 928 | 3.601 | d045c59a90d7587d8d671b5f5aec4e7c | 606
+ (9,34) | 907 | 5.918 | 621461af90cadfdaf0e8d4cc25129f91 | 675
+ (8,21) | 797 | 2.742 | beb22fb694d513edcf5533cf006dfeae | 683
+ (6,88) | 670 | 4.681 | 17c276c8e723eb46aef576537e9d56d0 | 624
+ (3,27) | 318 | 9.977 | 432aca3a1e345e339f35a30c8f65edce | 667
+ (1,72) | 169 | 1.767 | 3636638817772e42b59d74cff571fbb3 | 668
+ (0,36) | 36 | 3.822 | 19ca14e7ea6328a42e0eb13d585e4c22 | 593
+ (9,85) | 958 | 1.634 | d240e3d38a8882ecad8633c8f9c78c9b | 631
+ (6,2) | 584 | 3.751 | f5deaeeae1538fb6c45901d524ee2f98 | 641
+ (7,88) | 767 | 1.454 | f2201f5191c4e92cc5af043eebfd0946 | 595
+ (0,97) | 97 | 3.799 | e2ef524fbf3d9fe611d5a8e90fefdc9c | 592
+ (3,64) | 355 | 5.48 | 82cec96096d4281b7c95cd7e74623496 | 617
+ (4,14) | 402 | 8.246 | 69cb3ea317a32c4e6143e665fdb20b14 | 609
+ (5,41) | 526 | 7.94 | 85422afb467e9456013a2a51d4dff702 | 598
+ (8,24) | 800 | 8.901 | 7a53928fa4dd31e82c6ef826f341daec | 644
+ (6,67) | 649 | 8.757 | 55b37c5c270e5d84c793e486d798c01d | 592
+ (8,13) | 789 | 3.511 | 68053af2923e00204c3ca7c6a3150cf7 | 665
+ (1,57) | 154 | 8.796 | 1d7f7abc18fcb43975065399b0d1e48e | 682
+ (8,45) | 821 | 9.903 | 4558dbb6f6f8bb2e16d03b85bde76e2c | 672
+ (6,87) | 669 | 9.84 | 5c04925674920eb58467fb52ce4ef728 | 685
+ (6,79) | 661 | 6.478 | 3a066bda8c96b9478bb0512f0a43028c | 663
+ (7,57) | 736 | 2.94 | 6bc24fc1ab650b25b4114e93a98f1eba | 604
+ (1,42) | 139 | 2.042 | e00da03b685a0dd18fb6a08af0923de0 | 685
+ (7,68) | 747 | 7.546 | 8d317bdcf4aafcfc22149d77babee96d | 622
+ (3,28) | 319 | 7.195 | 8d3bba7425e7c98c50f52ca1b52d3735 | 626
+ (0,78) | 78 | 8.373 | 35f4a8d465e6e1edc05f3d8ab658c551 | 667
+ (2,52) | 246 | 7.953 | 38db3aed920cf82ab059bfccbd02be6a | 672
+ (5,92) | 577 | 0.858 | fde9264cf376fffe2ee4ddf4a988880d | 631
+ (4,85) | 473 | 3.453 | 2050e03ca119580f74cca14cc6e97462 | 658
+ (8,6) | 782 | 0.276 | 72da7fd6d1302c0a159f6436d01e9eb0 | 632
+ (7,19) | 698 | 1.767 | 99bcfcd754a98ce89cb86f73acc04645 | 682
+ (9,53) | 926 | 3.294 | cbb6a3b884f4f88b3a8e3d44c636cbd8 | 593
+ (3,57) | 348 | 1.463 | 01386bd6d8e091c2ab4c7c7de644d37b | 613
+ (3,38) | 329 | 4.234 | 6faa8040da20ef399b63a72d0e4ab575 | 683
+ (8,37) | 813 | 4.154 | e0cf1f47118daebc5b16269099ad7347 | 607
+ (1,25) | 122 | 8.126 | a0a080f42e6f13b3a2df133f073095dd | 602
+ (6,67) | 649 | 8.757 | 55b37c5c270e5d84c793e486d798c01d | 671
+ (9,97) | 970 | 7.977 | 89fcd07f20b6785b92134bd6c1d0fa42 | 624
+ (2,35) | 229 | 3.638 | 57aeee35c98205091e18d1140e9f38cf | 613
+ (8,73) | 849 | 4.814 | fe8c15fed5f808006ce95eddb7366e35 | 669
+ (6,50) | 632 | 9.487 | abd815286ba1007abfbb8415b83ae2cf | 639
+ (10,30) | 1000 | 2.776 | a9b7ba70783b617e9998dc4dd82eb3c5 | 669
+ (2,31) | 225 | 8.138 | d1c38a09acc34845c6be3a127a5aacaf | 667
+ (5,42) | 527 | 9.387 | 13f320e7b5ead1024ac95c3b208610db | 669
+ (1,3) | 100 | 1.237 | f899139df5e1059396431415e770c6dd | 604
+ (6,35) | 617 | 1.047 | 5d44ee6f2c3f71b73125876103c8f6c4 | 662
+ (8,8) | 784 | 5.872 | fc8001f834f6a5f0561080d134d53d29 | 678
+ (1,7) | 104 | 6.715 | c9e1074f5b3f9fc8ea15d152add07294 | 633
+ (2,71) | 265 | 6.691 | e56954b4f6347e897f954495eab16a88 | 651
+ (5,21) | 506 | 5.768 | ff4d5fbbafdf976cfdc032e3bde78de5 | 640
+ (1,41) | 138 | 5.781 | 013d407166ec4fa56eb1e1f8cbe183b9 | 673
+ (1,29) | 126 | 7.115 | 069059b7ef840f0c74a814ec9237b6ec | 650
+ (1,72) | 169 | 1.767 | 3636638817772e42b59d74cff571fbb3 | 608
+ (5,17) | 502 | 0.735 | b5b41fac0361d157d9673ecb926af5ae | 655
+ (1,87) | 184 | 7.903 | 6cdd60ea0045eb7a6ec44c54d29ed402 | 681
+ (3,57) | 348 | 1.463 | 01386bd6d8e091c2ab4c7c7de644d37b | 644
+ (1,35) | 132 | 6.002 | 65ded5353c5ee48d0b7d48c591b8f430 | 640
+ (0,70) | 70 | 0.403 | 7cbbc409ec990f19c78c75bd1e06f215 | 623
+ (10,5) | 975 | 6.5 | 92977ae4d2ba21425a59afb269c2a14e | 663
+ (8,78) | 854 | 6.787 | f7e9050c92a851b0016442ab604b0488 | 658
+ (8,54) | 830 | 9.382 | 8e82ab7243b7c66d768f1b8ce1c967eb | 647
+ (5,23) | 508 | 1.412 | 389bc7bb1e1c2a5e7e147703232a88f6 | 596
+ (3,4) | 295 | 9.821 | 49182f81e6a13cf5eaa496d51fea6406 | 632
+ (7,24) | 703 | 8.668 | d6c651ddcd97183b2e40bc464231c962 | 628
+ (2,53) | 247 | 8.511 | 3cec07e9ba5f5bb252d13f5f431e4bbb | 595
+ (2,7) | 201 | 8.395 | 757b505cfd34c64c85ca5b5690ee5293 | 626
+ (3,66) | 357 | 7.196 | fb7b9ffa5462084c5f4e7e85a093e6d7 | 643
+ (8,7) | 783 | 3.212 | 6e0721b2c6977135b916ef286bcb49ec | 615
+ (6,71) | 653 | 0.018 | eaae339c4d89fc102edd9dbdb6a28915 | 650
+ (7,54) | 733 | 7.616 | 6c29793a140a811d0c45ce03c1c93a28 | 661
+ (0,91) | 91 | 5.307 | 54229abfcfa5649e7003b83dd4755294 | 666
+ (4,55) | 443 | 3.362 | 13f3cf8c531952d72e5847c4183e6910 | 646
+ (1,87) | 184 | 7.903 | 6cdd60ea0045eb7a6ec44c54d29ed402 | 634
+ (0,19) | 19 | 5.218 | 1f0e3dad99908345f7439f8ffabdffc4 | 656
+ (6,61) | 643 | 4.234 | 9b698eb3105bd82528f23d0c92dedfc0 | 679
+ (0,4) | 4 | 0.488 | a87ff679a2f3e71d9181a67b7542122c | 594
+ (4,89) | 477 | 1.205 | 74071a673307ca7459bcf75fbd024e09 | 594
+ (3,94) | 385 | 5.103 | dc912a253d1e9ba40e2c597ed2376640 | 620
+ (1,55) | 152 | 6.651 | 37a749d808e46495a8da1e5352d03cae | 602
+ (0,57) | 57 | 2.005 | 72b32a1f754ba1c09b3695e0cb6cde7f | 616
+ (8,23) | 799 | 5.797 | 28267ab848bcf807b2ed53c3a8f8fc8a | 669
+ (4,68) | 456 | 7.784 | 250cf8b51c773f3f8dc8b4be867a9a02 | 684
+ (4,89) | 477 | 1.205 | 74071a673307ca7459bcf75fbd024e09 | 681
+ (6,25) | 607 | 4.795 | dc82d632c9fcecb0778afbc7924494a6 | 673
+ (7,55) | 734 | 0.84 | e995f98d56967d946471af29d7bf99f1 | 674
+ (2,59) | 253 | 1.789 | c24cd76e1ce41366a4bbe8a49b02a028 | 674
+ (6,22) | 604 | 1.82 | 9cf81d8026a9018052c429cc4e56739b | 610
+ (0,40) | 40 | 9.594 | d645920e395fedad7bbbed0eca3fe2e0 | 601
+ (2,83) | 277 | 1.613 | 20f07591c6fcb220ffe637cda29bb3f6 | 665
+ (10,13) | 983 | 7.567 | 6aab1270668d8cac7cef2566a1c5f569 | 593
+ (4,50) | 438 | 0.622 | 1651cf0d2f737d7adeab84d339dbabd3 | 591
+ (8,55) | 831 | 3.798 | e0ec453e28e061cc58ac43f91dc2f3f0 | 645
+ (7,82) | 761 | 0.899 | 88ae6372cfdc5df69a976e893f4d554b | 607
+ (10,8) | 978 | 8.832 | 2ab56412b1163ee131e1246da0955bd1 | 616
+ (6,19) | 601 | 2.693 | b2f627fff19fda463cb386442eac2b3d | 657
+ (2,36) | 230 | 1.073 | 6da9003b743b65f4c0ccd295cc484e57 | 616
+ (8,47) | 823 | 2.887 | 632cee946db83e7a52ce5e8d6f0fed35 | 683
+ (1,10) | 107 | 3.065 | a97da629b098b75c294dffdc3e463904 | 638
+ (9,10) | 883 | 9.689 | 210f760a89db30aa72ca258a3483cc7f | 589
+ (1,71) | 168 | 3.981 | 006f52e9102a8d3be2fe5614f42ba989 | 645
+ (0,23) | 23 | 9.137 | 37693cfc748049e45d87b8c7d8b9aacd | 684
+ (2,19) | 213 | 0.037 | 979d472a84804b9f647bc185a877a8b5 | 647
+ (5,77) | 562 | 8.753 | 4e4b5fbbbb602b6d35bea8460aa8f8e5 | 608
+ (5,42) | 527 | 9.387 | 13f320e7b5ead1024ac95c3b208610db | 625
+ (7,77) | 756 | 4.813 | 2823f4797102ce1a1aec05359cc16dd9 | 673
+ (10,23) | 993 | 9.428 | 7b13b2203029ed80337f27127a9f1d28 | 663
+ (4,20) | 408 | 2.929 | 0d0fd7c6e093f7b804fa0150b875b868 | 650
+ (6,52) | 634 | 4.386 | 6766aa2750c19aad2fa1b32f36ed4aee | 677
+ (6,19) | 601 | 2.693 | b2f627fff19fda463cb386442eac2b3d | 644
+ (4,59) | 447 | 2.898 | 9a96876e2f8f3dc4f3cf45f02c61c0c1 | 605
+ (5,96) | 581 | 9.818 | c6e19e830859f2cb9f7c8f8cacb8d2a6 | 682
+ (7,59) | 738 | 9.665 | 217eedd1ba8c592db97d0dbe54c7adfc | 617
+ (10,12) | 982 | 1.32 | fec8d47d412bcbeece3d9128ae855a7a | 662
+ (1,6) | 103 | 4.109 | 6974ce5ac660610b44d9b9fed0ff9548 | 596
+ (2,41) | 235 | 5.502 | 577ef1154f3240ad5b9b413aa7346a1e | 610
+ (4,79) | 467 | 7.696 | ab817c9349cf9c4f6877e1894a1faa00 | 597
+ (10,21) | 991 | 2.868 | 692f93be8c7a41525c0baf2076aecfb4 | 599
+ (9,72) | 945 | 0.3 | 4b6538a44a1dfdc2b83477cd76dee98e | 657
+ (7,53) | 732 | 1.649 | ba3866600c3540f67c1e9575e213be0a | 665
+ (5,10) | 495 | 5.686 | 35051070e572e47d2c26c241ab88307f | 649
+ (3,32) | 323 | 6.341 | bc6dc48b743dc5d013b1abaebd2faed2 | 611
+ (1,40) | 137 | 1.045 | 3988c7f88ebcb58c6ce932b957b6f332 | 621
+ (5,42) | 527 | 9.387 | 13f320e7b5ead1024ac95c3b208610db | 641
+ (1,55) | 152 | 6.651 | 37a749d808e46495a8da1e5352d03cae | 620
+ (4,75) | 463 | 0.808 | 428fca9bc1921c25c5121f9da7815cde | 668
+ (3,16) | 307 | 6.515 | 8e98d81f8217304975ccb23337bb5761 | 640
+ (9,20) | 893 | 1.805 | d56b9fc4b0f1be8871f5e1c40c0067e7 | 681
+ (1,51) | 148 | 8.453 | 47d1e990583c9c67424d369f3414728e | 599
+ (10,1) | 971 | 3.963 | 6602294be910b1e3c4571bd98c4d5484 | 632
+ (1,76) | 173 | 5.075 | f7e6c85504ce6e82442c770f7c8606f0 | 679
+ (1,29) | 126 | 7.115 | 069059b7ef840f0c74a814ec9237b6ec | 631
+ (9,87) | 960 | 7.25 | 437d7d1d97917cd627a34a6a0fb41136 | 662
+ (0,82) | 82 | 7.493 | 9778d5d219c5080b9a6a17bef029331c | 635
+ (6,25) | 607 | 4.795 | dc82d632c9fcecb0778afbc7924494a6 | 591
+ (0,4) | 4 | 0.488 | a87ff679a2f3e71d9181a67b7542122c | 612
+ (4,36) | 424 | 2.163 | 3c7781a36bcd6cf08c11a970fbe0e2a6 | 594
+ (9,16) | 889 | 4.188 | 07871915a8107172b3b5dc15a6574ad3 | 623
+ (6,70) | 652 | 6.461 | 30ef30b64204a3088a26bc2e6ecf7602 | 683
+ (5,86) | 571 | 0.032 | c9892a989183de32e976c6f04e700201 | 672
+ (1,61) | 158 | 6.246 | 06409663226af2f3114485aa4e0a23b4 | 677
+ (9,62) | 935 | 3.85 | e820a45f1dfc7b95282d10b6087e11c0 | 608
+ (5,70) | 555 | 7.829 | 15de21c670ae7c3f6f3f1f37029303c9 | 645
+ (4,24) | 412 | 7.018 | b9228e0962a78b84f3d5d92f4faa000b | 674
+ (2,92) | 286 | 9.295 | 16a5cdae362b8d27a1d8f8c7b78b4330 | 680
+ (8,73) | 849 | 4.814 | fe8c15fed5f808006ce95eddb7366e35 | 662
+ (9,84) | 957 | 8.796 | 2ba596643cbbbc20318224181fa46b28 | 671
+ (4,26) | 414 | 0.84 | 66808e327dc79d135ba18e051673d906 | 647
+ (10,19) | 989 | 7.603 | a1140a3d0df1c81e24ae954d935e8926 | 591
+ (7,20) | 699 | 9.884 | afd4836712c5e77550897e25711e1d96 | 605
+ (7,94) | 773 | 7.299 | 86b122d4358357d834a87ce618a55de0 | 646
+ (10,18) | 988 | 5.619 | 9908279ebbf1f9b250ba689db6a0222b | 598
+ (0,96) | 96 | 2.132 | 26657d5ff9020d2abefe558796b99584 | 678
+ (8,18) | 794 | 6.246 | 82489c9737cc245530c7a6ebef3753ec | 683
+ (5,59) | 544 | 1.784 | 97e8527feaf77a97fc38f34216141515 | 603
+ (8,25) | 801 | 2.903 | 1905aedab9bf2477edc068a355bba31a | 607
+ (4,42) | 430 | 4.709 | f74909ace68e51891440e4da0b65a70c | 675
+ (6,51) | 633 | 0.25 | 26dd0dbc6e3f4c8043749885523d6a25 | 662
+ (3,51) | 342 | 5.627 | 58238e9ae2dd305d79c2ebc8c1883422 | 622
+ (3,82) | 373 | 4.013 | ffd52f3c7e12435a724a8f30fddadd9c | 660
+ (3,26) | 317 | 7.566 | 5b8add2a5d98b1a652ea7fd72d942dac | 623
+ (2,20) | 214 | 3.568 | ca46c1b9512a7a8315fa3c5a946e8265 | 650
+ (3,83) | 374 | 4.769 | ad972f10e0800b49d76fed33a21f6698 | 629
+ (10,22) | 992 | 7.804 | 860320be12a1c050cd7731794e231bd3 | 679
+ (0,17) | 17 | 7.358 | 70efdf2ec9b086079795c442636b55fb | 680
+ (6,13) | 595 | 4.497 | 04ecb1fa28506ccb6f72b12c0245ddbc | 632
+ (4,6) | 394 | 3.99 | 28f0b864598a1291557bed248a998d4e | 596
+ (0,88) | 88 | 8.14 | 2a38a4a9316c49e5a833517c45d31070 | 670
+ (0,16) | 16 | 2.64 | c74d97b01eae257e44aa9d5bade97baf | 683
+ (2,30) | 224 | 2.281 | 13fe9d84310e77f13a6d184dbf1232f3 | 664
+ (1,64) | 161 | 7.939 | bd4c9ab730f5513206b999ec0d90d1fb | 641
+ (6,2) | 584 | 3.751 | f5deaeeae1538fb6c45901d524ee2f98 | 674
+ (10,3) | 973 | 2.017 | ca75910166da03ff9d4655a0338e6b09 | 676
+ (7,2) | 681 | 9.106 | 1595af6435015c77a7149e92a551338e | 601
+ (5,83) | 568 | 8.956 | dd458505749b2941217ddd59394240e8 | 621
+ (0,48) | 48 | 1.638 | 642e92efb79421734881b53e1e1b18b6 | 611
+ (5,61) | 546 | 8.622 | ed265bc903a5a097f61d3ec064d96d2e | 651
+ (5,31) | 516 | 0.909 | f3f27a324736617f20abbf2ffd806f6d | 651
+ (3,44) | 335 | 5.056 | f9b902fc3289af4dd08de5d1de54f68f | 661
+ (1,26) | 123 | 1.038 | 202cb962ac59075b964b07152d234b70 | 590
+ (1,47) | 144 | 5.376 | 0a09c8844ba8f0936c20bd791130d6b6 | 663
+ (10,23) | 993 | 9.428 | 7b13b2203029ed80337f27127a9f1d28 | 675
+ (1,34) | 131 | 3.525 | 1afa34a7f984eeabdbb0a7d494132ee5 | 593
+ (3,48) | 339 | 9.967 | 04025959b191f8f9de3f924f0940515f | 660
+ (4,21) | 409 | 2.081 | a96b65a721e561e1e3de768ac819ffbb | 681
+ (4,6) | 394 | 3.99 | 28f0b864598a1291557bed248a998d4e | 589
+ (1,32) | 129 | 1.358 | d1f491a404d6854880943e5c3cd9ca25 | 610
+ (2,5) | 199 | 1.387 | 84d9ee44e457ddef7f2c4f25dc8fa865 | 609
+ (8,79) | 855 | 5.458 | addfa9b7e234254d26e9c7f2af1005cb | 662
+ (3,60) | 351 | 4.695 | efe937780e95574250dabe07151bdc23 | 606
+ (9,51) | 924 | 7.052 | bea5955b308361a1b07bc55042e25e54 | 641
+ (1,79) | 176 | 0.635 | 38af86134b65d0f10fe33d30dd76442e | 679
+ (8,33) | 809 | 6.587 | 32b30a250abd6331e03a2a1f16466346 | 660
+ (0,68) | 68 | 3.698 | a3f390d88e4c41f2747bfa2f1b5f87db | 664
+ (10,2) | 972 | 7.836 | c22abfa379f38b5b0411bc11fa9bf92f | 609
+ (2,44) | 238 | 9.606 | ac1dd209cbcc5e5d1c6e28598e8cbbe8 | 664
+ (8,16) | 792 | 4.409 | 96ea64f3a1aa2fd00c72faacf0cb8ac9 | 597
+ (4,9) | 397 | 9.307 | e46de7e1bcaaced9a54f1e9d0d2f800d | 669
+ (6,3) | 585 | 2.718 | a9a1d5317a33ae8cef33961c34144f84 | 590
+ (4,74) | 462 | 3.655 | 51d92be1c60d1db1d2e5e7a07da55b26 | 671
+ (8,77) | 853 | 5.415 | aff1621254f7c1be92f64550478c56e6 | 630
+ (8,28) | 804 | 0.202 | dc5689792e08eb2e219dce49e64c885b | 674
+ (9,65) | 938 | 9.768 | 74bba22728b6185eec06286af6bec36d | 627
+ (5,53) | 538 | 9.297 | 7bcdf75ad237b8e02e301f4091fb6bc8 | 619
+ (1,87) | 184 | 7.903 | 6cdd60ea0045eb7a6ec44c54d29ed402 | 638
+ (10,23) | 993 | 9.428 | 7b13b2203029ed80337f27127a9f1d28 | 640
+ (9,81) | 954 | 5.503 | 6395ebd0f4b478145ecfbaf939454fa4 | 591
+ (8,79) | 855 | 5.458 | addfa9b7e234254d26e9c7f2af1005cb | 650
+ (5,42) | 527 | 9.387 | 13f320e7b5ead1024ac95c3b208610db | 605
+ (3,50) | 341 | 7.735 | 3dd48ab31d016ffcbf3314df2b3cb9ce | 603
+ (6,52) | 634 | 4.386 | 6766aa2750c19aad2fa1b32f36ed4aee | 621
+ (4,53) | 441 | 6.968 | 15d4e891d784977cacbfcbb00c48f133 | 682
+ (3,25) | 316 | 1.103 | 3fe94a002317b5f9259f82690aeea4cd | 627
+ (9,20) | 893 | 1.805 | d56b9fc4b0f1be8871f5e1c40c0067e7 | 617
+ (5,6) | 491 | 3.957 | 559cb990c9dffd8675f6bc2186971dc2 | 602
+ (9,39) | 912 | 1.142 | 2a9d121cd9c3a1832bb6d2cc6bd7a8a7 | 653
+ (0,71) | 71 | 3.292 | e2c420d928d4bf8ce0ff2ec19b371514 | 643
+ (3,76) | 367 | 8.421 | 05049e90fa4f5039a8cadc6acbb4b2cc | 655
+ (2,67) | 261 | 7.444 | b1a59b315fc9a3002ce38bbe070ec3f5 | 657
+ (2,32) | 226 | 5.432 | 9cfdf10e8fc047a44b08ed031e1f0ed1 | 633
+ (3,9) | 300 | 4.252 | 94f6d7e04a4d452035300f18b984988c | 671
+ (5,25) | 510 | 2.627 | 087408522c31eeb1f982bc0eaf81d35f | 615
+ (6,55) | 637 | 2.075 | a532400ed62e772b9dc0b86f46e583ff | 628
+ (8,18) | 794 | 6.246 | 82489c9737cc245530c7a6ebef3753ec | 635
+ (6,76) | 658 | 2.859 | 2f37d10131f2a483a8dd005b3d14b0d9 | 667
+ (5,65) | 550 | 1.335 | 01f78be6f7cad02658508fe4616098a9 | 616
+ (4,88) | 476 | 1.154 | 598b3e71ec378bd83e0a727608b5db01 | 655
+ (5,51) | 536 | 4.951 | 65658fde58ab3c2b6e5132a39fae7cb9 | 596
+ (10,15) | 985 | 6.823 | 54a367d629152b720749e187b3eaa11b | 648
+ (8,48) | 824 | 0.742 | 677e09724f0e2df9b6c000b75b5da10d | 660
+ (2,48) | 242 | 5.099 | e4a6222cdb5b34375400904f03d8e6a5 | 637
+ (5,93) | 578 | 8.483 | a8849b052492b5106526b2331e526138 | 604
+ (8,20) | 796 | 2.844 | 35cf8659cfcb13224cbd47863a34fc58 | 625
+ (1,67) | 164 | 8.752 | fa7cdfad1a5aaf8370ebeda47a1ff1c3 | 649
+ (0,82) | 82 | 7.493 | 9778d5d219c5080b9a6a17bef029331c | 594
+ (9,37) | 910 | 0.305 | e205ee2a5de471a70c1fd1b46033a75f | 604
+ (0,74) | 74 | 9.463 | ad61ab143223efbc24c7d2583be69251 | 681
+ (4,36) | 424 | 2.163 | 3c7781a36bcd6cf08c11a970fbe0e2a6 | 634
+ (1,23) | 120 | 9.306 | da4fb5c6e93e74d3df8527599fa62642 | 639
+ (7,64) | 743 | 9.999 | 5c572eca050594c7bc3c36e7e8ab9550 | 682
+ (0,6) | 6 | 1.83 | 1679091c5a880faf6fb5e6087eb1b2dc | 654
+ (1,89) | 186 | 7.125 | 9872ed9fc22fc182d371c3e9ed316094 | 604
+ (5,61) | 546 | 8.622 | ed265bc903a5a097f61d3ec064d96d2e | 624
+ (7,69) | 748 | 0.086 | e49b8b4053df9505e1f48c3a701c0682 | 611
+ (5,93) | 578 | 8.483 | a8849b052492b5106526b2331e526138 | 589
+ (2,13) | 207 | 4.104 | 69adc1e107f7f7d035d7baf04342e1ca | 602
+ (4,6) | 394 | 3.99 | 28f0b864598a1291557bed248a998d4e | 604
+ (6,27) | 609 | 6.172 | d7a728a67d909e714c0774e22cb806f2 | 624
+ (5,66) | 551 | 5.262 | 7f24d240521d99071c93af3917215ef7 | 590
+ (2,12) | 206 | 2.416 | 7eabe3a1649ffa2b3ff8c02ebfd5659f | 589
+ (5,94) | 579 | 9.939 | 258be18e31c8188555c2ff05b4d542c3 | 589
+ (0,97) | 97 | 3.799 | e2ef524fbf3d9fe611d5a8e90fefdc9c | 589
+ (2,80) | 274 | 3.661 | d947bf06a885db0d477d707121934ff8 | 680
+ (1,22) | 119 | 2.819 | 07e1cd7dca89a1678042477183b7ac3f | 614
+ (0,41) | 41 | 5.435 | 3416a75f4cea9109507cacd8e2f2aefc | 647
+ (4,46) | 434 | 1.851 | a49e9411d64ff53eccfdd09ad10a15b3 | 603
+ (6,54) | 636 | 9.116 | c5ab0bc60ac7929182aadd08703f1ec6 | 601
+ (9,28) | 901 | 8.444 | 892c91e0a653ba19df81a90f89d99bcd | 663
+ (1,84) | 181 | 6.904 | fc221309746013ac554571fbd180e1c8 | 672
+ (3,77) | 368 | 7.943 | cf004fdc76fa1a4f25f62e0eb5261ca3 | 640
+ (8,85) | 861 | 8.424 | f9a40a4780f5e1306c46f1c8daecee3b | 648
+ (8,10) | 786 | 9.74 | 61b4a64be663682e8cb037d9719ad8cd | 667
+ (1,40) | 137 | 1.045 | 3988c7f88ebcb58c6ce932b957b6f332 | 673
+ (8,46) | 822 | 5.911 | afda332245e2af431fb7b672a68b659d | 612
+ (0,47) | 47 | 9.07 | 67c6a1e7ce56d3d6fa748ab6d9af3fd7 | 645
+ (1,41) | 138 | 5.781 | 013d407166ec4fa56eb1e1f8cbe183b9 | 645
+ (5,20) | 505 | 0.208 | e8c0653fea13f91bf3c48159f7c24f78 | 673
+ (2,39) | 233 | 9.268 | e165421110ba03099a1c0393373c5b43 | 661
+ (4,10) | 398 | 6.664 | b7b16ecf8ca53723593894116071700c | 650
+ (5,85) | 570 | 7.279 | a86c450b76fb8c371afead6410d55534 | 655
+ (3,88) | 379 | 9.374 | a02ffd91ece5e7efeb46db8f10a74059 | 621
+ (8,24) | 800 | 8.901 | 7a53928fa4dd31e82c6ef826f341daec | 622
+ (5,78) | 563 | 8.07 | 8eefcfdf5990e441f0fb6f3fad709e21 | 601
+ (3,83) | 374 | 4.769 | ad972f10e0800b49d76fed33a21f6698 | 597
+ (8,26) | 802 | 4.672 | 1141938ba2c2b13f5505d7c424ebae5f | 608
+ (8,51) | 827 | 3.585 | fa3a3c407f82377f55c19c5d403335c7 | 621
+ (0,55) | 55 | 4.387 | b53b3a3d6ab90ce0268229151c9bde11 | 681
+ (3,47) | 338 | 1.571 | 819f46e52c25763a55cc642422644317 | 645
+ (5,33) | 518 | 9.49 | ebd9629fc3ae5e9f6611e2ee05a31cef | 597
+ (2,66) | 260 | 7.864 | a4f23670e1833f3fdb077ca70bbd5d66 | 640
+ (0,88) | 88 | 8.14 | 2a38a4a9316c49e5a833517c45d31070 | 592
+ (8,13) | 789 | 3.511 | 68053af2923e00204c3ca7c6a3150cf7 | 647
+(709 rows)
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+ WHERE c like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: ((c ~~ '%abc%'::text) AND (ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+ ctid quals: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: ((c ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+ ctid quals: ((ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+(3 rows)
+
+-- Also, EXPLAIN with none-text format
+EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------------------
+ <explain xmlns="http://www.postgresql.org/2009/explain"> +
+ <Query> +
+ <Plan> +
+ <Node-Type>Custom Scan</Node-Type> +
+ <Custom-Plan-Provider>ctidscan</Custom-Plan-Provider> +
+ <Relation-Name>t1</Relation-Name> +
+ <Alias>t1</Alias> +
+ <Filter>((c ~~ '%abc%'::text) AND (ctid >= '(0,0)'::tid) AND (ctid <= '(5,0)'::tid))</Filter>+
+ <ctid-quals>((ctid >= '(0,0)'::tid) AND (ctid <= '(5,0)'::tid))</ctid-quals> +
+ </Plan> +
+ </Query> +
+ </explain>
+(1 row)
+
+-- Turn off the feature
+SET enable_ctidscan = off;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
diff --git a/src/test/modules/ctidscan/sql/ctidscan.sql b/src/test/modules/ctidscan/sql/ctidscan.sql
new file mode 100644
index 0000000..c3ee864
--- /dev/null
+++ b/src/test/modules/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,99 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+SET SEED TO 0.20140702;
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+ a int primary key,
+ b float,
+ c text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*10000.0) / 1000.0, md5(i::text) FROM generate_series(1,1000) i);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+ x int primary key,
+ y float,
+ z text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*10000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,1200) i);
+VACUUM ANALYZE t2;
+
+CREATE TABLE t3 (
+ a int references t1(a),
+ x int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+
+RESET client_min_messages;
+--
+-- Check Plans if no special extensions are loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off)
+ SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+EXPLAIN (costs off, verbose)
+ SELECT count(*), ceil(b)
+ FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+ GROUP BY ceil(b);
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off)
+ SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+EXPLAIN (costs off, verbose)
+ SELECT count(*), ceil(b)
+ FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+ GROUP BY ceil(b);
+
+--
+-- Run the query without EXPLAIN
+--
+SELECT ctid,* FROM t1 WHERE ctid <= '(1,20)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid > '(10,0)'::tid;
+SELECT ctid,* FROM t1 WHERE c like '%678%' AND ctid >= '(3,50)'::tid;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT t1.ctid,* FROM t1 NATURAL JOIN t3 WHERE t3.ctid IN (
+ SELECT t3.ctid FROM t2 NATURAL JOIN t3
+ WHERE t2.ctid BETWEEN '(4,0)'::tid AND '(5,0)'::tid);
+
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+ WHERE c like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+
+-- Also, EXPLAIN with none-text format
+EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid);
+
+-- Turn off the feature
+SET enable_ctidscan = off;
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/src/test/regress/expected/custom_scan.out b/src/test/regress/expected/custom_scan.out
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/regress/sql/custom_scan.sql b/src/test/regress/sql/custom_scan.sql
new file mode 100644
index 0000000..683154b
--- /dev/null
+++ b/src/test/regress/sql/custom_scan.sql
@@ -0,0 +1,53 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+SET SEED 0.20140702;
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+ a int primary key,
+ b float,
+ c text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*1000.0) / 1000.0, md5(i::text) FROM generate_series(1,1000) i);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+ x int primary key,
+ y float,
+ z text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*1000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,1200) i);
+VACUUM ANALYZE t2;
+
+CREATE TABLE t3 (
+ a int references t1(a),
+ x int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+
+RESET client_min_messages;
+--
+-- Check Plans if no special extensions are loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8.0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+
+
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
On 12/24/14, 7:54 PM, Kouhei Kaigai wrote:
On Mon, Dec 15, 2014 at 4:55 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com>
wrote:
I'm not certain whether we should have this functionality in contrib
from the perspective of workload that can help, but its major worth
is for an example of custom-scan interface.worker_spi is now in src/test/modules. We may add it there as well, no?
Hmm, it makes sense for me. Does it also make sense to add a test-case to
the core regression test cases?The attached patch adds ctidscan module at test/module instead of contrib.
Basic portion is not changed from the previous post, but file locations
and test cases in regression test are changed.
First, I'm glad for this. It will be VERY valuable for anyone trying to clean up the end of a majorly bloated table.
Here's a partial review...
+++ b/src/test/modules/ctidscan/ctidscan.c
+/* missing declaration in pg_proc.h */
+#ifndef TIDGreaterOperator
+#define TIDGreaterOperator 2800
...
If we're calling that out here, should we have a corresponding comment in pg_proc.h, in case these ever get renumbered?
+CTidQualFromExpr(Node *expr, int varno)
...
+ if (IsCTIDVar(arg1, varno))
+ other = arg2;
+ else if (IsCTIDVar(arg2, varno))
+ other = arg1;
+ else
+ return NULL;
+ if (exprType(other) != TIDOID)
+ return NULL; /* should not happen */
+ /* The other argument must be a pseudoconstant */
+ if (!is_pseudo_constant_clause(other))
+ return NULL;
I think this needs some additional blank lines...
+ if (IsCTIDVar(arg1, varno))
+ other = arg2;
+ else if (IsCTIDVar(arg2, varno))
+ other = arg1;
+ else
+ return NULL;
+
+ if (exprType(other) != TIDOID)
+ return NULL; /* should not happen */
+
+ /* The other argument must be a pseudoconstant */
+ if (!is_pseudo_constant_clause(other))
+ return NULL;
+ * 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.
<grammar>That should read "same as what SeqScan is doing"... however, what actual function are you talking about? I couldn't find SeqScanEstimateCosts (or anything ending EstimateCosts).
BTW, there's other grammar issues but it'd be best to handle those all at once after all the code stuff is done.
+ opno = get_commutator(op->opno);
What happens if there's no commutator? Perhaps best to Assert(opno != InvalidOid) at the end of that if block.
Though, it seems things will fall apart anyway if ctid_quals isn't exactly what we're expecting; I don't know if that's OK or not.
+ /* disk costs --- assume each tuple on a different page */
+ run_cost += spc_random_page_cost * ntuples;
Isn't that extremely pessimistic?
I'm not familiar enough with the custom-scan stuff to really comment past this point, and I could certainly be missing some details about planning and execution.
I do have some concerns about the regression test, but perhaps I'm just being paranoid:
- The EXPLAIN tests don't cover >. They do cover <= and >= via BETWEEN.
- Nothing tests the case of '...'::tid op ctid; only lvalue cases are tested.
Also, it seems that we don't handle joining on a ctid qual... is that intentional? I know that sounds silly, but you'd probably want to do that if you're trying to move tuples off the end of a bloated table. You could work around it by constructing a dynamic SQL string, but it'd be easier to do something like:
UPDATE table1 SET ...
WHERE ctid >= (SELECT '(' || relpages || ',0)' FROM pg_class WHERE oid = 'table1'::regclass)
;
in some kind of loop.
Obviously better to only handle what you already are then not get this in at all though... :)
--
Jim Nasby, Data Architect, Blue Treble Consulting
Data in Trouble? Get it in Treble! http://BlueTreble.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Jim, Thanks for your reviewing the patch.
The attached patch is revised one according to your suggestion,
and also includes bug fix I could found.
* Definitions of TIDXXXXOperator was moved to pg_operator.h
as other operator doing.
* Support the case of "ctid (operator) Param" expression.
* Add checks if commutator of TID was not found.
* Qualifiers gets extracted on plan stage, not path stage.
* Adjust cost estimation logic to fit SeqScan manner.
* Add some new test cases for regression test.
On 12/24/14, 7:54 PM, Kouhei Kaigai wrote:
On Mon, Dec 15, 2014 at 4:55 PM, Kouhei Kaigai
<kaigai@ak.jp.nec.com>wrote:
I'm not certain whether we should have this functionality in
contrib from the perspective of workload that can help, but its
major worth is for an example of custom-scan interface.worker_spi is now in src/test/modules. We may add it there as well,
no?
Hmm, it makes sense for me. Does it also make sense to add a
test-case to the core regression test cases?The attached patch adds ctidscan module at test/module instead of contrib.
Basic portion is not changed from the previous post, but file
locations and test cases in regression test are changed.First, I'm glad for this. It will be VERY valuable for anyone trying to
clean up the end of a majorly bloated table.Here's a partial review...
+++ b/src/test/modules/ctidscan/ctidscan.c+/* missing declaration in pg_proc.h */ +#ifndef TIDGreaterOperator +#define TIDGreaterOperator 2800 ... If we're calling that out here, should we have a corresponding comment in pg_proc.h, in case these ever get renumbered?
It was a quick hack when I moved this module out of the tree.
Yep, I should revert when I submit this patch again.
+CTidQualFromExpr(Node *expr, int varno) ... + if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + if (exprType(other) != TIDOID) + return NULL; /* should not happen */ + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL;I think this needs some additional blank lines...
OK. And, I also noticed the coding style around this function is
different from other built-in plans, so I redefined the role of
this function just to check whether the supplied RestrictInfo is
OpExpr that involves TID inequality operator, or not.
Expression node shall be extracted when Plan node is created
from Path node.
+ if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + + if (exprType(other) != TIDOID) + return NULL; /* should not happen */ + + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL;+ * 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.<grammar>That should read "same as what SeqScan is doing"... however, what
actual function are you talking about? I couldn't find SeqScanEstimateCosts
(or anything ending EstimateCosts).
It is cost_seqscan(). But I don't put a raw function name in the source
code comments in other portion, because nobody can guarantee it is up to
date in the future...
BTW, there's other grammar issues but it'd be best to handle those all at
once after all the code stuff is done.
Yep. Help by native English speaker is very helpful for us.
+ opno = get_commutator(op->opno);
What happens if there's no commutator? Perhaps best to Assert(opno !=
InvalidOid) at the end of that if block.
Usually, commutator operator of TID is defined on initdb time, however,
nobody can guarantee a mad superuser doesn't drop it.
So, I added elog(ERROR,...) here.
Though, it seems things will fall apart anyway if ctid_quals isn't exactly
what we're expecting; I don't know if that's OK or not.
No worry, it was already checked on planning stage.
+ /* disk costs --- assume each tuple on a different page */ + run_cost += spc_random_page_cost * ntuples; Isn't that extremely pessimistic?
Probably. I follow the manner of SeqScan.
I'm not familiar enough with the custom-scan stuff to really comment past
this point, and I could certainly be missing some details about planning
and execution.I do have some concerns about the regression test, but perhaps I'm just
being paranoid:- The EXPLAIN tests don't cover >. They do cover <= and >= via BETWEEN.
- Nothing tests the case of '...'::tid op ctid; only lvalue cases are tested.
Both are added.
Also, it seems that we don't handle joining on a ctid qual... is that
intentional?
Yep. This module does not intend to handle joining, because custom-/
foreign-join interface is still under the discussion.
https://commitfest.postgresql.org/action/patch_view?id=1653
Hanada-san makes an enhancement of postgres_fdw on this enhancement.
https://commitfest.postgresql.org/action/patch_view?id=1674
I know that sounds silly, but you'd probably want to do that
if you're trying to move tuples off the end of a bloated table. You could
work around it by constructing a dynamic SQL string, but it'd be easier
to do something like:UPDATE table1 SET ...
WHERE ctid >= (SELECT '(' || relpages || ',0)' FROM pg_class WHERE oid
= 'table1'::regclass) ;
This example noticed me that the previous version didn't support the case
of "ctid (operator) Param".
So, I enhanced to support above case, and update the regression test also.
Thanks,
--
NEC OSS Promotion Center / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
Attachments:
pgsql-v9.5-test-ctidscan.v3.patchapplication/octet-stream; name=pgsql-v9.5-test-ctidscan.v3.patchDownload
doc/src/sgml/contrib.sgml | 1 +
doc/src/sgml/ctidscan.sgml | 52 ++
doc/src/sgml/filelist.sgml | 1 +
src/include/catalog/pg_operator.h | 4 +
src/test/modules/Makefile | 1 +
src/test/modules/ctidscan/Makefile | 16 +
src/test/modules/ctidscan/ctidscan.c | 828 ++++++++++++++++++++++++
src/test/modules/ctidscan/expected/ctidscan.out | 588 +++++++++++++++++
src/test/modules/ctidscan/sql/ctidscan.sql | 132 ++++
src/test/regress/expected/custom_scan.out | 0
src/test/regress/sql/custom_scan.sql | 53 ++
11 files changed, 1676 insertions(+)
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index a698d0f..59eab97 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -109,6 +109,7 @@ CREATE EXTENSION <replaceable>module_name</> FROM unpackaged;
&btree-gist;
&chkpass;
&citext;
+ &ctidscan;
&cube;
&dblink;
&dict-int;
diff --git a/doc/src/sgml/ctidscan.sgml b/doc/src/sgml/ctidscan.sgml
new file mode 100644
index 0000000..fa21b0f
--- /dev/null
+++ b/doc/src/sgml/ctidscan.sgml
@@ -0,0 +1,52 @@
+<!-- doc/src/sgml/ctidscan.sgml -->
+
+<sect1 id="ctidscan" xreflabel="ctidscan">
+ <title>ctidscan</title>
+
+ <indexterm zone="ctidscan">
+ <primary>ctidscan</primary>
+ </indexterm>
+
+ <para>
+ This module implements a custom-scan provider that utilizes inequality
+ operator that involves the <literal>ctid</literal> system column.
+ </para>
+
+ <para>
+ This module provides no SQL accessible interface. For installation,
+ all you need to do is just load the module to the server.
+
+ You can load it an individual session using:
+<programlisting>
+LOAD 'ctidscan';
+</programlisting>
+
+ or, you can also take more typical usage with extension preloading
+ using <xref linkend="guc-session-preload-libraries"> or
+ <xref linkend="guc-shared-preload-libraries"> in
+ <filename>postgresql.conf</>.
+
+ Then, planner may consider more cheap execution path if supplied query
+ involves above operators.
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <varname>enable_ctidscan</varname> (<type>bool</type>)
+ <indexterm>
+ <primary><varname>enable_ctidscan</> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ <varname>enable_ctidscan</varname> turns on/off functionality of
+ ctidscan custom-scan provider.
+ If turned off, it does not offer alternative scan path even if
+ supplied query is sufficient to run by ctidscan plan.
+ Its default is <literal>true</>.
+ Anybody can change using <command>SET</command> command.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</sect1>
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f03b72a..15d569e 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -107,6 +107,7 @@
<!ENTITY btree-gist SYSTEM "btree-gist.sgml">
<!ENTITY chkpass SYSTEM "chkpass.sgml">
<!ENTITY citext SYSTEM "citext.sgml">
+<!ENTITY ctidscan SYSTEM "ctidscan.sgml">
<!ENTITY cube SYSTEM "cube.sgml">
<!ENTITY dblink SYSTEM "dblink.sgml">
<!ENTITY dict-int SYSTEM "dict-int.sgml">
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 88c737b..2ab15a5 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -161,15 +161,19 @@ DESCR("equal");
#define TIDEqualOperator 387
DATA(insert OID = 402 ( "<>" PGNSP PGUID b f f 27 27 16 402 387 tidne neqsel neqjoinsel ));
DESCR("not equal");
+#define TIDNotEqualOperator 402
DATA(insert OID = 2799 ( "<" PGNSP PGUID b f f 27 27 16 2800 2802 tidlt scalarltsel scalarltjoinsel ));
DESCR("less than");
#define TIDLessOperator 2799
DATA(insert OID = 2800 ( ">" PGNSP PGUID b f f 27 27 16 2799 2801 tidgt scalargtsel scalargtjoinsel ));
DESCR("greater than");
+#define TIDGreaterOperator 2800
DATA(insert OID = 2801 ( "<=" PGNSP PGUID b f f 27 27 16 2802 2800 tidle scalarltsel scalarltjoinsel ));
DESCR("less than or equal");
+#define TIDLessEqualOperator 2801
DATA(insert OID = 2802 ( ">=" PGNSP PGUID b f f 27 27 16 2801 2799 tidge scalargtsel scalargtjoinsel ));
DESCR("greater than or equal");
+#define TIDGreaterEqualOperator 2802
DATA(insert OID = 410 ( "=" PGNSP PGUID b t t 20 20 16 410 411 int8eq eqsel eqjoinsel ));
DESCR("equal");
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 93d93af..c288276 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -6,6 +6,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = \
commit_ts \
+ ctidscan \
worker_spi \
dummy_seclabel \
test_shm_mq \
diff --git a/src/test/modules/ctidscan/Makefile b/src/test/modules/ctidscan/Makefile
new file mode 100644
index 0000000..bbe280a
--- /dev/null
+++ b/src/test/modules/ctidscan/Makefile
@@ -0,0 +1,16 @@
+# contrib/ctidscan/Makefile
+
+MODULES = ctidscan
+
+REGRESS = ctidscan
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/ctidscan
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/ctidscan/ctidscan.c b/src/test/modules/ctidscan/ctidscan.c
new file mode 100644
index 0000000..54f0c85
--- /dev/null
+++ b/src/test/modules/ctidscan/ctidscan.c
@@ -0,0 +1,828 @@
+/*
+ * ctidscan.c
+ *
+ * A custom-scan provide that utilizes ctid system column within
+ * inequality-operators, to skip block reads never referenced.
+ *
+ * It is designed to demonstrate Custom Scan APIs; that allows to override
+ * a part of executor node. This extension focus on a workload that tries
+ * to fetch records with tid larger or less than a particular value.
+ * In case when inequality operators were given, this module construct
+ * a custom scan path that enables to skip records not to be read. Then,
+ * if it was the cheapest one, it shall be used to run the query.
+ * Custom Scan APIs callbacks this extension when executor tries to fetch
+ * underlying records, then it utilizes existing heap_getnext() but seek
+ * the records to be read prior to fetching the first record.
+ *
+ * Portions Copyright (c) 2014, PostgreSQL Global Development Group
+ */
+#include "postgres.h"
+#include "access/relscan.h"
+#include "access/sysattr.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/explain.h"
+#include "executor/executor.h"
+#include "executor/nodeCustom.h"
+#include "fmgr.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
+#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
+#include "optimizer/planmain.h"
+#include "optimizer/placeholder.h"
+#include "optimizer/restrictinfo.h"
+#include "optimizer/subselect.h"
+#include "parser/parsetree.h"
+#include "storage/bufmgr.h"
+#include "storage/itemptr.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/ruleutils.h"
+#include "utils/spccache.h"
+
+PG_MODULE_MAGIC;
+
+/*
+ * NOTE: We don't use any special data type to save the private data.
+ * All we want to save in private fields is expression-list that shall
+ * be adjusted by setrefs.c/subselect.c, so we put it on the custom_exprs
+ * of CustomScan structure, not custom_private field.
+ * Due to the interface contract, only expression nodes are allowed to put
+ * on the custom_exprs, and we have to pay attention the core backend may
+ * adjust expression items.
+ */
+
+/*
+ * CtidScanState - state object of ctidscan on executor.
+ * It has few additional internal state. The 'ctid_quals' has list of
+ * ExprState for inequality operators that involve ctid system column.
+ */
+typedef struct {
+ CustomScanState css;
+ List *ctid_quals; /* list of ExprState for inequality ops */
+} CtidScanState;
+
+/* static variables */
+static bool enable_ctidscan;
+static set_rel_pathlist_hook_type set_rel_pathlist_next = NULL;
+
+/* function declarations */
+void _PG_init(void);
+
+static void SetCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ Index rti,
+ RangeTblEntry *rte);
+/* CustomPathMethods */
+static Plan *PlanCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ CustomPath *best_path,
+ List *tlist,
+ List *clauses);
+
+/* CustomScanMethods */
+static Node *CreateCtidScanState(CustomScan *custom_plan);
+
+/* CustomScanExecMethods */
+static void BeginCtidScan(CustomScanState *node, EState *estate, int eflags);
+static void ReScanCtidScan(CustomScanState *node);
+static TupleTableSlot *ExecCtidScan(CustomScanState *node);
+static void EndCtidScan(CustomScanState *node);
+static void ExplainCtidScan(CustomScanState *node, List *ancestors,
+ ExplainState *es);
+
+/* static table of custom-scan callbacks */
+static CustomPathMethods ctidscan_path_methods = {
+ "ctidscan", /* CustomName */
+ PlanCtidScanPath, /* PlanCustomPath */
+ NULL, /* TextOutCustomPath */
+};
+
+static CustomScanMethods ctidscan_scan_methods = {
+ "ctidscan", /* CustomName */
+ CreateCtidScanState, /* CreateCustomScanState */
+ NULL, /* TextOutCustomScan */
+};
+
+static CustomExecMethods ctidscan_exec_methods = {
+ "ctidscan", /* CustomName */
+ BeginCtidScan, /* BeginCustomScan */
+ ExecCtidScan, /* ExecCustomScan */
+ EndCtidScan, /* EndCustomScan */
+ ReScanCtidScan, /* ReScanCustomScan */
+ NULL, /* MarkPosCustomScan */
+ NULL, /* RestrPosCustomScan */
+ ExplainCtidScan, /* ExplainCustomScan */
+};
+
+#define IsCTIDVar(node,rtindex) \
+ ((node) != NULL && \
+ IsA((node), Var) && \
+ ((Var *) (node))->varno == (rtindex) && \
+ ((Var *) (node))->varattno == SelfItemPointerAttributeNumber && \
+ ((Var *) (node))->varlevelsup == 0)
+
+/*
+ * IsCTidInequalExpr
+ *
+ * It checks whether the given restriction clauses enables to determine
+ * the zone to be scanned, or not.
+ */
+static bool
+IsCTidInequalExpr(RelOptInfo *rel, RestrictInfo *rinfo)
+{
+ if (is_opclause(rinfo->clause))
+ {
+ OpExpr *op = (OpExpr *) rinfo->clause;
+ Node *arg1;
+ Node *arg2;
+ Node *other = NULL;
+
+ /* only inequality operators are candidate */
+ if (op->opno != TIDLessOperator &&
+ op->opno != TIDLessEqualOperator &&
+ op->opno != TIDGreaterOperator &&
+ op->opno != TIDGreaterEqualOperator)
+ return false;
+
+ if (list_length(op->args) != 2)
+ return false; /* should not happen */
+
+ arg1 = linitial(op->args);
+ arg2 = lsecond(op->args);
+
+ if (IsCTIDVar(arg1, rel->relid))
+ other = arg2;
+ else if (IsCTIDVar(arg2, rel->relid))
+ other = arg1;
+ else
+ return false;
+
+ if (exprType(other) != TIDOID)
+ return false; /* should not happen */
+
+ /* The other argument must be a pseudoconstant */
+ if (!is_pseudo_constant_clause(other))
+ return false;
+
+ return true;
+ }
+ return false;
+}
+
+/*
+ * CTidEstimateCosts
+ *
+ * It estimates cost to scan the target relation according to the given
+ * restriction clauses. Its logic to scan relations are almost same as
+ * SeqScan doing, because it uses regular heap_getnext(), except for
+ * the number of tuples to be scanned if restriction clauses work well.
+*/
+static void
+CTidEstimateCosts(PlannerInfo *root,
+ RelOptInfo *baserel,
+ CustomPath *cpath)
+{
+ Path *path = &cpath->path;
+ List *ctid_quals = cpath->custom_private;
+ ListCell *lc;
+ double ntuples;
+ ItemPointerData ip_min;
+ ItemPointerData ip_max;
+ bool has_min_val = false;
+ bool has_max_val = false;
+ BlockNumber num_pages;
+ Cost startup_cost = 0;
+ Cost run_cost = 0;
+ Cost cpu_per_tuple;
+ QualCost qpqual_cost;
+ QualCost ctid_qual_cost;
+ double spc_seq_page_cost;
+
+ /* Should only be applied to base relations */
+ Assert(baserel->relid > 0);
+ Assert(baserel->rtekind == RTE_RELATION);
+
+ /* Mark the path with the correct row estimate */
+ if (path->param_info)
+ path->rows = path->param_info->ppi_rows;
+ else
+ path->rows = baserel->rows;
+
+ /* Estimate how many tuples we may retrieve */
+ ItemPointerSet(&ip_min, 0, 0);
+ ItemPointerSet(&ip_max, MaxBlockNumber, MaxOffsetNumber);
+ foreach (lc, ctid_quals)
+ {
+ RestrictInfo *rinfo = lfirst(lc);
+ OpExpr *op = (OpExpr *) rinfo->clause;
+ Oid opno;
+ Node *other;
+
+ Assert(is_opclause(op));
+ if (IsCTIDVar(linitial(op->args), baserel->relid))
+ {
+ opno = op->opno;
+ other = lsecond(op->args);
+ }
+ else if (IsCTIDVar(lsecond(op->args), baserel->relid))
+ {
+ /* To simplifies, we assume as if Var node is 1st argument */
+ opno = get_commutator(op->opno);
+ if (!OidIsValid(opno))
+ elog(ERROR, "could not find commutator for operator %u",
+ op->opno);
+ other = linitial(op->args);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ if (IsA(other, Const))
+ {
+ ItemPointer ip = (ItemPointer)(((Const *) other)->constvalue);
+
+ /*
+ * Just an rough estimation, we don't distinct inequality and
+ * inequality-or-equal operator from scan-size estimation
+ * perspective.
+ */
+ switch (opno)
+ {
+ case TIDLessOperator:
+ case TIDLessEqualOperator:
+ if (ItemPointerCompare(ip, &ip_max) < 0)
+ ItemPointerCopy(ip, &ip_max);
+ has_max_val = true;
+ break;
+ case TIDGreaterOperator:
+ case TIDGreaterEqualOperator:
+ if (ItemPointerCompare(ip, &ip_min) > 0)
+ ItemPointerCopy(ip, &ip_min);
+ has_min_val = true;
+ break;
+ default:
+ elog(ERROR, "unexpected operator code: %u", opno);
+ break;
+ }
+ }
+ else if (IsA(other, Param))
+ {
+ ItemPointerData temp;
+
+ /*
+ * Just a "very" rough estimation. We cannot know exact value
+ * of parameter until execution stage, so assume roughly
+ * 33% blocks shall be read by an operator.
+ */
+ switch (opno)
+ {
+ case TIDLessOperator:
+ case TIDLessEqualOperator:
+ ItemPointerSetBlockNumber(&temp, baserel->pages * 2 / 3);
+ ItemPointerSetOffsetNumber(&temp, 0);
+ if (ItemPointerCompare(&temp, &ip_max) < 0)
+ ItemPointerCopy(&temp, &ip_max);
+ has_max_val = true;
+ break;
+ case TIDGreaterOperator:
+ case TIDGreaterEqualOperator:
+ ItemPointerSetBlockNumber(&temp, baserel->pages / 3);
+ ItemPointerSetOffsetNumber(&temp, 0);
+ if (ItemPointerCompare(&temp, &ip_max) > 0)
+ ItemPointerCopy(&temp, &ip_max);
+ has_min_val = true;
+ break;
+ default:
+ elog(ERROR, "unexpected operator code: %u", op->opno);
+ break;
+ }
+ }
+ }
+
+ /* estimate min/max block numbers */
+ if (has_min_val && has_max_val)
+ {
+ /* case of both side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Max(bnum_max - bnum_min + 1, 1);
+ }
+ else if (has_min_val)
+ {
+ /* case of only lower side being bounded */
+ BlockNumber bnum_max = baserel->pages;
+ BlockNumber bnum_min = BlockIdGetBlockNumber(&ip_min.ip_blkid);
+
+ bnum_min = Max(bnum_min, 0);
+ num_pages = Max(bnum_max - bnum_min, 0) + 1;
+ }
+ else if (has_max_val)
+ {
+ /* case of only upper side being bounded */
+ BlockNumber bnum_max = BlockIdGetBlockNumber(&ip_max.ip_blkid);
+ BlockNumber bnum_min = 0;
+
+ bnum_max = Min(bnum_max, baserel->pages);
+ num_pages = Max(bnum_max - bnum_min, 0) + 1;
+ }
+ else
+ {
+ /*
+ * Just a rough estimation. We assume half of records shall be
+ * read using this restriction clause, but indeterministic until
+ * executor run it actually.
+ */
+ num_pages = Max((baserel->pages + 1) / 2, 1);
+ }
+ ntuples = baserel->tuples * (double) num_pages / (double) baserel->pages;
+
+ /*
+ * The TID qual expressions will be computed once, any other baserestrict
+ * quals once per retrieved tuple.
+ */
+ cost_qual_eval(&ctid_qual_cost, ctid_quals, root);
+
+ /* fetch estimated page cost for tablespace containing table */
+ get_tablespace_page_costs(baserel->reltablespace,
+ NULL,
+ &spc_seq_page_cost);
+
+ /* Disk costs */
+ run_cost += spc_seq_page_cost * num_pages;
+
+ /* CPU costs; logic originates get_restriction_qual_cost() */
+ if (path->param_info)
+ {
+ /* Include costs of pushed-down clauses */
+ cost_qual_eval(&qpqual_cost, path->param_info->ppi_clauses, root);
+
+ qpqual_cost.startup += baserel->baserestrictcost.startup;
+ qpqual_cost.per_tuple += baserel->baserestrictcost.per_tuple;
+ }
+ else
+ qpqual_cost = baserel->baserestrictcost;
+
+ /*
+ * We don't eliminate the cost for the ctid inequality operators,
+ * because it is still subset of qpquals and needs to be evaluated
+ * on run time to check offset-number (even we can skip blocks that
+ * are never referenced obviously).
+ */
+ startup_cost += qpqual_cost.startup;
+ cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
+ run_cost += cpu_per_tuple * ntuples;
+
+ /* Just a spice to prefer SeqScan, if inequality operators tooks
+ * obvious out-of-range thus its cost is identical with SeqScan.
+ */
+ startup_cost += 1.0;
+
+ path->startup_cost = startup_cost;
+ path->total_cost = startup_cost + run_cost;
+}
+
+/*
+ * SetCtidScanPath - entrypoint of the series of custom-scan execution.
+ * It adds CustomPath if referenced relation has inequality expressions on
+ * the ctid system column.
+ */
+static void
+SetCtidScanPath(PlannerInfo *root, RelOptInfo *baserel,
+ Index rtindex, RangeTblEntry *rte)
+{
+ char relkind;
+ ListCell *lc;
+ List *ctid_quals = NIL;
+
+ /* only plain relations are supported */
+ if (rte->rtekind != RTE_RELATION)
+ return;
+ relkind = get_rel_relkind(rte->relid);
+ if (relkind != RELKIND_RELATION &&
+ relkind != RELKIND_MATVIEW &&
+ relkind != RELKIND_TOASTVALUE)
+ return;
+
+ /*
+ * NOTE: Unlike built-in execution path, always we can have core path
+ * even though ctid scan is not available. So, simply, we don't add
+ * any paths, instead of adding disable_cost.
+ */
+ if (!enable_ctidscan)
+ return;
+
+ /* walk on the restrict info */
+ foreach (lc, baserel->baserestrictinfo)
+ {
+ RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
+
+ Assert(IsA(rinfo, RestrictInfo));
+ if (IsCTidInequalExpr(baserel, rinfo))
+ ctid_quals = lappend(ctid_quals, rinfo);
+ }
+
+ /*
+ * OK, it is case when a part of restriction clause makes sense to
+ * reduce number of tuples, so we will add a custom scan path being
+ * provided by this module.
+ */
+ if (ctid_quals != NIL)
+ {
+ CustomPath *cpath;
+ Relids required_outer;
+
+ /*
+ * We don't support pushing join clauses into the quals of a ctidscan,
+ * but it could still have required parameterization due to LATERAL
+ * refs in its tlist.
+ */
+ required_outer = baserel->lateral_relids;
+
+ cpath = palloc0(sizeof(CustomPath));
+ cpath->path.type = T_CustomPath;
+ cpath->path.pathtype = T_CustomScan;
+ cpath->path.parent = baserel;
+ cpath->path.param_info
+ = get_baserel_parampathinfo(root, baserel, required_outer);
+ cpath->flags = CUSTOMPATH_SUPPORT_BACKWARD_SCAN;
+ cpath->custom_private = ctid_quals;
+ cpath->methods = &ctidscan_path_methods;
+
+ CTidEstimateCosts(root, baserel, cpath);
+
+ add_path(baserel, &cpath->path);
+ }
+}
+
+/*
+ * PlanCtidScanPlan - A method of CustomPath; that populate a custom
+ * object being delivered from CustomScan type, according to the supplied
+ * CustomPath object.
+ */
+static Plan *
+PlanCtidScanPath(PlannerInfo *root,
+ RelOptInfo *rel,
+ CustomPath *best_path,
+ List *tlist,
+ List *clauses)
+{
+ CustomScan *cscan = makeNode(CustomScan);
+ List *scan_quals = extract_actual_clauses(clauses, false);
+ List *ctid_quals
+ = extract_actual_clauses(best_path->custom_private, false);
+
+ cscan->flags = best_path->flags;
+ cscan->methods = &ctidscan_scan_methods;
+
+ /* set scanrelid */
+ cscan->scan.scanrelid = rel->relid;
+ /* set targetlist as is */
+ cscan->scan.plan.targetlist = tlist;
+ /* reduce RestrictInfo list to bare expressions */
+ cscan->scan.plan.qual = list_difference(scan_quals, ctid_quals);
+ /* set ctid related quals */
+ cscan->custom_exprs = ctid_quals;
+
+ return &cscan->scan.plan;
+}
+
+/*
+ * CreateCtidScanState - A method of CustomScan; that populate a custom
+ * object being delivered from CustomScanState type, according to the
+ * supplied CustomPath object.
+ */
+static Node *
+CreateCtidScanState(CustomScan *custom_plan)
+{
+ CtidScanState *ctss = palloc0(sizeof(CtidScanState));
+
+ NodeSetTag(ctss, T_CustomScanState);
+ ctss->css.flags = custom_plan->flags;
+ ctss->css.methods = &ctidscan_exec_methods;
+
+ return (Node *)&ctss->css;
+}
+
+/*
+ * BeginCtidScan - A method of CustomScanState; that initializes
+ * the supplied CtidScanState object, at beginning of the executor.
+ */
+static void
+BeginCtidScan(CustomScanState *node, EState *estate, int eflags)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CustomScan *cscan = (CustomScan *) node->ss.ps.plan;
+
+ /*
+ * In case of custom-scan provider that offers an alternative way
+ * to scan a particular relation, most of the needed initialization,
+ * like relation open or assignment of scan tuple-slot or projection
+ * info, shall be done by the core implementation. So, all we need
+ * to have is initialization of own local properties.
+ */
+ ctss->ctid_quals = (List *)
+ ExecInitExpr((Expr *)cscan->custom_exprs, &node->ss.ps);
+}
+
+/*
+ * ReScanCtidScan - A method of CustomScanState; that rewind the current
+ * seek position.
+ */
+static void
+ReScanCtidScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *)node;
+ HeapScanDesc scan = ctss->css.ss.ss_currentScanDesc;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ Relation relation = ctss->css.ss.ss_currentRelation;
+ ExprContext *econtext = ctss->css.ss.ps.ps_ExprContext;
+ ScanKeyData keys[2];
+ bool has_ubound = false;
+ bool has_lbound = false;
+ ItemPointerData ip_max;
+ ItemPointerData ip_min;
+ ListCell *lc;
+
+ /* once close the existing scandesc, if any */
+ if (scan)
+ {
+ heap_endscan(scan);
+ scan = ctss->css.ss.ss_currentScanDesc = NULL;
+ }
+
+ /* walks on the inequality operators */
+ foreach (lc, ctss->ctid_quals)
+ {
+ FuncExprState *fexstate = (FuncExprState *) lfirst(lc);
+ OpExpr *op = (OpExpr *)fexstate->xprstate.expr;
+ Node *arg1 = linitial(op->args);
+ Node *arg2 = lsecond(op->args);
+ Index scanrelid;
+ Oid opno;
+ ExprState *exstate;
+ ItemPointer itemptr;
+ Datum value;
+ bool isnull;
+
+ scanrelid = ((Scan *)ctss->css.ss.ps.plan)->scanrelid;
+ if (IsCTIDVar(arg1, scanrelid))
+ {
+ exstate = (ExprState *) lsecond(fexstate->args);
+ opno = op->opno;
+ }
+ else if (IsCTIDVar(arg2, scanrelid))
+ {
+ exstate = (ExprState *) linitial(fexstate->args);
+ opno = get_commutator(op->opno);
+ if (!OidIsValid(opno))
+ elog(ERROR, "could not find commutator for operator %u",
+ op->opno);
+ }
+ else
+ elog(ERROR, "could not identify CTID variable");
+
+ /* evaluate the CTID value */
+ value = ExecEvalExprSwitchContext(exstate,
+ econtext,
+ &isnull,
+ NULL);
+ if (isnull)
+ {
+ /*
+ * Whole of the restriction clauses chained with AND- boolean
+ * operators because false, if one of the clauses has NULL result.
+ * So, we can immediately break the evaluation to inform caller
+ * it does not make sense to scan any more.
+ * In this case, scandesc is kept to NULL.
+ */
+ return;
+ }
+
+ /*
+ * NOTE: ScanKeyInit saves only pointers, so we have to keep the
+ * ItemPointerData variable on somewhere available during the
+ * query execution.
+ */
+ itemptr = MemoryContextAlloc(estate->es_query_cxt,
+ sizeof(ItemPointerData));
+ ItemPointerCopy((ItemPointer) DatumGetPointer(value), itemptr);
+
+ switch (opno)
+ {
+ case TIDLessOperator:
+ if (!has_ubound || ItemPointerCompare(itemptr, &ip_max) <= 0)
+ {
+ ScanKeyInit(&keys[0],
+ SelfItemPointerAttributeNumber,
+ BTLessStrategyNumber,
+ F_TIDLT,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_max);
+ has_ubound = true;
+ }
+ break;
+
+ case TIDLessEqualOperator:
+ if (!has_ubound || ItemPointerCompare(itemptr, &ip_max) < 0)
+ {
+ ScanKeyInit(&keys[0],
+ SelfItemPointerAttributeNumber,
+ BTLessEqualStrategyNumber,
+ F_TIDLE,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_max);
+ has_ubound = true;
+ }
+ break;
+
+
+ case TIDGreaterOperator:
+ if (!has_lbound || ItemPointerCompare(itemptr, &ip_min) >= 0)
+ {
+ ScanKeyInit(&keys[1],
+ SelfItemPointerAttributeNumber,
+ BTGreaterStrategyNumber,
+ F_TIDGT,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_min);
+ has_lbound = true;
+ }
+ break;
+
+ case TIDGreaterEqualOperator:
+ if (!has_lbound || ItemPointerCompare(itemptr, &ip_min) > 0)
+ {
+ ScanKeyInit(&keys[1],
+ SelfItemPointerAttributeNumber,
+ BTGreaterEqualStrategyNumber,
+ F_TIDGE,
+ PointerGetDatum(itemptr));
+ ItemPointerCopy(itemptr, &ip_min);
+ has_lbound = true;
+ }
+ break;
+
+ default:
+ elog(ERROR, "unsupported operator");
+ break;
+ }
+ }
+ /* begin heapscan with the key above */
+ if (has_ubound && has_lbound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 2, &keys[0]);
+ else if (has_ubound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[0]);
+ else if (has_lbound)
+ scan = heap_beginscan(relation, estate->es_snapshot, 1, &keys[1]);
+ else
+ scan = heap_beginscan(relation, estate->es_snapshot, 0, NULL);
+
+ /* Seek the starting position, if possible */
+ if (direction == ForwardScanDirection && has_lbound)
+ {
+ BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_min.ip_blkid) - 1,
+ scan->rs_nblocks - 1);
+ heap_setscanlimits(scan, blknum, scan->rs_nblocks - blknum);
+ }
+ else if (direction == BackwardScanDirection && has_ubound)
+ {
+ BlockNumber blknum = Min(BlockIdGetBlockNumber(&ip_max.ip_blkid),
+ scan->rs_nblocks - 1);
+ heap_setscanlimits(scan, 0, blknum);
+ }
+ ctss->css.ss.ss_currentScanDesc = scan;
+}
+
+/*
+ * CTidAccessCustomScan
+ *
+ * Access method of ExecCtidScan below. It fetches a tuple from the underlying
+ * heap scan that was started from the point according to the tid clauses.
+ */
+static TupleTableSlot *
+CTidAccessCustomScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ HeapScanDesc scan;
+ TupleTableSlot *slot;
+ EState *estate = node->ss.ps.state;
+ ScanDirection direction = estate->es_direction;
+ HeapTuple tuple;
+
+ if (!ctss->css.ss.ss_currentScanDesc)
+ ReScanCtidScan(node);
+ scan = ctss->css.ss.ss_currentScanDesc;
+ Assert(scan != NULL);
+
+ /*
+ * get the next tuple from the table
+ */
+ tuple = heap_getnext(scan, direction);
+ if (!HeapTupleIsValid(tuple))
+ return NULL;
+
+ slot = ctss->css.ss.ss_ScanTupleSlot;
+ ExecStoreTuple(tuple, slot, scan->rs_cbuf, false);
+
+ return slot;
+}
+
+static bool
+CTidRecheckCustomScan(CustomScanState *node, TupleTableSlot *slot)
+{
+ return true;
+}
+
+/*
+ * ExecCtidScan - A method of CustomScanState; that fetches a tuple
+ * from the relation, if exist anymore.
+ */
+static TupleTableSlot *
+ExecCtidScan(CustomScanState *node)
+{
+ return ExecScan(&node->ss,
+ (ExecScanAccessMtd) CTidAccessCustomScan,
+ (ExecScanRecheckMtd) CTidRecheckCustomScan);
+}
+
+/*
+ * CTidEndCustomScan - A method of CustomScanState; that closes heap and
+ * scan descriptor, and release other related resources.
+ */
+static void
+EndCtidScan(CustomScanState *node)
+{
+ CtidScanState *ctss = (CtidScanState *)node;
+
+ if (ctss->css.ss.ss_currentScanDesc)
+ heap_endscan(ctss->css.ss.ss_currentScanDesc);
+}
+
+/*
+ * ExplainCtidScan - A method of CustomScanState; that shows extra info
+ * on EXPLAIN command.
+ */
+static void
+ExplainCtidScan(CustomScanState *node, List *ancestors, ExplainState *es)
+{
+ CtidScanState *ctss = (CtidScanState *) node;
+ CustomScan *cscan = (CustomScan *) ctss->css.ss.ps.plan;
+
+ /* logic copied from show_qual and show_expression */
+ if (cscan->custom_exprs)
+ {
+ bool useprefix = es->verbose;
+ Node *qual;
+ List *context;
+ char *exprstr;
+
+ /* Convert AND list to explicit AND */
+ qual = (Node *) make_ands_explicit(cscan->custom_exprs);
+
+ /* Set up deparsing context */
+ context = deparse_context_for_planstate((Node *)&node->ss.ps,
+ ancestors,
+ es->rtable,
+ es->rtable_names);
+
+ /* Deparse the expression */
+ exprstr = deparse_expression(qual, context, useprefix, false);
+
+ /* And add to es->str */
+ ExplainPropertyText("TID Range", exprstr, es);
+ }
+}
+
+/*
+ * Entrypoint of this extension
+ */
+void
+_PG_init(void)
+{
+ DefineCustomBoolVariable("enable_ctidscan",
+ "Enables the planner's use of ctid-scan plans.",
+ NULL,
+ &enable_ctidscan,
+ true,
+ PGC_USERSET,
+ GUC_NOT_IN_SAMPLE,
+ NULL, NULL, NULL);
+
+ /* registration of the hook to add alternative path */
+ set_rel_pathlist_next = set_rel_pathlist_hook;
+ set_rel_pathlist_hook = SetCtidScanPath;
+}
diff --git a/src/test/modules/ctidscan/expected/ctidscan.out b/src/test/modules/ctidscan/expected/ctidscan.out
new file mode 100644
index 0000000..b16f088
--- /dev/null
+++ b/src/test/modules/ctidscan/expected/ctidscan.out
@@ -0,0 +1,588 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+-- construction of test data
+SET client_min_messages TO 'warning';
+SET SEED TO 0.20140702;
+CREATE SCHEMA regtest_custom_scan;
+SET search_path TO regtest_custom_scan, public;
+CREATE TABLE t1 (
+ a int primary key,
+ b float,
+ c text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*10000.0) / 1000.0, md5(i::text) FROM generate_series(1,40000) i);
+VACUUM ANALYZE t1;
+CREATE TABLE t2 (
+ x int primary key,
+ y float,
+ z text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*10000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,40200) i);
+VACUUM ANALYZE t2;
+CREATE TABLE t3 (
+ a int references t1(a),
+ x int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+RESET client_min_messages;
+set log_error_verbosity = verbose;
+--
+-- Check Plans if no special extensions are loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+ QUERY PLAN
+---------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+ Filter: (ctid < '(6,0)'::tid)
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+ QUERY PLAN
+------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((c ~~ '%789%'::text) AND (ctid < '(5,0)'::tid))
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE '(8,0)'::tid < ctid AND c like '%ab%';
+ QUERY PLAN
+-----------------------------------------------------------
+ Seq Scan on t1
+ Filter: (('(8,0)'::tid < ctid) AND (c ~~ '%ab%'::text))
+(2 rows)
+
+EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%';
+ QUERY PLAN
+----------------------------------------------------------------
+ Seq Scan on t1 (cost=0.00..1013.00 rows=135 width=45)
+ Filter: (('(9999,99)'::tid > ctid) AND (c ~~ '%def%'::text))
+(2 rows)
+
+EXPLAIN (costs off)
+ SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+ QUERY PLAN
+--------------------------------------------------------------------------------
+ Nested Loop
+ -> Nested Loop
+ -> Seq Scan on t3
+ Filter: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid))
+ -> Index Scan using t1_pkey on t1
+ Index Cond: (a = t3.a)
+ Filter: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid))
+ -> Index Scan using t2_pkey on t2
+ Index Cond: (x = t3.x)
+ Filter: ((ctid >= '(4,9999)'::tid) AND (ctid <= '(8,0)'::tid))
+(10 rows)
+
+EXPLAIN (costs off, verbose)
+ SELECT count(*), ceil(b)
+ FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+ GROUP BY ceil(b);
+ QUERY PLAN
+----------------------------------------------------------------------------
+ HashAggregate
+ Output: count(*), (ceil(b))
+ Group Key: ceil(t1.b)
+ -> Seq Scan on regtest_custom_scan.t1
+ Output: ceil(b)
+ Filter: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid))
+(6 rows)
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+ QUERY PLAN
+---------------------------------
+ Index Scan using t1_pkey on t1
+ Index Cond: (a = 40)
+ Filter: (ctid < '(6,0)'::tid)
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+ QUERY PLAN
+------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: (c ~~ '%789%'::text)
+ TID Range: (ctid < '(5,0)'::tid)
+(3 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+ QUERY PLAN
+------------------------------------
+ Tid Scan on t1
+ TID Cond: (ctid = '(2,10)'::tid)
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+---------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ TID Range: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+EXPLAIN (costs off) SELECT * FROM t1 WHERE '(8,0)'::tid < ctid AND c like '%ab%';
+ QUERY PLAN
+------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: (c ~~ '%ab%'::text)
+ TID Range: ('(8,0)'::tid < ctid)
+(3 rows)
+
+EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%'; -- SeqScan is cheaper
+ QUERY PLAN
+----------------------------------------------------------------
+ Seq Scan on t1 (cost=0.00..1013.00 rows=135 width=45)
+ Filter: (('(9999,99)'::tid > ctid) AND (c ~~ '%def%'::text))
+(2 rows)
+
+EXPLAIN (costs off)
+ SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------------------------
+ Nested Loop
+ -> Hash Join
+ Hash Cond: (t2.x = t3.x)
+ -> Custom Scan (ctidscan) on t2
+ TID Range: ((ctid >= '(4,9999)'::tid) AND (ctid <= '(8,0)'::tid))
+ -> Hash
+ -> Custom Scan (ctidscan) on t3
+ TID Range: ((ctid >= '(2,0)'::tid) AND (ctid <= '(5,0)'::tid))
+ -> Index Scan using t1_pkey on t1
+ Index Cond: (a = t3.a)
+ Filter: ((ctid >= '(3,10)'::tid) AND (ctid <= '(10,9999)'::tid))
+(11 rows)
+
+EXPLAIN (costs off, verbose)
+ SELECT count(*), ceil(b)
+ FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+ GROUP BY ceil(b);
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ HashAggregate
+ Output: count(*), (ceil(b))
+ Group Key: ceil(t1.b)
+ -> Custom Scan (ctidscan) on regtest_custom_scan.t1
+ Output: ceil(b)
+ TID Range: ((t1.ctid >= '(3,0)'::tid) AND (t1.ctid <= '(10,0)'::tid))
+(6 rows)
+
+--
+-- Run the query without EXPLAIN
+--
+SELECT ctid,* FROM t1 WHERE ctid <= '(1,20)'::tid ORDER BY ctid DESC LIMIT 25;
+ ctid | a | b | c
+--------+-----+-------+----------------------------------
+ (1,20) | 117 | 2.893 | eb160de1de89d9058fcb0b968dbbbd68
+ (1,19) | 116 | 4.68 | c45147dee729311ef5b5c3003946c48f
+ (1,18) | 115 | 3.107 | 2b44928ae11fb9384c4cf38708677c48
+ (1,17) | 114 | 0.675 | 5fd0b37cd7dbbb00f97ba6ce92bf5add
+ (1,16) | 113 | 8.544 | 73278a4a86960eeb576a8fd4c9ec6997
+ (1,15) | 112 | 4.443 | 7f6ffaa6bb0b408017b62254211691b5
+ (1,14) | 111 | 2.845 | 698d51a19d8a121ce581499d7b701668
+ (1,13) | 110 | 1.052 | 5f93f983524def3dca464469d2cf9f3e
+ (1,12) | 109 | 1.784 | 2723d092b63885e0d7c260cc007e8b9d
+ (1,11) | 108 | 2.646 | a3c65c2974270fd093ee8a9bf8ae7d0b
+ (1,10) | 107 | 3.065 | a97da629b098b75c294dffdc3e463904
+ (1,9) | 106 | 3.412 | f0935e4cd5920aa6c7c996a5ee53a70f
+ (1,8) | 105 | 8.583 | 65b9eea6e1cc6bb9f0cd2a47751a186f
+ (1,7) | 104 | 6.715 | c9e1074f5b3f9fc8ea15d152add07294
+ (1,6) | 103 | 4.109 | 6974ce5ac660610b44d9b9fed0ff9548
+ (1,5) | 102 | 9.121 | ec8956637a99787bd197eacd77acce5e
+ (1,4) | 101 | 4.645 | 38b3eff8baf56627478ec76a704e9b52
+ (1,3) | 100 | 1.237 | f899139df5e1059396431415e770c6dd
+ (1,2) | 99 | 5.83 | ac627ab1ccbdb62ec96e702f07f6425b
+ (1,1) | 98 | 4.243 | ed3d2c21991e3bef5e069713af9fa6ca
+ (0,97) | 97 | 3.799 | e2ef524fbf3d9fe611d5a8e90fefdc9c
+ (0,96) | 96 | 2.132 | 26657d5ff9020d2abefe558796b99584
+ (0,95) | 95 | 6.077 | 812b4ba287f5ee0bc9d43bbf5bbe87fb
+ (0,94) | 94 | 0.365 | f4b9ec30ad9f68f89b29639786cb62ef
+ (0,93) | 93 | 2.718 | 98dce83da57b0395e163467c9dae521b
+(25 rows)
+
+SELECT count(*) FROM t1 WHERE ctid > '(200,0)'::tid;
+ count
+-------
+ 20600
+(1 row)
+
+SELECT ctid,* FROM t1 WHERE c like '%678%' AND ctid >= '(3,50)'::tid LIMIT 25;
+ ctid | a | b | c
+---------+------+-------+----------------------------------
+ (6,78) | 660 | 2.946 | 68264bdb65b97eeae6788aa3348e553c
+ (7,43) | 722 | 1.612 | c8ed21db4f678f3b13b9d5ee16489088
+ (8,36) | 812 | 3.673 | 81e74d678581a3bb7a720b019f4f1a93
+ (9,97) | 970 | 7.977 | 89fcd07f20b6785b92134bd6c1d0fa42
+ (11,60) | 1127 | 8.636 | 678a1491514b7f1006d605e9161946b1
+ (14,10) | 1368 | 9.3 | 6c1da886822c67822bcf3679d04369fa
+ (14,41) | 1399 | 5.773 | 602d1305678a8d5fdb372271e980da6a
+ (14,94) | 1452 | 2.564 | 6786f3c62fbf9021694f6e51cc07fe3c
+ (16,74) | 1626 | 8.511 | bc573864331a9e42e4511de6f678aa83
+ (17,25) | 1674 | 4.681 | 757f843a169cc678064d9530d12a1881
+ (17,56) | 1705 | 8.138 | 2a27b8144ac02f67687f76782a3b5d8f
+ (18,90) | 1836 | 4.489 | f26dab9bf6a137c3b6782e562794c2f2
+ (19,41) | 1884 | 0.274 | f0fcf351df4eb6786e9bb6fc4e2dee02
+ (22,48) | 2182 | 2.666 | d51b416788b6ee70eb0c381c06efc9f1
+ (23,63) | 2294 | 3.001 | 229754d7799160502a143a72f6789927
+ (23,83) | 2314 | 8.831 | db9eeb7e678863649bce209842e0d164
+ (30,18) | 2928 | 0.852 | 9087b0efc7c7acd1ef7e153678809c77
+ (30,19) | 2929 | 5.443 | a36b0dcd1e6384abc0e1867860ad3ee3
+ (30,64) | 2974 | 9.84 | 6788076842014c83cedadbe6b0ba0314
+ (30,85) | 2995 | 9.828 | cb12d7f933e7d102c52231bf62b8a678
+ (32,21) | 3125 | 5.251 | 019f8b946a256d9357eadc5ace2c8678
+ (32,32) | 3136 | 5.17 | 2ecd2bd94734e5dd392d8678bc64cdab
+ (32,58) | 3162 | 8.215 | d60678e8f2ba9c540798ebbde31177e8
+ (32,64) | 3168 | 2.302 | ce393994e8cf430867408678d1a5a9c8
+ (33,14) | 3215 | 5.748 | e2eacaff46787bfeefcaa24cf35264c7
+(25 rows)
+
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ ctid | a | b | c
+--------+-----+-------+----------------------------------
+ (3,1) | 292 | 3.11 | 1700002963a49da13542e0726b7bb758
+ (3,2) | 293 | 4.932 | 53c3bce66e43be4f209556518c2fcb54
+ (3,3) | 294 | 3.851 | 6883966fd8f918a4aa29be29d2c386fb
+ (3,4) | 295 | 9.821 | 49182f81e6a13cf5eaa496d51fea6406
+ (3,5) | 296 | 1.623 | d296c101daa88a51f6ca8cfc1ac79b50
+ (3,6) | 297 | 9.248 | 9fd81843ad7f202f26c1a174c7357585
+ (3,7) | 298 | 8.073 | 26e359e83860db1d11b6acca57d8ea88
+ (3,8) | 299 | 5.224 | ef0d3930a7b6c95bd2b32ed45989c61f
+ (3,9) | 300 | 4.252 | 94f6d7e04a4d452035300f18b984988c
+ (3,10) | 301 | 4.599 | 34ed066df378efacc9b924ec161e7639
+(10 rows)
+
+SELECT ctid,* FROM t1 WHERE '(80,0)'::tid < ctid AND c like '%ab%' LIMIT 25;
+ ctid | a | b | c
+---------+------+-------+----------------------------------
+ (80,4) | 7764 | 7.307 | fdff71fcab656abfbefaabecab1a7f6d
+ (80,10) | 7770 | 6.521 | c802ceaa43e6ad9ddc511cab5f34789c
+ (80,27) | 7787 | 7.681 | 72a8ab4748d4707fda159db0088d85de
+ (80,51) | 7811 | 5.957 | ddd1df443471e3abe89933f20d08116a
+ (80,52) | 7812 | 0.676 | eefc7bfe8fd6e2c8c01aa6ca7b1aab1a
+ (80,85) | 7845 | 0.486 | 0118a063b4aae95277f0bc1752c75abf
+ (80,89) | 7849 | 6.941 | ae3f4c649fb55c2ee3ef4d1abdb79ce5
+ (81,19) | 7876 | 9.923 | 42dab56861d81108ee356d037190c315
+ (81,36) | 7893 | 1.108 | 76fabdc82dd649afd7efa2d6894e568d
+ (81,43) | 7900 | 8.274 | 400c3241004b5db7ca7f5abfef2794f2
+ (81,47) | 7904 | 4.554 | b5d62aa6024ab6a65a12c78c4c2d4efc
+ (81,50) | 7907 | 4.477 | 10b4945abe2e627db646b3c5226a4e50
+ (81,56) | 7913 | 8.261 | 5527eaab87a00dbe1614481ef174f285
+ (81,86) | 7943 | 6.993 | 0163cceb20f5ca7b313419c068abd9dc
+ (81,92) | 7949 | 0.418 | 5fedcaffc4aba6e57a3563b1c7d60c1d
+ (81,95) | 7952 | 1.682 | f316e3fe33f1f754851712c760ab9d48
+ (82,2) | 7956 | 2.71 | 7fd4db88d31ab524e0afe153c4f9465a
+ (82,3) | 7957 | 2.215 | fa636c3d216834a2e0db24cc157ab5f0
+ (82,9) | 7963 | 2.67 | a1d4c20b182ad7137ab3606f0e3fc8a4
+ (82,12) | 7966 | 0.01 | ab00b14a2da2e3cdcc44f06265db6574
+ (82,13) | 7967 | 0.551 | dab1263d1e6a88c9ba5e7e294def5e8b
+ (82,18) | 7972 | 4.943 | b5507f51b88a3ae4a99ba87e4877ab57
+ (82,21) | 7975 | 6.321 | 25f09e44e51b17fb527fba402bfba5ab
+ (82,37) | 7991 | 0.868 | ef7be8c57773f2ab48d013434d3ad4f7
+ (82,47) | 8001 | 0.091 | bc3c4a6331a8a9950945a1aa8c95ab8a
+(25 rows)
+
+SELECT ctid,* FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%' LIMIT 25;
+ ctid | a | b | c
+---------+------+-------+----------------------------------
+ (1,13) | 110 | 1.052 | 5f93f983524def3dca464469d2cf9f3e
+ (1,28) | 125 | 8.49 | 3def184ad8f4755ff269862ea77393dd
+ (1,77) | 174 | 4.08 | bf8229696f7a3bb4700cfddef19fa23f
+ (2,5) | 199 | 1.387 | 84d9ee44e457ddef7f2c4f25dc8fa865
+ (6,14) | 596 | 7.644 | b2eeb7362ef83deff5c7813a67e14f0a
+ (8,3) | 779 | 1.881 | 67d96d458abdef21792e6d8e590244e7
+ (8,68) | 844 | 3.318 | e97ee2054defb209c35fe4dc94599061
+ (8,91) | 867 | 0.073 | ede7e2b6d13a41ddf9f4bdef84fdc737
+ (10,31) | 1001 | 0.921 | b8c37e33defde51cf91e1e03e51657da
+ (11,52) | 1119 | 7.95 | 8597a6cfa74defcbde3047c891d78f90
+ (11,57) | 1124 | 9.97 | c7635bfd99248a2cdef8249ef7bfbef4
+ (12,4) | 1168 | 5.197 | 2f29b6e3abc6ebdefb55456ea6ca5dc8
+ (12,14) | 1178 | 1.548 | 7d771e0e8f3633ab54856925ecdefc5d
+ (13,91) | 1352 | 4.859 | 8b0dc65f996f98fd178a9defd0efa077
+ (14,83) | 1441 | 5.126 | b197ffdef2ddc3308584dce7afa3661b
+ (16,2) | 1554 | 8.012 | 98986c005e5def2da341b4e0627d4712
+ (16,90) | 1642 | 9.65 | 81c650caac28cdefce4de5ddc18befa0
+ (17,5) | 1654 | 0.452 | 9d2682367c3935defcb1f9e247a97c0d
+ (17,77) | 1726 | 8.232 | 74563ba21a90da13dacf2a73e3ddefa7
+ (17,97) | 1746 | 7.861 | 442cde81694ca09a626eeddefd1b74ca
+ (23,62) | 2293 | 8.685 | 5f6371c9126149517d9ba475def53139
+ (25,36) | 2461 | 9.39 | cf05968255451bdefe3c5bc64d550517
+ (26,87) | 2609 | 4.644 | 339a18def9898dd60a634b2ad8fbbd58
+ (26,90) | 2612 | 6.852 | 1175defd049d3301e047ce50d93e9c7a
+ (27,46) | 2665 | 2.777 | e727fa59ddefcefb5d39501167623132
+(25 rows)
+
+SELECT t1.ctid,* FROM t1 NATURAL JOIN t3 WHERE t3.ctid IN (
+ SELECT t3.ctid FROM t2 NATURAL JOIN t3
+ WHERE t2.ctid BETWEEN '(4,0)'::tid AND '(5,0)'::tid)
+ LIMIT 25 OFFSET 50;
+ ctid | a | b | c | x
+---------+-----+-------+----------------------------------+-----
+ (8,44) | 820 | 8.478 | e2a2dcc36a08a345332c751b2f2e476c | 640
+ (1,21) | 118 | 5.258 | 5ef059938ba799aaa845e1c2e8a762bd | 636
+ (8,92) | 868 | 9.159 | dd45045f8c68db9f54e70c67048d32e8 | 660
+ (9,65) | 938 | 9.768 | 74bba22728b6185eec06286af6bec36d | 629
+ (1,87) | 184 | 7.903 | 6cdd60ea0045eb7a6ec44c54d29ed402 | 672
+ (9,88) | 961 | 0.201 | d707329bece455a462b58ce00d1194c9 | 612
+ (0,94) | 94 | 0.365 | f4b9ec30ad9f68f89b29639786cb62ef | 664
+ (7,51) | 730 | 7.128 | d5cfead94f5350c12c322b5b664544c1 | 669
+ (4,52) | 440 | 6.344 | a8abb4bb284b5b27aa7cb790dc20f80b | 683
+ (10,3) | 973 | 2.017 | ca75910166da03ff9d4655a0338e6b09 | 633
+ (4,65) | 453 | 2.254 | 49ae49a23f67c759bf4fc791ba842aa2 | 674
+ (9,48) | 921 | 5.248 | 430c3626b879b4005d41b8a46172e0c0 | 599
+ (5,5) | 490 | 5.695 | c410003ef13d451727aeff9082c29a5c | 655
+ (0,35) | 35 | 8.377 | 1c383cd30b7c298ab50293adfecb7b18 | 676
+ (4,37) | 425 | 5.531 | 25b2822c2f5a3230abfadd476e8b04c9 | 611
+ (10,12) | 982 | 1.32 | fec8d47d412bcbeece3d9128ae855a7a | 682
+ (9,83) | 956 | 3.577 | 168908dd3227b8358eababa07fcaf091 | 612
+ (9,66) | 939 | 7.47 | 3df1d4b96d8976ff5986393e8767f5b2 | 617
+ (8,53) | 829 | 0.186 | ce78d1da254c0843eb23951ae077ff5f | 658
+ (7,27) | 706 | 0.11 | 9c82c7143c102b71c593d98d96093fde | 609
+ (8,63) | 839 | 3.211 | 8f7d807e1f53eff5f9efbe5cb81090fb | 678
+ (8,33) | 809 | 6.587 | 32b30a250abd6331e03a2a1f16466346 | 622
+ (8,1) | 777 | 2.071 | f1c1592588411002af340cbaedd6fc33 | 616
+ (1,92) | 189 | 3.371 | a2557a7b2e94197ff767970b67041697 | 660
+ (4,45) | 433 | 2.954 | 019d385eb67632a7e958e23f24bd07d7 | 625
+(25 rows)
+
+-- Misc Queries
+SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50;
+ ctid | a | b | c
+----------+-------+-------+----------------------------------
+ (412,36) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0
+ (412,35) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3
+ (412,34) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1
+ (412,33) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1
+ (412,32) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9
+ (412,31) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0
+ (412,30) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73
+ (412,29) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918
+ (412,28) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7
+ (412,27) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837
+ (412,26) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5
+ (412,25) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1
+ (412,24) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f
+ (412,23) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e
+ (412,22) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452
+ (412,21) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d
+ (412,20) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d
+ (412,19) | 39983 | 1.165 | c8f2e54fe7b8ab5c291ea8d5831669df
+ (412,18) | 39982 | 0.123 | 54996ced8ec545754b9c7404027969d8
+ (412,17) | 39981 | 2.212 | 5e26badb3867ac7f26d3624ca39a9df4
+ (412,16) | 39980 | 5.832 | 906fec3cca7ccc130fa2b1844aa10126
+ (412,15) | 39979 | 4.755 | 34c5b339ea5917d60a26a3d1ef3a8fb2
+ (412,14) | 39978 | 9.307 | 497290bed604efe673ff973099876689
+ (412,13) | 39977 | 8.515 | 2a8b437cac4fd7012194170f76b385fe
+ (412,12) | 39976 | 7.702 | 589e19fafa037ef3e798363d7f9bd6d3
+ (412,11) | 39975 | 1.391 | b4803e027e986edc9bb95df3e2a0b525
+ (412,10) | 39974 | 3.145 | 1dc43216fffa0191d44329797ac898c0
+ (412,9) | 39973 | 3.922 | 7a1fd501b45f517e975995e1ef9b956c
+ (412,8) | 39972 | 9.996 | ae0752582ef51ba1b182f3d7f7cd9751
+ (412,7) | 39971 | 3.518 | b0c2187f8453302e766a91b72f65a6cf
+ (412,6) | 39970 | 5.561 | 12b25b878c7297605a57d9a104de9976
+ (412,5) | 39969 | 9.684 | e0b6c164920a5a463aa98867f13038d4
+ (412,4) | 39968 | 3.852 | f36a0f70291c903ae5664927b016c488
+ (412,3) | 39967 | 7.527 | 385d960968e481fe04be1a04f429110d
+ (412,2) | 39966 | 7.57 | f339e1789582cc3bbe44b27ef01b3617
+ (412,1) | 39965 | 4.137 | b404579a33d6c50199eefb174df7b02a
+ (411,97) | 39964 | 2.785 | 6e4fd1f8d7082b92ba9842ab88aab57e
+ (411,96) | 39963 | 4.053 | 21ee021ac65ce078bfd68b48368dc6a8
+ (411,95) | 39962 | 4.783 | 76e91437c7f841334f01db0c85a8ec00
+ (411,94) | 39961 | 2.377 | f80ca2ec372a23b59dc6a5bfee18bfac
+ (411,93) | 39960 | 3.522 | 744a37185fa8c4449a60bd11976f9045
+ (411,92) | 39959 | 8.512 | 2d968cb99817ea5e49f4bcb70f7f154a
+ (411,91) | 39958 | 7.262 | b3f7bb40292f61fa966cb2a8a4cd339d
+ (411,90) | 39957 | 3.722 | eef43b55de33c2d530ea4a9669b81062
+ (411,89) | 39956 | 3.402 | f5fee8f7da74f4887f5bcae2bafb6dd6
+ (411,88) | 39955 | 5.841 | bc4d9b0e9bdbd3186592452785c479cc
+ (411,87) | 39954 | 6.971 | 23a28e6d57e4c5d8eb0bff70ae01ed09
+ (411,86) | 39953 | 8.96 | e954fd23871b5505e296fe8fddf78623
+ (411,85) | 39952 | 5.334 | 7ea9f068a460ee6c285e7ca7af850c51
+ (411,84) | 39951 | 5.368 | a08758e937069b10802ab9331cd70273
+(50 rows)
+
+EXPLAIN (verbose) UPDATE t1 SET c = c || '_may_last_page'
+ WHERE ctid >= (
+ SELECT '(' || relpages - 1 || ',20)' FROM pg_class
+ WHERE relname = 't1' AND
+ relnamespace = (
+ SELECT oid FROM pg_namespace
+ WHERE nspname = 'regtest_custom_scan'))::tid
+ RETURNING ctid,*;
+ QUERY PLAN
+--------------------------------------------------------------------------------------------------------------
+ Update on regtest_custom_scan.t1 (cost=10.38..601.56 rows=13333 width=51)
+ Output: t1.ctid, t1.a, t1.b, t1.c
+ InitPlan 2 (returns $1)
+ -> Index Scan using pg_class_relname_nsp_index on pg_catalog.pg_class (cost=1.35..9.38 rows=1 width=4)
+ Output: (('('::text || ((pg_class.relpages - 1))::text) || ',20)'::text)
+ Index Cond: ((pg_class.relname = 't1'::name) AND (pg_class.relnamespace = $0))
+ InitPlan 1 (returns $0)
+ -> Seq Scan on pg_catalog.pg_namespace (cost=0.00..1.07 rows=1 width=4)
+ Output: pg_namespace.oid
+ Filter: (pg_namespace.nspname = 'regtest_custom_scan'::name)
+ -> Custom Scan (ctidscan) on regtest_custom_scan.t1 (cost=1.00..592.18 rows=13333 width=51)
+ Output: t1.a, t1.b, (t1.c || '_may_last_page'::text), t1.ctid
+ TID Range: (t1.ctid >= ($1)::tid)
+(13 rows)
+
+--set enable_ctidscan = off;
+UPDATE t1 SET c = c || '_may_last_page'
+ WHERE ctid >= (
+ SELECT '(' || relpages - 1 || ',20)' FROM pg_class
+ WHERE relname = 't1' AND
+ relnamespace = (
+ SELECT oid FROM pg_namespace
+ WHERE nspname = 'regtest_custom_scan'))::tid
+ RETURNING ctid,*;
+ ctid | a | b | c
+----------+-------+-------+------------------------------------------------
+ (412,37) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d_may_last_page
+ (412,38) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d_may_last_page
+ (412,39) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452_may_last_page
+ (412,40) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e_may_last_page
+ (412,41) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f_may_last_page
+ (412,42) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1_may_last_page
+ (412,43) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5_may_last_page
+ (412,44) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837_may_last_page
+ (412,45) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7_may_last_page
+ (412,46) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918_may_last_page
+ (412,47) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73_may_last_page
+ (412,48) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0_may_last_page
+ (412,49) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9_may_last_page
+ (412,50) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1_may_last_page
+ (412,51) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1_may_last_page
+ (412,52) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3_may_last_page
+ (412,53) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0_may_last_page
+(17 rows)
+
+SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50;
+ ctid | a | b | c
+----------+-------+-------+------------------------------------------------
+ (412,53) | 40000 | 5.642 | 7c77f048a2d02e784926184a82686fa0_may_last_page
+ (412,52) | 39999 | 5.855 | 6d7364731230122df155fddea6878dd3_may_last_page
+ (412,51) | 39998 | 6.955 | 1969f0d80e750feb485671caaa4c59d1_may_last_page
+ (412,50) | 39997 | 5.958 | 26e31ab36807914055cf505c63c05bd1_may_last_page
+ (412,49) | 39996 | 2.004 | 16ecd261ac5088aee91078bf5225abd9_may_last_page
+ (412,48) | 39995 | 9.428 | 3724f1b309d02b7f475f69eba8107ae0_may_last_page
+ (412,47) | 39994 | 8.388 | 95831099d5d2171aea50c24de5332f73_may_last_page
+ (412,46) | 39993 | 7.867 | a463cac2327534f6f02563ffbdf92918_may_last_page
+ (412,45) | 39992 | 6.644 | ffacbb7db90628bfcc8be667616dfcc7_may_last_page
+ (412,44) | 39991 | 4.335 | 62e81b7815b24e46b69fcfa197aea837_may_last_page
+ (412,43) | 39990 | 3.085 | df7c6cbfde52a0ccf19c3a82487c3ca5_may_last_page
+ (412,42) | 39989 | 4.267 | 4325d1772bbb08248572e96f643a8de1_may_last_page
+ (412,41) | 39988 | 0.814 | 7b3f65a67546eca7a9249e1310a6be4f_may_last_page
+ (412,40) | 39987 | 4.573 | 4a2ad15a73d498efa82cc2893a52d08e_may_last_page
+ (412,39) | 39986 | 7.006 | 4144092976e61a9c32e1c7b205d85452_may_last_page
+ (412,38) | 39985 | 7.093 | 797ed5077436dc8abaec64750e2c3d3d_may_last_page
+ (412,37) | 39984 | 1.172 | e42a68e0a57044cc230ac7c901756c1d_may_last_page
+ (412,19) | 39983 | 1.165 | c8f2e54fe7b8ab5c291ea8d5831669df
+ (412,18) | 39982 | 0.123 | 54996ced8ec545754b9c7404027969d8
+ (412,17) | 39981 | 2.212 | 5e26badb3867ac7f26d3624ca39a9df4
+ (412,16) | 39980 | 5.832 | 906fec3cca7ccc130fa2b1844aa10126
+ (412,15) | 39979 | 4.755 | 34c5b339ea5917d60a26a3d1ef3a8fb2
+ (412,14) | 39978 | 9.307 | 497290bed604efe673ff973099876689
+ (412,13) | 39977 | 8.515 | 2a8b437cac4fd7012194170f76b385fe
+ (412,12) | 39976 | 7.702 | 589e19fafa037ef3e798363d7f9bd6d3
+ (412,11) | 39975 | 1.391 | b4803e027e986edc9bb95df3e2a0b525
+ (412,10) | 39974 | 3.145 | 1dc43216fffa0191d44329797ac898c0
+ (412,9) | 39973 | 3.922 | 7a1fd501b45f517e975995e1ef9b956c
+ (412,8) | 39972 | 9.996 | ae0752582ef51ba1b182f3d7f7cd9751
+ (412,7) | 39971 | 3.518 | b0c2187f8453302e766a91b72f65a6cf
+ (412,6) | 39970 | 5.561 | 12b25b878c7297605a57d9a104de9976
+ (412,5) | 39969 | 9.684 | e0b6c164920a5a463aa98867f13038d4
+ (412,4) | 39968 | 3.852 | f36a0f70291c903ae5664927b016c488
+ (412,3) | 39967 | 7.527 | 385d960968e481fe04be1a04f429110d
+ (412,2) | 39966 | 7.57 | f339e1789582cc3bbe44b27ef01b3617
+ (412,1) | 39965 | 4.137 | b404579a33d6c50199eefb174df7b02a
+ (411,97) | 39964 | 2.785 | 6e4fd1f8d7082b92ba9842ab88aab57e
+ (411,96) | 39963 | 4.053 | 21ee021ac65ce078bfd68b48368dc6a8
+ (411,95) | 39962 | 4.783 | 76e91437c7f841334f01db0c85a8ec00
+ (411,94) | 39961 | 2.377 | f80ca2ec372a23b59dc6a5bfee18bfac
+ (411,93) | 39960 | 3.522 | 744a37185fa8c4449a60bd11976f9045
+ (411,92) | 39959 | 8.512 | 2d968cb99817ea5e49f4bcb70f7f154a
+ (411,91) | 39958 | 7.262 | b3f7bb40292f61fa966cb2a8a4cd339d
+ (411,90) | 39957 | 3.722 | eef43b55de33c2d530ea4a9669b81062
+ (411,89) | 39956 | 3.402 | f5fee8f7da74f4887f5bcae2bafb6dd6
+ (411,88) | 39955 | 5.841 | bc4d9b0e9bdbd3186592452785c479cc
+ (411,87) | 39954 | 6.971 | 23a28e6d57e4c5d8eb0bff70ae01ed09
+ (411,86) | 39953 | 8.96 | e954fd23871b5505e296fe8fddf78623
+ (411,85) | 39952 | 5.334 | 7ea9f068a460ee6c285e7ca7af850c51
+ (411,84) | 39951 | 5.368 | a08758e937069b10802ab9331cd70273
+(50 rows)
+
+-- PREPARE'd statement
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+ WHERE c like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+ QUERY PLAN
+-------------------------------------------------------------------
+ Custom Scan (ctidscan) on t1
+ Filter: (c ~~ '%abc%'::text)
+ TID Range: ((ctid >= '(5,0)'::tid) AND (ctid <= '(10,0)'::tid))
+(3 rows)
+
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+ QUERY PLAN
+-----------------------------------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((c ~~ '%abc%'::text) AND (ctid >= '(10,0)'::tid) AND (ctid <= '(5,0)'::tid))
+(2 rows)
+
+-- Also, EXPLAIN with none-text format
+EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid);
+ QUERY PLAN
+----------------------------------------------------------------------------------------
+ <explain xmlns="http://www.postgresql.org/2009/explain"> +
+ <Query> +
+ <Plan> +
+ <Node-Type>Custom Scan</Node-Type> +
+ <Custom-Plan-Provider>ctidscan</Custom-Plan-Provider> +
+ <Relation-Name>t1</Relation-Name> +
+ <Alias>t1</Alias> +
+ <Filter>(c ~~ '%abc%'::text)</Filter> +
+ <TID-Range>((ctid >= '(0,0)'::tid) AND (ctid <= '(5,0)'::tid))</TID-Range>+
+ </Plan> +
+ </Query> +
+ </explain>
+(1 row)
+
+-- Turn off the feature
+SET enable_ctidscan = off;
+EXPLAIN (costs off)
+SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+ QUERY PLAN
+------------------------------------------------------------------
+ Seq Scan on t1
+ Filter: ((ctid >= '(2,115)'::tid) AND (ctid <= '(3,10)'::tid))
+(2 rows)
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
+NOTICE: drop cascades to 3 other objects
+DETAIL: drop cascades to table t1
+drop cascades to table t2
+drop cascades to table t3
diff --git a/src/test/modules/ctidscan/sql/ctidscan.sql b/src/test/modules/ctidscan/sql/ctidscan.sql
new file mode 100644
index 0000000..231768e
--- /dev/null
+++ b/src/test/modules/ctidscan/sql/ctidscan.sql
@@ -0,0 +1,132 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+SET SEED TO 0.20140702;
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+ a int primary key,
+ b float,
+ c text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*10000.0) / 1000.0, md5(i::text) FROM generate_series(1,40000) i);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+ x int primary key,
+ y float,
+ z text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*10000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,40200) i);
+VACUUM ANALYZE t2;
+
+CREATE TABLE t3 (
+ a int references t1(a),
+ x int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+
+RESET client_min_messages;
+
+--
+-- Check Plans if no special extensions are loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE '(8,0)'::tid < ctid AND c like '%ab%';
+EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%';
+EXPLAIN (costs off)
+ SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+EXPLAIN (costs off, verbose)
+ SELECT count(*), ceil(b)
+ FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+ GROUP BY ceil(b);
+
+--
+-- Plan for same query but ctidscan was loaded
+--
+LOAD '$libdir/ctidscan';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40 AND ctid < '(6,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE c like '%789%' AND ctid < '(5,0)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE '(8,0)'::tid < ctid AND c like '%ab%';
+EXPLAIN (costs on) SELECT * FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%'; -- SeqScan is cheaper
+EXPLAIN (costs off)
+ SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8,0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+EXPLAIN (costs off, verbose)
+ SELECT count(*), ceil(b)
+ FROM t1 WHERE ctid BETWEEN '(3,0)'::tid AND '(10,0)'::tid
+ GROUP BY ceil(b);
+
+--
+-- Run the query without EXPLAIN
+--
+SELECT ctid,* FROM t1 WHERE ctid <= '(1,20)'::tid ORDER BY ctid DESC LIMIT 25;
+SELECT count(*) FROM t1 WHERE ctid > '(200,0)'::tid;
+SELECT ctid,* FROM t1 WHERE c like '%678%' AND ctid >= '(3,50)'::tid LIMIT 25;
+SELECT ctid,* FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+SELECT ctid,* FROM t1 WHERE '(80,0)'::tid < ctid AND c like '%ab%' LIMIT 25;
+SELECT ctid,* FROM t1 WHERE '(9999,99)'::tid > ctid AND c like '%def%' LIMIT 25;
+SELECT t1.ctid,* FROM t1 NATURAL JOIN t3 WHERE t3.ctid IN (
+ SELECT t3.ctid FROM t2 NATURAL JOIN t3
+ WHERE t2.ctid BETWEEN '(4,0)'::tid AND '(5,0)'::tid)
+ LIMIT 25 OFFSET 50;
+
+-- Misc Queries
+SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50;
+
+EXPLAIN (verbose) UPDATE t1 SET c = c || '_may_last_page'
+ WHERE ctid >= (
+ SELECT '(' || relpages - 1 || ',20)' FROM pg_class
+ WHERE relname = 't1' AND
+ relnamespace = (
+ SELECT oid FROM pg_namespace
+ WHERE nspname = 'regtest_custom_scan'))::tid
+ RETURNING ctid,*;
+--set enable_ctidscan = off;
+UPDATE t1 SET c = c || '_may_last_page'
+ WHERE ctid >= (
+ SELECT '(' || relpages - 1 || ',20)' FROM pg_class
+ WHERE relname = 't1' AND
+ relnamespace = (
+ SELECT oid FROM pg_namespace
+ WHERE nspname = 'regtest_custom_scan'))::tid
+ RETURNING ctid,*;
+
+SELECT ctid,* FROM t1 ORDER BY ctid DESC LIMIT 50;
+
+-- PREPARE'd statement
+PREPARE p1(tid, tid) AS SELECT ctid,* FROM t1
+ WHERE c like '%abc%' AND ctid BETWEEN $1 AND $2;
+EXPLAIN (costs off) EXECUTE p1('(5,0)'::tid, '(10,0)'::tid);
+EXPLAIN (costs off) EXECUTE p1('(10,0)'::tid, '(5,0)'::tid);
+
+-- Also, EXPLAIN with none-text format
+EXPLAIN (costs off, format xml) EXECUTE p1('(0,0)'::tid, '(5,0)'::tid);
+
+-- Turn off the feature
+SET enable_ctidscan = off;
+
+EXPLAIN (costs off)
+SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
diff --git a/src/test/regress/expected/custom_scan.out b/src/test/regress/expected/custom_scan.out
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/regress/sql/custom_scan.sql b/src/test/regress/sql/custom_scan.sql
new file mode 100644
index 0000000..683154b
--- /dev/null
+++ b/src/test/regress/sql/custom_scan.sql
@@ -0,0 +1,53 @@
+--
+-- Regression Tests for CustomScan Interface with CtidScan Provider
+--
+
+-- construction of test data
+SET client_min_messages TO 'warning';
+
+SET SEED 0.20140702;
+
+CREATE SCHEMA regtest_custom_scan;
+
+SET search_path TO regtest_custom_scan, public;
+
+CREATE TABLE t1 (
+ a int primary key,
+ b float,
+ c text
+);
+INSERT INTO t1 (SELECT i, ceil(random()*1000.0) / 1000.0, md5(i::text) FROM generate_series(1,1000) i);
+VACUUM ANALYZE t1;
+
+CREATE TABLE t2 (
+ x int primary key,
+ y float,
+ z text
+);
+INSERT INTO t2 (SELECT i, ceil(random()*1000.0) / 1000.0, md5((-i)::text) FROM generate_series(201,1200) i);
+VACUUM ANALYZE t2;
+
+CREATE TABLE t3 (
+ a int references t1(a),
+ x int references t2(x)
+);
+INSERT INTO t3 (SELECT ceil(random() * 1000), ceil(random() * 1000) + 200 FROM generate_series(1,8000) i);
+
+RESET client_min_messages;
+--
+-- Check Plans if no special extensions are loaded.
+--
+EXPLAIN (costs off) SELECT * FROM t1 WHERE a = 40;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE b like '%789%';
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid = '(2,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1 WHERE ctid BETWEEN '(2,115)'::tid AND '(3,10)'::tid;
+EXPLAIN (costs off) SELECT * FROM t1, t2, t3
+ WHERE t1.a = t3.a AND t2.x = t3.x
+ AND t1.ctid BETWEEN '(3,10)'::tid AND '(10,9999)'::tid
+ AND t2.ctid BETWEEN '(4,9999)'::tid AND '(8.0)'::tid
+ AND t3.ctid BETWEEN '(2,0)'::tid AND '(5,0)'::tid;
+
+
+
+-- Test Cleanup
+DROP SCHEMA regtest_custom_scan CASCADE;
On Tue, Jan 6, 2015 at 10:51 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:
Jim, Thanks for your reviewing the patch.
The attached patch is revised one according to your suggestion,
and also includes bug fix I could found.* Definitions of TIDXXXXOperator was moved to pg_operator.h
as other operator doing.
* Support the case of "ctid (operator) Param" expression.
* Add checks if commutator of TID was not found.
* Qualifiers gets extracted on plan stage, not path stage.
* Adjust cost estimation logic to fit SeqScan manner.
* Add some new test cases for regression test.On 12/24/14, 7:54 PM, Kouhei Kaigai wrote:
On Mon, Dec 15, 2014 at 4:55 PM, Kouhei Kaigai
<kaigai@ak.jp.nec.com>wrote:
I'm not certain whether we should have this functionality in
contrib from the perspective of workload that can help, but its
major worth is for an example of custom-scan interface.worker_spi is now in src/test/modules. We may add it there as well,
no?
Hmm, it makes sense for me. Does it also make sense to add a
test-case to the core regression test cases?The attached patch adds ctidscan module at test/module instead of
contrib.
Basic portion is not changed from the previous post, but file
locations and test cases in regression test are changed.First, I'm glad for this. It will be VERY valuable for anyone trying to
clean up the end of a majorly bloated table.Here's a partial review...
+++ b/src/test/modules/ctidscan/ctidscan.c+/* missing declaration in pg_proc.h */ +#ifndef TIDGreaterOperator +#define TIDGreaterOperator 2800 ... If we're calling that out here, should we have a corresponding comment in pg_proc.h, in case these ever get renumbered?It was a quick hack when I moved this module out of the tree.
Yep, I should revert when I submit this patch again.+CTidQualFromExpr(Node *expr, int varno) ... + if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + if (exprType(other) != TIDOID) + return NULL; /* should not happen */ + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL;I think this needs some additional blank lines...
OK. And, I also noticed the coding style around this function is
different from other built-in plans, so I redefined the role of
this function just to check whether the supplied RestrictInfo is
OpExpr that involves TID inequality operator, or not.Expression node shall be extracted when Plan node is created
from Path node.+ if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + + if (exprType(other) != TIDOID) + return NULL; /* should not happen */ + + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL;+ * 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.<grammar>That should read "same as what SeqScan is doing"... however,
what
actual function are you talking about? I couldn't find
SeqScanEstimateCosts
(or anything ending EstimateCosts).
It is cost_seqscan(). But I don't put a raw function name in the source
code comments in other portion, because nobody can guarantee it is up to
date in the future...BTW, there's other grammar issues but it'd be best to handle those all at
once after all the code stuff is done.Yep. Help by native English speaker is very helpful for us.
+ opno = get_commutator(op->opno);
What happens if there's no commutator? Perhaps best to Assert(opno !=
InvalidOid) at the end of that if block.Usually, commutator operator of TID is defined on initdb time, however,
nobody can guarantee a mad superuser doesn't drop it.
So, I added elog(ERROR,...) here.Though, it seems things will fall apart anyway if ctid_quals isn't
exactly
what we're expecting; I don't know if that's OK or not.
No worry, it was already checked on planning stage.
+ /* disk costs --- assume each tuple on a different page */ + run_cost += spc_random_page_cost * ntuples; Isn't that extremely pessimistic?Probably. I follow the manner of SeqScan.
I'm not familiar enough with the custom-scan stuff to really comment past
this point, and I could certainly be missing some details about planning
and execution.I do have some concerns about the regression test, but perhaps I'm just
being paranoid:- The EXPLAIN tests don't cover >. They do cover <= and >= via BETWEEN.
- Nothing tests the case of '...'::tid op ctid; only lvalue cases aretested.
Both are added.
Also, it seems that we don't handle joining on a ctid qual... is that
intentional?Yep. This module does not intend to handle joining, because custom-/
foreign-join interface is still under the discussion.
https://commitfest.postgresql.org/action/patch_view?id=1653Hanada-san makes an enhancement of postgres_fdw on this enhancement.
https://commitfest.postgresql.org/action/patch_view?id=1674I know that sounds silly, but you'd probably want to do that
if you're trying to move tuples off the end of a bloated table. You could
work around it by constructing a dynamic SQL string, but it'd be easier
to do something like:UPDATE table1 SET ...
WHERE ctid >= (SELECT '(' || relpages || ',0)' FROM pg_class WHERE oid
= 'table1'::regclass) ;This example noticed me that the previous version didn't support the case
of "ctid (operator) Param".
So, I enhanced to support above case, and update the regression test also.
Moved this patch to next CF 2015-02 because of lack of review(ers).
--
Michael
Folks,
Moved this patch to next CF 2015-02 because of lack of review(ers).
Do we still need this patch as contrib module?
It was originally required it as example of custom-scan interface last
summer, however, here was no strong requirement after that, then, it
was bumped to v9.6 development cycle.
If somebody still needs it, I'll rebase and adjust the patch towards
the latest custom-scan interface. However, I cannot be motivated for
the feature nobody wants.
Thanks,
--
NEC Business Creation Division / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
-----Original Message-----
From: pgsql-hackers-owner@postgresql.org
[mailto:pgsql-hackers-owner@postgresql.org] On Behalf Of Michael Paquier
Sent: Friday, February 13, 2015 4:41 PM
To: Kaigai Kouhei(海外 浩平)
Cc: Jim Nasby; Robert Haas; Simon Riggs; Kohei KaiGai; PgHacker
Subject: Re: ctidscan as an example of custom-scan (Re: [HACKERS] [v9.5] Custom
Plan API)On Tue, Jan 6, 2015 at 10:51 PM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:
Jim, Thanks for your reviewing the patch.
The attached patch is revised one according to your suggestion,
and also includes bug fix I could found.* Definitions of TIDXXXXOperator was moved to pg_operator.h
as other operator doing.
* Support the case of "ctid (operator) Param" expression.
* Add checks if commutator of TID was not found.
* Qualifiers gets extracted on plan stage, not path stage.
* Adjust cost estimation logic to fit SeqScan manner.
* Add some new test cases for regression test.On 12/24/14, 7:54 PM, Kouhei Kaigai wrote:
On Mon, Dec 15, 2014 at 4:55 PM, Kouhei Kaigai
<kaigai@ak.jp.nec.com>wrote:
I'm not certain whether we should have this functionality in
contrib from the perspective of workload that can help, but its
major worth is for an example of custom-scan interface.worker_spi is now in src/test/modules. We may add it there as well,
no?
Hmm, it makes sense for me. Does it also make sense to add a
test-case to the core regression test cases?The attached patch adds ctidscan module at test/module instead of
contrib.
Basic portion is not changed from the previous post, but file
locations and test cases in regression test are changed.First, I'm glad for this. It will be VERY valuable for anyone trying
to
clean up the end of a majorly bloated table.
Here's a partial review...
+++ b/src/test/modules/ctidscan/ctidscan.c+/* missing declaration in pg_proc.h */ +#ifndef TIDGreaterOperator +#define TIDGreaterOperator 2800 ... If we're calling that out here, should we have a corresponding commentin
pg_proc.h, in case these ever get renumbered?
It was a quick hack when I moved this module out of the tree.
Yep, I should revert when I submit this patch again.+CTidQualFromExpr(Node *expr, int varno) ... + if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + if (exprType(other) != TIDOID) + return NULL; /* should not happen */ + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL;I think this needs some additional blank lines...
OK. And, I also noticed the coding style around this function is
different from other built-in plans, so I redefined the role of
this function just to check whether the supplied RestrictInfo is
OpExpr that involves TID inequality operator, or not.Expression node shall be extracted when Plan node is created
from Path node.+ if (IsCTIDVar(arg1, varno)) + other = arg2; + else if (IsCTIDVar(arg2, varno)) + other = arg1; + else + return NULL; + + if (exprType(other) != TIDOID) + return NULL; /* should not happen */ + + /* The other argument must be a pseudoconstant */ + if (!is_pseudo_constant_clause(other)) + return NULL;+ * CTidEstimateCosts + * + * It estimates cost to scan the target relation according to the given + * restriction clauses. Its logic to scan relations are almost sameas
+ * SeqScan doing, because it uses regular heap_getnext(), except for + * the number of tuples to be scanned if restriction clauses work well.<grammar>That should read "same as what SeqScan is doing"... however,
what
actual function are you talking about? I couldn't find
SeqScanEstimateCosts
(or anything ending EstimateCosts).
It is cost_seqscan(). But I don't put a raw function name in the source
code comments in other portion, because nobody can guarantee it is up
to
date in the future...BTW, there's other grammar issues but it'd be best to handle those all
at
once after all the code stuff is done.
Yep. Help by native English speaker is very helpful for us.
+ opno = get_commutator(op->opno);
What happens if there's no commutator? Perhaps best to Assert(opno !=
InvalidOid) at the end of that if block.Usually, commutator operator of TID is defined on initdb time, however,
nobody can guarantee a mad superuser doesn't drop it.
So, I added elog(ERROR,...) here.Though, it seems things will fall apart anyway if ctid_quals isn't
exactly
what we're expecting; I don't know if that's OK or not.
No worry, it was already checked on planning stage.
+ /* disk costs --- assume each tuple on a different page */ + run_cost += spc_random_page_cost * ntuples; Isn't that extremely pessimistic?Probably. I follow the manner of SeqScan.
I'm not familiar enough with the custom-scan stuff to really comment
past
this point, and I could certainly be missing some details about planning
and execution.I do have some concerns about the regression test, but perhaps I'm just
being paranoid:- The EXPLAIN tests don't cover >. They do cover <= and >= via BETWEEN.
- Nothing tests the case of '...'::tid op ctid; only lvalue cases aretested.
Both are added.
Also, it seems that we don't handle joining on a ctid qual... is that
intentional?Yep. This module does not intend to handle joining, because custom-/
foreign-join interface is still under the discussion.
https://commitfest.postgresql.org/action/patch_view?id=1653Hanada-san makes an enhancement of postgres_fdw on this enhancement.
https://commitfest.postgresql.org/action/patch_view?id=1674I know that sounds silly, but you'd probably want to do that
if you're trying to move tuples off the end of a bloated table. Youcould
work around it by constructing a dynamic SQL string, but it'd be easier
to do something like:UPDATE table1 SET ...
WHERE ctid >= (SELECT '(' || relpages || ',0)' FROM pg_class WHEREoid
= 'table1'::regclass) ;
This example noticed me that the previous version didn't support the case
of "ctid (operator) Param".
So, I enhanced to support above case, and update the regression test also.Moved this patch to next CF 2015-02 because of lack of review(ers).
--Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 07/02/2015 08:21 AM, Kouhei Kaigai wrote:
Folks,
Moved this patch to next CF 2015-02 because of lack of review(ers).
Do we still need this patch as contrib module?
It was originally required it as example of custom-scan interface last
summer, however, here was no strong requirement after that, then, it
was bumped to v9.6 development cycle.If somebody still needs it, I'll rebase and adjust the patch towards
the latest custom-scan interface. However, I cannot be motivated for
the feature nobody wants.
Robert, can you weigh in on this? Do we currently have anything in the
tree that tests the Custom Scan interface? If not, would this be helpful
for that purpose?
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Jul 10, 2015 at 8:55 AM, Heikki Linnakangas <hlinnaka@iki.fi> wrote:
If somebody still needs it, I'll rebase and adjust the patch towards
the latest custom-scan interface. However, I cannot be motivated for
the feature nobody wants.Robert, can you weigh in on this? Do we currently have anything in the
tree that tests the Custom Scan interface? If not, would this be helpful
for that purpose?
We don't have anything that currently tests the Custom Scan interface
in the tree. The question is how important that is, and whether it's
worth having what's basically a toy implementation just to demonstrate
that the feature can work. If so, I think ctidscan is as good a toy
example as any; in the interest of full disclosure, I was the one who
suggested it in the first place. But I am not entirely sure it's a
good idea to saddle ourselves with that maintenance effort. It would
be a lot more interesting if we had an example that figured to be
generally useful.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas wrote:
We don't have anything that currently tests the Custom Scan interface
in the tree. The question is how important that is, and whether it's
worth having what's basically a toy implementation just to demonstrate
that the feature can work. If so, I think ctidscan is as good a toy
example as any; in the interest of full disclosure, I was the one who
suggested it in the first place. But I am not entirely sure it's a
good idea to saddle ourselves with that maintenance effort. It would
be a lot more interesting if we had an example that figured to be
generally useful.
As a general principle, I think it's a good idea to have a module that's
mostly just a skeleton that guides people into writing something real to
use whatever API is being tested. It needs to be simple enough that not
much need to be deleted when writing the real thing, and complex enough
to cover the parts that need covering. If whatever replaces ctidscan is
too complex, it will not serve that purpose.
My guess is that something whose only purpose is to test the custom scan
interface for coverage purposes can be simpler than this module.
--
�lvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 14, 2015 at 3:07 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
We don't have anything that currently tests the Custom Scan interface
in the tree. The question is how important that is, and whether it's
worth having what's basically a toy implementation just to demonstrate
that the feature can work. If so, I think ctidscan is as good a toy
example as any; in the interest of full disclosure, I was the one who
suggested it in the first place. But I am not entirely sure it's a
good idea to saddle ourselves with that maintenance effort. It would
be a lot more interesting if we had an example that figured to be
generally useful.As a general principle, I think it's a good idea to have a module that's
mostly just a skeleton that guides people into writing something real to
use whatever API is being tested. It needs to be simple enough that not
much need to be deleted when writing the real thing, and complex enough
to cover the parts that need covering. If whatever replaces ctidscan is
too complex, it will not serve that purpose.My guess is that something whose only purpose is to test the custom scan
interface for coverage purposes can be simpler than this module.
See, I actually think the opposite: I think we've been accumulating a
reasonable amount of test code that actually serves no really useful
purpose and is just cruft. Stuff like test_shm_mq and test_decoding
seem like they actually catches bugs, so I like that, but I think
stuff like worker_spi is actually TOO simple to be useful in building
anything real, and it provides no useful test coverage, either. But
this is all a matter of opinion, of course, and I'll defer to whatever
the consensus is.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Jul 14, 2015 at 3:07 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:As a general principle, I think it's a good idea to have a module that's
mostly just a skeleton that guides people into writing something real to
use whatever API is being tested. It needs to be simple enough that not
much need to be deleted when writing the real thing, and complex enough
to cover the parts that need covering. If whatever replaces ctidscan is
too complex, it will not serve that purpose.My guess is that something whose only purpose is to test the custom scan
interface for coverage purposes can be simpler than this module.
See, I actually think the opposite: I think we've been accumulating a
reasonable amount of test code that actually serves no really useful
purpose and is just cruft. Stuff like test_shm_mq and test_decoding
seem like they actually catches bugs, so I like that, but I think
stuff like worker_spi is actually TOO simple to be useful in building
anything real, and it provides no useful test coverage, either. But
this is all a matter of opinion, of course, and I'll defer to whatever
the consensus is.
I think this ties into my core unhappiness with the customscan stuff,
which is that I don't believe it's *possible* to do anything of very
great interest with it. I think anything really useful will require
core code modifications and/or hooks that don't exist now. So a finger
exercise like ctidscan, even though it might have some marginal use,
doesn't do much to alleviate that concern. It certainly doesn't seem
like it's a suitable placeholder proving that we aren't breaking any
actual use cases for the feature.
(BTW, if we care about the use cases this has, such as data recovery from
partially-corrupt tables, it would make way more sense and take way less
net new code to just teach TidScan about it.)
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 14, 2015 at 4:47 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think this ties into my core unhappiness with the customscan stuff,
which is that I don't believe it's *possible* to do anything of very
great interest with it. I think anything really useful will require
core code modifications and/or hooks that don't exist now. So a finger
exercise like ctidscan, even though it might have some marginal use,
doesn't do much to alleviate that concern. It certainly doesn't seem
like it's a suitable placeholder proving that we aren't breaking any
actual use cases for the feature.
Both you and Andres have articulated the concern that CustomScan isn't
actually useful, but I still don't really understand why not. KaiGai
has got working code (under a GPL license) that shows that it can be
used for what he calls GpuScan and GpuJoin, and the speedups are
actually pretty cool if you happen to have the right sort of query to
take advantage of it. That code may be buggy and the license
precludes us using it anyway, but FWICT it does seem to work. I'd be
entirely amenable to improving the infrastructure such that it could
be used for a broader array of purposes, and I'm also amenable to
adding more hooks if we need more hooks to make it useful, but I'm not
clear at all on what you think is missing.
I'm curious, for example, whether CustomScan would have been
sufficient to build TABLESAMPLE, and if not, why not. Obviously the
syntax has to be in core, but why couldn't the syntax just call an
extension-provided callback that returns a custom scan, instead of
having a node just for TABLESAMPLE?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Both you and Andres have articulated the concern that CustomScan isn't
actually useful, but I still don't really understand why not.
I'm curious, for example, whether CustomScan would have been
sufficient to build TABLESAMPLE, and if not, why not. Obviously the
syntax has to be in core,
... so you just made the point ...
but why couldn't the syntax just call an
extension-provided callback that returns a custom scan, instead of
having a node just for TABLESAMPLE?
Because that only works for small values of "work". As far as TABLESAMPLE
goes, I intend to hold Simon's feet to the fire until there's a less
cheesy answer to the problem of scan reproducibility. Assuming we're
going to allow sample methods that can't meet the reproducibility
requirement, we need something to prevent them from producing visibly
broken query results. Ideally, the planner would avoid putting such a
scan on the inside of a nestloop. A CustomScan-based implementation could
not possibly arrange such a thing; we'd have to teach the core planner
about the concern.
Or, taking the example of a GpuScan node, it's essentially impossible
to persuade the planner to delegate any expensive function calculations,
aggregates, etc to such a node; much less teach it that that way is cheaper
than doing such things the usual way. So yeah, KaiGai-san may have a
module that does a few things with a GPU, but it's far from doing all or
even very much of what one would want.
Now, as part of the upper-planner-rewrite business that I keep hoping to
get to when I'm not riding herd on bad patches, it's likely that we might
have enough new infrastructure soon that that particular problem could
be solved. But there would just be another problem after that; a likely
example is not having adequate statistics, or sufficiently fine-grained
function cost estimates, to be able to make valid choices once there's
more than one way to do such calculations. (I'm not really impressed by
"the GPU is *always* faster" approaches.) Significant improvements of
that sort are going to take core-code changes.
Even worse, if there do get to be any popular custom-scan extensions,
we'll break them anytime we make any nontrivial planner changes, because
there is no arms-length API there. A trivial example is that even adding
or changing any fields in struct Path will necessarily break custom scan
providers, because they build Paths for themselves with no interposed API.
In large part this is the same as my core concern about the TABLESAMPLE
patch: exposing dubiously-designed APIs is soon going to force us to make
choices between breaking those APIs or not being able to make changes we
need to make. In the case of custom scans, I will not be particularly
sad when (not if) we break custom scan providers; but in other cases such
tradeoffs are going to be harder to make.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
-----Original Message-----
From: Tom Lane [mailto:tgl@sss.pgh.pa.us]
Sent: Wednesday, July 15, 2015 5:47 AM
To: Robert Haas
Cc: Alvaro Herrera; hlinnaka; Kaigai Kouhei(海外 浩平); Michael Paquier; Jim
Nasby; Kohei KaiGai; PgHacker; Simon Riggs
Subject: Re: ctidscan as an example of custom-scan (Re: [HACKERS] [v9.5] Custom
Plan API)Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Jul 14, 2015 at 3:07 PM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:As a general principle, I think it's a good idea to have a module that's
mostly just a skeleton that guides people into writing something real to
use whatever API is being tested. It needs to be simple enough that not
much need to be deleted when writing the real thing, and complex enough
to cover the parts that need covering. If whatever replaces ctidscan is
too complex, it will not serve that purpose.My guess is that something whose only purpose is to test the custom scan
interface for coverage purposes can be simpler than this module.See, I actually think the opposite: I think we've been accumulating a
reasonable amount of test code that actually serves no really useful
purpose and is just cruft. Stuff like test_shm_mq and test_decoding
seem like they actually catches bugs, so I like that, but I think
stuff like worker_spi is actually TOO simple to be useful in building
anything real, and it provides no useful test coverage, either. But
this is all a matter of opinion, of course, and I'll defer to whatever
the consensus is.I think this ties into my core unhappiness with the customscan stuff,
which is that I don't believe it's *possible* to do anything of very
great interest with it. I think anything really useful will require
core code modifications and/or hooks that don't exist now. So a finger
exercise like ctidscan, even though it might have some marginal use,
doesn't do much to alleviate that concern. It certainly doesn't seem
like it's a suitable placeholder proving that we aren't breaking any
actual use cases for the feature.
The ctidscan is originally designed to validate the behavior of custom-scan
interface, so it is natural this module is valuable in limited cased.
However, I don't think that anything valuable usually takes core code
enhancement and/or new hooks, because we already have various extensions
around core code that utilizes existing infrastructures (even though its
specifications may be changed on major version up).
At least, custom-scan enables to implement edge-features which are not
easy to merge the core because of various reasons; like dependency to
proprietary library, too experimental features, too large code to review
as minimum valuable portion and so on.
(BTW, if we care about the use cases this has, such as data recovery from
partially-corrupt tables, it would make way more sense and take way less
net new code to just teach TidScan about it.)
What I discussed with Hanada-san before was, a custom-scan provider that
replaces a particular relations join by simple scan of materialized-view
transparently. It is probably one other use case. Its design is in my
brain, but time for development is missing piece for me.
Thanks,
--
NEC Business Creation Division / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Or, taking the example of a GpuScan node, it's essentially impossible
to persuade the planner to delegate any expensive function calculations,
aggregates, etc to such a node; much less teach it that that way is cheaper
than doing such things the usual way. So yeah, KaiGai-san may have a
module that does a few things with a GPU, but it's far from doing all or
even very much of what one would want.
Why do we need to run all the functions on GPU device? PG-Strom simply
gives up to inject CustomPath if required qualifier contains unsupported
functions or data types, thus, these workloads are executed as usual.
I don't intend 100% coverage by GPU. That's no matter. People who use
massive numerical/mathematical workload will love GPU.
Now, as part of the upper-planner-rewrite business that I keep hoping to
get to when I'm not riding herd on bad patches, it's likely that we might
have enough new infrastructure soon that that particular problem could
be solved. But there would just be another problem after that; a likely
example is not having adequate statistics, or sufficiently fine-grained
function cost estimates, to be able to make valid choices once there's
more than one way to do such calculations. (I'm not really impressed by
"the GPU is *always* faster" approaches.) Significant improvements of
that sort are going to take core-code changes.
We never guarantee the interface compatibility between major version up.
If we add/modify interface on v9.6, it is duty for developer of extensions
to follow the new version, even not specific to custom-scan provider.
If v9.6 adds support fine-grained function cost estimation, I also have
to follow the feature, but it is natural.
Even worse, if there do get to be any popular custom-scan extensions,
we'll break them anytime we make any nontrivial planner changes, because
there is no arms-length API there. A trivial example is that even adding
or changing any fields in struct Path will necessarily break custom scan
providers, because they build Paths for themselves with no interposed API.
I cannot understand... If Path field gets changed, it is duty of extension
to follow the core change. We usually add/modify API specifications on
major version up. For example, I remember ProcessUtility_hook has been
changed a few times in the last several years.
Thanks,
--
NEC Business Creation Division / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 15, 2015 at 10:12 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:
We never guarantee the interface compatibility between major version up.
If we add/modify interface on v9.6, it is duty for developer of extensions
to follow the new version, even not specific to custom-scan provider.
If v9.6 adds support fine-grained function cost estimation, I also have
to follow the feature, but it is natural.
Maintaining compatibility across major versions is a best-effort and
even if we sometimes break things across major versions, and sometimes
even silently (take the example of 9.3's background worker that do not
start with 9.4 as long as bgw_notify_pid is not set to 0), the
approach is usually taken to have APIs stable and convenient able to
cover a maximum set of cases for a given set of plugins, and this
serves well in the long term. Now it is true that we cannot assume
either that the version of a plugin API will be perfect forever and
will be able to cover all the imagined test cases at first shot, still
I'd like to think that things are broken only when necessary and with
good reasons. A set of APIs changed at each major release tends to be
proof that research lacked in the first version, and would surely
demotivate its adoption by extension developers.
--
Michael
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
-----Original Message-----
From: Michael Paquier [mailto:michael.paquier@gmail.com]
Sent: Wednesday, July 15, 2015 3:29 PM
To: Kaigai Kouhei(海外 浩平)
Cc: Tom Lane; Robert Haas; Alvaro Herrera; hlinnaka; Jim Nasby; Kohei KaiGai;
PgHacker; Simon Riggs
Subject: Re: ctidscan as an example of custom-scan (Re: [HACKERS] [v9.5] Custom
Plan API)On Wed, Jul 15, 2015 at 10:12 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:
We never guarantee the interface compatibility between major version up.
If we add/modify interface on v9.6, it is duty for developer of extensions
to follow the new version, even not specific to custom-scan provider.
If v9.6 adds support fine-grained function cost estimation, I also have
to follow the feature, but it is natural.Maintaining compatibility across major versions is a best-effort and
even if we sometimes break things across major versions, and sometimes
even silently (take the example of 9.3's background worker that do not
start with 9.4 as long as bgw_notify_pid is not set to 0), the
approach is usually taken to have APIs stable and convenient able to
cover a maximum set of cases for a given set of plugins, and this
serves well in the long term. Now it is true that we cannot assume
either that the version of a plugin API will be perfect forever and
will be able to cover all the imagined test cases at first shot, still
I'd like to think that things are broken only when necessary and with
good reasons. A set of APIs changed at each major release tends to be
proof that research lacked in the first version, and would surely
demotivate its adoption by extension developers.
Yep, I also don't want to break the interface compatibility without
something reasonable, and also think following-up new core features
is a good reason to enhance the interface in the future version.
Thanks,
--
NEC Business Creation Division / PG-Strom Project
KaiGai Kohei <kaigai@ak.jp.nec.com>
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jul 14, 2015 at 6:04 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Both you and Andres have articulated the concern that CustomScan isn't
actually useful, but I still don't really understand why not.I'm curious, for example, whether CustomScan would have been
sufficient to build TABLESAMPLE, and if not, why not. Obviously the
syntax has to be in core,... so you just made the point ...
If you're going to set the bar that high, there's no point in anyone
ever trying to add extensibility anywhere, because there will always
be some other place where there isn't enough extensibility someplace
else to do everything that someone might want. The fact that the
parser isn't extensible doesn't make custom scans useless any more
than the fact that the non-extensibility of WAL-logging makes pg_am
useless.
but why couldn't the syntax just call an
extension-provided callback that returns a custom scan, instead of
having a node just for TABLESAMPLE?Because that only works for small values of "work". As far as TABLESAMPLE
goes, I intend to hold Simon's feet to the fire until there's a less
cheesy answer to the problem of scan reproducibility. Assuming we're
going to allow sample methods that can't meet the reproducibility
requirement, we need something to prevent them from producing visibly
broken query results. Ideally, the planner would avoid putting such a
scan on the inside of a nestloop. A CustomScan-based implementation could
not possibly arrange such a thing; we'd have to teach the core planner
about the concern.
Well, I think it would be better to change the tablesample methods as
you previously proposed, so that they are actually stable. But if
that's not possible, then when you (or someone) makes the core planner
able to consider such matters, you could add a CUSTOM_PATH_UNSTABLE
flag that a custom path can use to notify the core system about the
problem. Then tablesample methods that are unstable can set the flag
and those that are stable are not penalized. Presumably we'll end up
with such a flag in the tablesample-path anyway, and a separate flag
in every kind of future scan that needs similar consideration. If we
put it in some piece of infrastructure (like the custom-path stuff)
that is reusable, we could avoid reinventing the wheel every time.
Or, taking the example of a GpuScan node, it's essentially impossible
to persuade the planner to delegate any expensive function calculations,
aggregates, etc to such a node; much less teach it that that way is cheaper
than doing such things the usual way. So yeah, KaiGai-san may have a
module that does a few things with a GPU, but it's far from doing all or
even very much of what one would want.
It's true that there are things he can't do this way, but without the
custom-scan stuff, he'd be able to do even less.
Now, as part of the upper-planner-rewrite business that I keep hoping to
get to when I'm not riding herd on bad patches, it's likely that we might
have enough new infrastructure soon that that particular problem could
be solved. But there would just be another problem after that; a likely
example is not having adequate statistics, or sufficiently fine-grained
function cost estimates, to be able to make valid choices once there's
more than one way to do such calculations. (I'm not really impressed by
"the GPU is *always* faster" approaches.) Significant improvements of
that sort are going to take core-code changes.
Probably, but giving people the ability to experiment outside core,
even if it's limited, often leads to good things. And when it
doesn't, it doesn't really cost us anything.
Even worse, if there do get to be any popular custom-scan extensions,
we'll break them anytime we make any nontrivial planner changes, because
there is no arms-length API there. A trivial example is that even adding
or changing any fields in struct Path will necessarily break custom scan
providers, because they build Paths for themselves with no interposed API.
I agree that will happen, but I don't care. This happens all the
time, and every person other than yourself who has commented on this
issue has said that they'd rather have an API exposed that will later
get whacked around without warning than have no API exposed at all,
not just with respect to custom-paths, but with a whole variety of
other things as well, like sticking PGDLLIMPORT on all of the
variables that back GUCs. It is really far more tedious to try to
work around the lack of a PGDLLIMPORT marking (which causes a huge
annoyance now) than to adjust the code if and when somebody changes
something about the GUC (which causes a small annoyance later, and
only if there actually are changes).
In large part this is the same as my core concern about the TABLESAMPLE
patch: exposing dubiously-designed APIs is soon going to force us to make
choices between breaking those APIs or not being able to make changes we
need to make. In the case of custom scans, I will not be particularly
sad when (not if) we break custom scan providers; but in other cases such
tradeoffs are going to be harder to make.
I'll vote for meaningful forward progress over refusing to change the
API in every single case. I don't think I'll be remotely alone.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jul 15, 2015 at 2:28 AM, Michael Paquier
<michael.paquier@gmail.com> wrote:
On Wed, Jul 15, 2015 at 10:12 AM, Kouhei Kaigai <kaigai@ak.jp.nec.com> wrote:
We never guarantee the interface compatibility between major version up.
If we add/modify interface on v9.6, it is duty for developer of extensions
to follow the new version, even not specific to custom-scan provider.
If v9.6 adds support fine-grained function cost estimation, I also have
to follow the feature, but it is natural.Maintaining compatibility across major versions is a best-effort and
even if we sometimes break things across major versions, and sometimes
even silently (take the example of 9.3's background worker that do not
start with 9.4 as long as bgw_notify_pid is not set to 0), the
approach is usually taken to have APIs stable and convenient able to
cover a maximum set of cases for a given set of plugins, and this
serves well in the long term. Now it is true that we cannot assume
either that the version of a plugin API will be perfect forever and
will be able to cover all the imagined test cases at first shot, still
I'd like to think that things are broken only when necessary and with
good reasons. A set of APIs changed at each major release tends to be
proof that research lacked in the first version, and would surely
demotivate its adoption by extension developers.
Maybe. If those changes are demanded by extension developers who are
trying to do useful stuff with the APIs and finding problems, then
changing things will probably make those extension developers happy.
If the changes are necessitated by core improvements, then we might
get some complaints if those core improvements are perceived to have
small value compared to the API break. But I think complaints of that
kind are very rare. The only thing I can think of that falls into
approximately that category is a gripe about the replacement of
relistemp by relpersistence. But I think that was more motivated by
the way it broke SQL monitoring queries than by the way it broke C
code. There may be other more recent examples; but that's the only
one I can think of offhand. I just don't see it as a big issue.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers