On 20.12.2017 19:26, Tom Lane wrote:
Peter Eisentraut <peter.eisentr...@2ndquadrant.com> writes:
On 12/20/17 10:29, Tom Lane wrote:
Please say that's just an Oracle-ism and not SQL standard, because it's
formally ambiguous.
The SQL standard syntax appears to be something like
"tablename" [ AS OF SYSTEM TIME 'something' ] [ [ AS ] "alias" ]
That's not going to be fun to parse.
Bleah. In principle we could look two tokens ahead so as to recognize
"AS OF SYSTEM", but base_yylex is already a horrid mess with one-token
lookahead; I don't much want to try to extend it to that.
Possibly the most workable compromise is to use lookahead to convert
"AS OF" to "AS_LA OF", and then we could either just break using OF
as an alias, or add an extra production that allows "AS_LA OF" to
be treated as "AS alias" if it's not followed by the appropriate
stuff.
It's a shame that the SQL committee appears to be so ignorant of
standard parsing technology.
regards, tom lane
Thank you for suggestion with AS_LA: it really works.
Actually instead of AS_LA I just return ASOF token if next token after
AS is OF.
So now it is possible to write query in this way:
select * from foo as of timestamp '2017-12-21 14:12:15.1867';
There is still one significant difference of my prototype implementation
with SQL standard: it associates timestamp with select statement, not
with particular table.
It seems to be more difficult to support and I am not sure that joining
tables from different timelines has much sense.
But certainly it also can be fixed.
--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index 3de8333..2126847 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -2353,6 +2353,7 @@ JumbleQuery(pgssJumbleState *jstate, Query *query)
JumbleExpr(jstate, (Node *) query->sortClause);
JumbleExpr(jstate, query->limitOffset);
JumbleExpr(jstate, query->limitCount);
+ JumbleExpr(jstate, query->asofTimestamp);
/* we ignore rowMarks */
JumbleExpr(jstate, query->setOperations);
}
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index cc09895..d2e0799 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -29,6 +29,6 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \
nodeCtescan.o nodeNamedtuplestorescan.o nodeWorktablescan.o \
nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \
nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \
- nodeTableFuncscan.o
+ nodeTableFuncscan.o nodeAsof.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index f1636a5..38c79b8 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -285,6 +285,10 @@ ExecReScan(PlanState *node)
ExecReScanLimit((LimitState *) node);
break;
+ case T_AsofState:
+ ExecReScanAsof((AsofState *) node);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
break;
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index a3e962e..1912ae4 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -329,6 +329,7 @@ search_plan_tree(PlanState *node, Oid table_oid)
*/
case T_ResultState:
case T_LimitState:
+ case T_AsofState:
return search_plan_tree(node->lefttree, table_oid);
/*
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index fcb8b56..586b5b3 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -75,6 +75,7 @@
#include "executor/executor.h"
#include "executor/nodeAgg.h"
#include "executor/nodeAppend.h"
+#include "executor/nodeAsof.h"
#include "executor/nodeBitmapAnd.h"
#include "executor/nodeBitmapHeapscan.h"
#include "executor/nodeBitmapIndexscan.h"
@@ -364,6 +365,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags)
estate, eflags);
break;
+ case T_Asof:
+ result = (PlanState *) ExecInitAsof((Asof *) node,
+ estate, eflags);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
result = NULL; /* keep compiler quiet */
@@ -727,6 +733,10 @@ ExecEndNode(PlanState *node)
ExecEndLimit((LimitState *) node);
break;
+ case T_AsofState:
+ ExecEndAsof((AsofState *) node);
+ break;
+
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node));
break;
diff --git a/src/backend/executor/nodeAsof.c b/src/backend/executor/nodeAsof.c
new file mode 100644
index 0000000..8957a91
--- /dev/null
+++ b/src/backend/executor/nodeAsof.c
@@ -0,0 +1,157 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeAsof.c
+ * Routines to handle asofing of query results where appropriate
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/executor/nodeAsof.c
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ * ExecAsof - extract a asofed range of tuples
+ * ExecInitAsof - initialize node and subnodes..
+ * ExecEndAsof - shutdown node and subnodes
+ */
+
+#include "postgres.h"
+
+#include "executor/executor.h"
+#include "executor/nodeAsof.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+
+/* ----------------------------------------------------------------
+ * ExecAsof
+ *
+ * This is a very simple node which just performs ASOF/OFFSET
+ * filtering on the stream of tuples returned by a subplan.
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot * /* return: a tuple or NULL */
+ExecAsof(PlanState *pstate)
+{
+ AsofState *node = castNode(AsofState, pstate);
+ PlanState *outerPlan = outerPlanState(node);
+ TimestampTz outerAsofTimestamp;
+ TupleTableSlot *slot;
+
+ if (!node->timestampCalculated)
+ {
+ Datum val;
+ bool isNull;
+
+ val = ExecEvalExprSwitchContext(node->asofExpr,
+ pstate->ps_ExprContext,
+ &isNull);
+ /* Interpret NULL timestamp as no timestamp */
+ if (isNull)
+ node->asofTimestamp = 0;
+ else
+ {
+ node->asofTimestamp = DatumGetInt64(val);
+ }
+ node->timestampCalculated = true;
+ }
+ outerAsofTimestamp = pstate->state->es_snapshot->asofTimestamp;
+ pstate->state->es_snapshot->asofTimestamp = node->asofTimestamp;
+ slot = ExecProcNode(outerPlan);
+ pstate->state->es_snapshot->asofTimestamp = outerAsofTimestamp;
+ return slot;
+}
+
+
+/* ----------------------------------------------------------------
+ * ExecInitAsof
+ *
+ * This initializes the asof node state structures and
+ * the node's subplan.
+ * ----------------------------------------------------------------
+ */
+AsofState *
+ExecInitAsof(Asof *node, EState *estate, int eflags)
+{
+ AsofState *asofstate;
+ Plan *outerPlan;
+
+ /* check for unsupported flags */
+ Assert(!(eflags & EXEC_FLAG_MARK));
+
+ /*
+ * create state structure
+ */
+ asofstate = makeNode(AsofState);
+ asofstate->ps.plan = (Plan *) node;
+ asofstate->ps.state = estate;
+ asofstate->ps.ExecProcNode = ExecAsof;
+ asofstate->timestampCalculated = false;
+
+ /*
+ * Miscellaneous initialization
+ *
+ * Asof nodes never call ExecQual or ExecProject, but they need an
+ * exprcontext anyway to evaluate the asof/offset parameters in.
+ */
+ ExecAssignExprContext(estate, &asofstate->ps);
+
+ /*
+ * initialize child expressions
+ */
+ asofstate->asofExpr = ExecInitExpr((Expr *) node->asofTimestamp,
+ (PlanState *) asofstate);
+ /*
+ * Tuple table initialization (XXX not actually used...)
+ */
+ ExecInitResultTupleSlot(estate, &asofstate->ps);
+
+ /*
+ * then initialize outer plan
+ */
+ outerPlan = outerPlan(node);
+ outerPlanState(asofstate) = ExecInitNode(outerPlan, estate, eflags);
+
+ /*
+ * asof nodes do no projections, so initialize projection info for this
+ * node appropriately
+ */
+ ExecAssignResultTypeFromTL(&asofstate->ps);
+ asofstate->ps.ps_ProjInfo = NULL;
+
+ return asofstate;
+}
+
+/* ----------------------------------------------------------------
+ * ExecEndAsof
+ *
+ * This shuts down the subplan and frees resources allocated
+ * to this node.
+ * ----------------------------------------------------------------
+ */
+void
+ExecEndAsof(AsofState *node)
+{
+ ExecFreeExprContext(&node->ps);
+ ExecEndNode(outerPlanState(node));
+}
+
+
+void
+ExecReScanAsof(AsofState *node)
+{
+ /*
+ * Recompute AS OF in case parameters changed, and reset the current snapshot
+ */
+ node->timestampCalculated = false;
+
+ /*
+ * if chgParam of subnode is not null then plan will be re-scanned by
+ * first ExecProcNode.
+ */
+ if (node->ps.lefttree->chgParam == NULL)
+ ExecReScan(node->ps.lefttree);
+}
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index 883f46c..ebe0362 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -23,6 +23,7 @@
#include "executor/executor.h"
#include "executor/nodeLimit.h"
+#include "executor/nodeAsof.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b1515dd..e142bbc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1134,6 +1134,27 @@ _copyLimit(const Limit *from)
}
/*
+ * _copyAsof
+ */
+static Asof *
+_copyAsof(const Asof *from)
+{
+ Asof *newnode = makeNode(Asof);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyPlanFields((const Plan *) from, (Plan *) newnode);
+
+ /*
+ * copy remainder of node
+ */
+ COPY_NODE_FIELD(asofTimestamp);
+
+ return newnode;
+}
+
+/*
* _copyNestLoopParam
*/
static NestLoopParam *
@@ -2958,6 +2979,7 @@ _copyQuery(const Query *from)
COPY_NODE_FIELD(sortClause);
COPY_NODE_FIELD(limitOffset);
COPY_NODE_FIELD(limitCount);
+ COPY_NODE_FIELD(asofTimestamp);
COPY_NODE_FIELD(rowMarks);
COPY_NODE_FIELD(setOperations);
COPY_NODE_FIELD(constraintDeps);
@@ -3048,6 +3070,7 @@ _copySelectStmt(const SelectStmt *from)
COPY_SCALAR_FIELD(all);
COPY_NODE_FIELD(larg);
COPY_NODE_FIELD(rarg);
+ COPY_NODE_FIELD(asofTimestamp);
return newnode;
}
@@ -4840,6 +4863,9 @@ copyObjectImpl(const void *from)
case T_Limit:
retval = _copyLimit(from);
break;
+ case T_Asof:
+ retval = _copyAsof(from);
+ break;
case T_NestLoopParam:
retval = _copyNestLoopParam(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2e869a9..6bbbc1c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -982,6 +982,7 @@ _equalQuery(const Query *a, const Query *b)
COMPARE_NODE_FIELD(sortClause);
COMPARE_NODE_FIELD(limitOffset);
COMPARE_NODE_FIELD(limitCount);
+ COMPARE_NODE_FIELD(asofTimestamp);
COMPARE_NODE_FIELD(rowMarks);
COMPARE_NODE_FIELD(setOperations);
COMPARE_NODE_FIELD(constraintDeps);
@@ -1062,6 +1063,7 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
COMPARE_SCALAR_FIELD(all);
COMPARE_NODE_FIELD(larg);
COMPARE_NODE_FIELD(rarg);
+ COMPARE_NODE_FIELD(asofTimestamp);
return true;
}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index c2a93b2..d674ec2 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2267,6 +2267,8 @@ query_tree_walker(Query *query,
return true;
if (walker(query->limitCount, context))
return true;
+ if (walker(query->asofTimestamp, context))
+ return true;
if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
{
if (walker((Node *) query->cteList, context))
@@ -3089,6 +3091,7 @@ query_tree_mutator(Query *query,
MUTATE(query->havingQual, query->havingQual, Node *);
MUTATE(query->limitOffset, query->limitOffset, Node *);
MUTATE(query->limitCount, query->limitCount, Node *);
+ MUTATE(query->asofTimestamp, query->asofTimestamp, Node *);
if (!(flags & QTW_IGNORE_CTE_SUBQUERIES))
MUTATE(query->cteList, query->cteList, List *);
else /* else copy CTE list as-is */
@@ -3442,6 +3445,8 @@ raw_expression_tree_walker(Node *node,
return true;
if (walker(stmt->rarg, context))
return true;
+ if (walker(stmt->asofTimestamp, context))
+ return true;
}
break;
case T_A_Expr:
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b59a521..e59c60d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -978,6 +978,16 @@ _outLimit(StringInfo str, const Limit *node)
}
static void
+_outAsof(StringInfo str, const Asof *node)
+{
+ WRITE_NODE_TYPE("ASOF");
+
+ _outPlanInfo(str, (const Plan *) node);
+
+ WRITE_NODE_FIELD(asofTimestamp);
+}
+
+static void
_outNestLoopParam(StringInfo str, const NestLoopParam *node)
{
WRITE_NODE_TYPE("NESTLOOPPARAM");
@@ -2127,6 +2137,17 @@ _outLimitPath(StringInfo str, const LimitPath *node)
}
static void
+_outAsofPath(StringInfo str, const AsofPath *node)
+{
+ WRITE_NODE_TYPE("ASOFPATH");
+
+ _outPathInfo(str, (const Path *) node);
+
+ WRITE_NODE_FIELD(subpath);
+ WRITE_NODE_FIELD(asofTimestamp);
+}
+
+static void
_outGatherMergePath(StringInfo str, const GatherMergePath *node)
{
WRITE_NODE_TYPE("GATHERMERGEPATH");
@@ -2722,6 +2743,7 @@ _outSelectStmt(StringInfo str, const SelectStmt *node)
WRITE_BOOL_FIELD(all);
WRITE_NODE_FIELD(larg);
WRITE_NODE_FIELD(rarg);
+ WRITE_NODE_FIELD(asofTimestamp);
}
static void
@@ -2925,6 +2947,7 @@ _outQuery(StringInfo str, const Query *node)
WRITE_NODE_FIELD(sortClause);
WRITE_NODE_FIELD(limitOffset);
WRITE_NODE_FIELD(limitCount);
+ WRITE_NODE_FIELD(asofTimestamp);
WRITE_NODE_FIELD(rowMarks);
WRITE_NODE_FIELD(setOperations);
WRITE_NODE_FIELD(constraintDeps);
@@ -3753,6 +3776,9 @@ outNode(StringInfo str, const void *obj)
case T_Limit:
_outLimit(str, obj);
break;
+ case T_Asof:
+ _outAsof(str, obj);
+ break;
case T_NestLoopParam:
_outNestLoopParam(str, obj);
break;
@@ -4002,6 +4028,9 @@ outNode(StringInfo str, const void *obj)
case T_LimitPath:
_outLimitPath(str, obj);
break;
+ case T_AsofPath:
+ _outAsofPath(str, obj);
+ break;
case T_GatherMergePath:
_outGatherMergePath(str, obj);
break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 0d17ae8..f805ea3 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -266,6 +266,7 @@ _readQuery(void)
READ_NODE_FIELD(sortClause);
READ_NODE_FIELD(limitOffset);
READ_NODE_FIELD(limitCount);
+ READ_NODE_FIELD(asofTimestamp);
READ_NODE_FIELD(rowMarks);
READ_NODE_FIELD(setOperations);
READ_NODE_FIELD(constraintDeps);
@@ -2272,6 +2273,21 @@ _readLimit(void)
}
/*
+ * _readAsof
+ */
+static Asof *
+_readAsof(void)
+{
+ READ_LOCALS(Asof);
+
+ ReadCommonPlan(&local_node->plan);
+
+ READ_NODE_FIELD(asofTimestamp);
+
+ READ_DONE();
+}
+
+/*
* _readNestLoopParam
*/
static NestLoopParam *
@@ -2655,6 +2671,8 @@ parseNodeString(void)
return_value = _readLockRows();
else if (MATCH("LIMIT", 5))
return_value = _readLimit();
+ else if (MATCH("ASOF", 4))
+ return_value = _readAsof();
else if (MATCH("NESTLOOPPARAM", 13))
return_value = _readNestLoopParam();
else if (MATCH("PLANROWMARK", 11))
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 0e8463e..9c97018 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -2756,7 +2756,7 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
SetOperationStmt *topop;
/* Check point 1 */
- if (subquery->limitOffset != NULL || subquery->limitCount != NULL)
+ if (subquery->limitOffset != NULL || subquery->limitCount != NULL || subquery->asofTimestamp != NULL)
return false;
/* Check points 3, 4, and 5 */
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index f6c83d0..413a7a7 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -114,6 +114,8 @@ static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path
static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path);
static Limit *create_limit_plan(PlannerInfo *root, LimitPath *best_path,
int flags);
+static Asof *create_asof_plan(PlannerInfo *root, AsofPath *best_path,
+ int flags);
static SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path,
List *tlist, List *scan_clauses);
static SampleScan *create_samplescan_plan(PlannerInfo *root, Path *best_path,
@@ -483,6 +485,11 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
(LimitPath *) best_path,
flags);
break;
+ case T_Asof:
+ plan = (Plan *) create_asof_plan(root,
+ (AsofPath *) best_path,
+ flags);
+ break;
case T_GatherMerge:
plan = (Plan *) create_gather_merge_plan(root,
(GatherMergePath *) best_path);
@@ -2410,6 +2417,29 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
return plan;
}
+/*
+ * create_asof_plan
+ *
+ * Create a Limit plan for 'best_path' and (recursively) plans
+ * for its subpaths.
+ */
+static Asof *
+create_asof_plan(PlannerInfo *root, AsofPath *best_path, int flags)
+{
+ Asof *plan;
+ Plan *subplan;
+
+ /* Limit doesn't project, so tlist requirements pass through */
+ subplan = create_plan_recurse(root, best_path->subpath, flags);
+
+ plan = make_asof(subplan,
+ best_path->asofTimestamp);
+
+ copy_generic_path_info(&plan->plan, (Path *) best_path);
+
+ return plan;
+}
+
/*****************************************************************************
*
@@ -6385,6 +6415,26 @@ make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount)
}
/*
+ * make_asof
+ * Build a Asof plan node
+ */
+Asof *
+make_asof(Plan *lefttree, Node *asofTimestamp)
+{
+ Asof *node = makeNode(Asof);
+ Plan *plan = &node->plan;
+
+ plan->targetlist = lefttree->targetlist;
+ plan->qual = NIL;
+ plan->lefttree = lefttree;
+ plan->righttree = NULL;
+
+ node->asofTimestamp = asofTimestamp;
+
+ return node;
+}
+
+/*
* make_result
* Build a Result plan node
*/
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index e8bc15c..e5c867b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -84,6 +84,7 @@ create_upper_paths_hook_type create_upper_paths_hook = NULL;
#define EXPRKIND_ARBITER_ELEM 10
#define EXPRKIND_TABLEFUNC 11
#define EXPRKIND_TABLEFUNC_LATERAL 12
+#define EXPRKIND_ASOF 13
/* Passthrough data for standard_qp_callback */
typedef struct
@@ -696,6 +697,9 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
parse->limitCount = preprocess_expression(root, parse->limitCount,
EXPRKIND_LIMIT);
+ parse->asofTimestamp = preprocess_expression(root, parse->asofTimestamp,
+ EXPRKIND_ASOF);
+
if (parse->onConflict)
{
parse->onConflict->arbiterElems = (List *)
@@ -2032,12 +2036,13 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
/*
* If the input rel is marked consider_parallel and there's nothing that's
- * not parallel-safe in the LIMIT clause, then the final_rel can be marked
+ * not parallel-safe in the LIMIT and ASOF clauses, then the final_rel can be marked
* consider_parallel as well. Note that if the query has rowMarks or is
* not a SELECT, consider_parallel will be false for every relation in the
* query.
*/
if (current_rel->consider_parallel &&
+ is_parallel_safe(root, parse->asofTimestamp) &&
is_parallel_safe(root, parse->limitOffset) &&
is_parallel_safe(root, parse->limitCount))
final_rel->consider_parallel = true;
@@ -2084,6 +2089,15 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
}
/*
+ * If there is a AS OF clause, add the ASOF node.
+ */
+ if (parse->asofTimestamp)
+ {
+ path = (Path *) create_asof_path(root, final_rel, path,
+ parse->asofTimestamp);
+ }
+
+ /*
* If this is an INSERT/UPDATE/DELETE, and we're not being called from
* inheritance_planner, add the ModifyTable node.
*/
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index b5c4124..bc79055 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -700,6 +700,23 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
fix_scan_expr(root, splan->limitCount, rtoffset);
}
break;
+ case T_Asof:
+ {
+ Asof *splan = (Asof *) plan;
+
+ /*
+ * Like the plan types above, Asof doesn't evaluate its tlist
+ * or quals. It does have live expression for asof,
+ * however; and those cannot contain subplan variable refs, so
+ * fix_scan_expr works for them.
+ */
+ set_dummy_tlist_references(plan, rtoffset);
+ Assert(splan->plan.qual == NIL);
+
+ splan->asofTimestamp =
+ fix_scan_expr(root, splan->asofTimestamp, rtoffset);
+ }
+ break;
case T_Agg:
{
Agg *agg = (Agg *) plan;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 2e3abee..c215d3b 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1602,6 +1602,7 @@ simplify_EXISTS_query(PlannerInfo *root, Query *query)
query->hasModifyingCTE ||
query->havingQual ||
query->limitOffset ||
+ query->asofTimestamp ||
query->rowMarks)
return false;
@@ -2691,6 +2692,11 @@ finalize_plan(PlannerInfo *root, Plan *plan,
&context);
break;
+ case T_Asof:
+ finalize_primnode(((Asof *) plan)->asofTimestamp,
+ &context);
+ break;
+
case T_RecursiveUnion:
/* child nodes are allowed to reference wtParam */
locally_added_param = ((RecursiveUnion *) plan)->wtParam;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 1d7e499..a06806e 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1443,6 +1443,7 @@ is_simple_subquery(Query *subquery, RangeTblEntry *rte,
subquery->distinctClause ||
subquery->limitOffset ||
subquery->limitCount ||
+ subquery->asofTimestamp ||
subquery->hasForUpdate ||
subquery->cteList)
return false;
@@ -1758,6 +1759,7 @@ is_simple_union_all(Query *subquery)
if (subquery->sortClause ||
subquery->limitOffset ||
subquery->limitCount ||
+ subquery->asofTimestamp ||
subquery->rowMarks ||
subquery->cteList)
return false;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 6a2d5ad..1372fe5 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4514,6 +4514,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
querytree->sortClause ||
querytree->limitOffset ||
querytree->limitCount ||
+ querytree->asofTimestamp ||
querytree->setOperations ||
list_length(querytree->targetList) != 1)
goto fail;
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 54126fb..8a6f057 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3358,6 +3358,37 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
}
/*
+ * create_asof_path
+ * Creates a pathnode that represents performing AS OF clause
+ */
+AsofPath *
+create_asof_path(PlannerInfo *root, RelOptInfo *rel,
+ Path *subpath,
+ Node *asofTimestamp)
+{
+ AsofPath *pathnode = makeNode(AsofPath);
+ pathnode->path.pathtype = T_Asof;
+ pathnode->path.parent = rel;
+ /* Limit doesn't project, so use source path's pathtarget */
+ pathnode->path.pathtarget = subpath->pathtarget;
+ /* For now, assume we are above any joins, so no parameterization */
+ pathnode->path.param_info = NULL;
+ pathnode->path.parallel_aware = false;
+ pathnode->path.parallel_safe = rel->consider_parallel &&
+ subpath->parallel_safe;
+ pathnode->path.parallel_workers = subpath->parallel_workers;
+ pathnode->path.rows = subpath->rows;
+ pathnode->path.startup_cost = subpath->startup_cost;
+ pathnode->path.total_cost = subpath->total_cost;
+ pathnode->path.pathkeys = subpath->pathkeys;
+ pathnode->subpath = subpath;
+ pathnode->asofTimestamp = asofTimestamp;
+
+ return pathnode;
+}
+
+
+/*
* create_limit_path
* Creates a pathnode that represents performing LIMIT/OFFSET
*
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index d680d22..aa37f74 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -505,6 +505,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
selectStmt->sortClause != NIL ||
selectStmt->limitOffset != NULL ||
selectStmt->limitCount != NULL ||
+ selectStmt->asofTimestamp != NULL ||
selectStmt->lockingClause != NIL ||
selectStmt->withClause != NULL));
@@ -1266,6 +1267,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
EXPR_KIND_LIMIT, "LIMIT");
+ qry->asofTimestamp = transformAsofClause(pstate, stmt->asofTimestamp);
/* transform window clauses after we have seen all window functions */
qry->windowClause = transformWindowDefinitions(pstate,
@@ -1512,6 +1514,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
EXPR_KIND_LIMIT, "LIMIT");
+ qry->asofTimestamp = transformAsofClause(pstate, stmt->asofTimestamp);
if (stmt->lockingClause)
ereport(ERROR,
@@ -1553,6 +1556,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
List *sortClause;
Node *limitOffset;
Node *limitCount;
+ Node *asofTimestamp;
List *lockingClause;
WithClause *withClause;
Node *node;
@@ -1598,12 +1602,14 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
sortClause = stmt->sortClause;
limitOffset = stmt->limitOffset;
limitCount = stmt->limitCount;
+ asofTimestamp = stmt->asofTimestamp;
lockingClause = stmt->lockingClause;
withClause = stmt->withClause;
stmt->sortClause = NIL;
stmt->limitOffset = NULL;
stmt->limitCount = NULL;
+ stmt->asofTimestamp = NULL;
stmt->lockingClause = NIL;
stmt->withClause = NULL;
@@ -1747,6 +1753,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
EXPR_KIND_OFFSET, "OFFSET");
qry->limitCount = transformLimitClause(pstate, limitCount,
EXPR_KIND_LIMIT, "LIMIT");
+ qry->asofTimestamp = transformAsofClause(pstate, asofTimestamp);
qry->rtable = pstate->p_rtable;
qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
@@ -1829,7 +1836,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
{
Assert(stmt->larg != NULL && stmt->rarg != NULL);
if (stmt->sortClause || stmt->limitOffset || stmt->limitCount ||
- stmt->lockingClause || stmt->withClause)
+ stmt->lockingClause || stmt->withClause || stmt->asofTimestamp)
isLeaf = true;
else
isLeaf = false;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ebfc94f..6a9821e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -424,6 +424,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> for_locking_strength
%type <node> for_locking_item
%type <list> for_locking_clause opt_for_locking_clause for_locking_items
+%type <node> asof_clause
%type <list> locked_rels_list
%type <boolean> all_or_distinct
@@ -605,7 +606,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD_P ADMIN AFTER
- AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC
+ AGGREGATE ALL ALSO ALTER ALWAYS ANALYSE ANALYZE AND ANY ARRAY AS ASC ASOF
ASSERTION ASSIGNMENT ASYMMETRIC AT ATTACH ATTRIBUTE AUTHORIZATION
BACKWARD BEFORE BEGIN_P BETWEEN BIGINT BINARY BIT
@@ -11129,8 +11130,8 @@ SelectStmt: select_no_parens %prec UMINUS
;
select_with_parens:
- '(' select_no_parens ')' { $$ = $2; }
- | '(' select_with_parens ')' { $$ = $2; }
+ '(' select_no_parens ')' { $$ = $2; }
+ | '(' select_with_parens ')' { $$ = $2; }
;
/*
@@ -11234,7 +11235,7 @@ select_clause:
simple_select:
SELECT opt_all_clause opt_target_list
into_clause from_clause where_clause
- group_clause having_clause window_clause
+ group_clause having_clause window_clause asof_clause
{
SelectStmt *n = makeNode(SelectStmt);
n->targetList = $3;
@@ -11244,11 +11245,12 @@ simple_select:
n->groupClause = $7;
n->havingClause = $8;
n->windowClause = $9;
+ n->asofTimestamp = $10;
$$ = (Node *)n;
}
| SELECT distinct_clause target_list
into_clause from_clause where_clause
- group_clause having_clause window_clause
+ group_clause having_clause window_clause asof_clause
{
SelectStmt *n = makeNode(SelectStmt);
n->distinctClause = $2;
@@ -11259,6 +11261,7 @@ simple_select:
n->groupClause = $7;
n->havingClause = $8;
n->windowClause = $9;
+ n->asofTimestamp = $10;
$$ = (Node *)n;
}
| values_clause { $$ = $1; }
@@ -11494,6 +11497,10 @@ opt_select_limit:
| /* EMPTY */ { $$ = list_make2(NULL,NULL); }
;
+asof_clause: ASOF a_expr { $$ = $2; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
limit_clause:
LIMIT select_limit_value
{ $$ = $2; }
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 4c4f4cd..6c3e506 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -439,6 +439,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr)
break;
case EXPR_KIND_LIMIT:
case EXPR_KIND_OFFSET:
+ case EXPR_KIND_ASOF:
errkind = true;
break;
case EXPR_KIND_RETURNING:
@@ -856,6 +857,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc,
break;
case EXPR_KIND_LIMIT:
case EXPR_KIND_OFFSET:
+ case EXPR_KIND_ASOF:
errkind = true;
break;
case EXPR_KIND_RETURNING:
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 2828bbf..6cdf1af 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -1729,6 +1729,30 @@ transformWhereClause(ParseState *pstate, Node *clause,
/*
+ * transformAsofClause -
+ * Transform the expression and make sure it is of type bigint.
+ * Used for ASOF clause.
+ *
+ */
+Node *
+transformAsofClause(ParseState *pstate, Node *clause)
+{
+ Node *qual;
+
+ if (clause == NULL)
+ return NULL;
+
+ qual = transformExpr(pstate, clause, EXPR_KIND_ASOF);
+
+ qual = coerce_to_specific_type(pstate, qual, TIMESTAMPTZOID, "ASOF");
+
+ /* LIMIT can't refer to any variables of the current query */
+ checkExprIsVarFree(pstate, qual, "ASOF");
+
+ return qual;
+}
+
+/*
* transformLimitClause -
* Transform the expression and make sure it is of type bigint.
* Used for LIMIT and allied clauses.
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index 5160fdb..d326937 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -942,6 +942,8 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate)
cstate);
checkWellFormedRecursionWalker((Node *) stmt->limitCount,
cstate);
+ checkWellFormedRecursionWalker((Node *) stmt->asofTimestamp,
+ cstate);
checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
cstate);
/* stmt->withClause is intentionally ignored here */
@@ -961,6 +963,8 @@ checkWellFormedSelectStmt(SelectStmt *stmt, CteState *cstate)
cstate);
checkWellFormedRecursionWalker((Node *) stmt->limitCount,
cstate);
+ checkWellFormedRecursionWalker((Node *) stmt->asofTimestamp,
+ cstate);
checkWellFormedRecursionWalker((Node *) stmt->lockingClause,
cstate);
/* stmt->withClause is intentionally ignored here */
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 29f9da7..94b6b52 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1814,6 +1814,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
case EXPR_KIND_DISTINCT_ON:
case EXPR_KIND_LIMIT:
case EXPR_KIND_OFFSET:
+ case EXPR_KIND_ASOF:
case EXPR_KIND_RETURNING:
case EXPR_KIND_VALUES:
case EXPR_KIND_VALUES_SINGLE:
@@ -3445,6 +3446,8 @@ ParseExprKindName(ParseExprKind exprKind)
return "LIMIT";
case EXPR_KIND_OFFSET:
return "OFFSET";
+ case EXPR_KIND_ASOF:
+ return "ASOF";
case EXPR_KIND_RETURNING:
return "RETURNING";
case EXPR_KIND_VALUES:
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index e6b0856..a6bcfc7 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -2250,6 +2250,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location)
break;
case EXPR_KIND_LIMIT:
case EXPR_KIND_OFFSET:
+ case EXPR_KIND_ASOF:
errkind = true;
break;
case EXPR_KIND_RETURNING:
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index b032651..fec5ac5 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -767,6 +767,7 @@ typeStringToTypeName(const char *str)
stmt->sortClause != NIL ||
stmt->limitOffset != NULL ||
stmt->limitCount != NULL ||
+ stmt->asofTimestamp != NULL ||
stmt->lockingClause != NIL ||
stmt->withClause != NULL ||
stmt->op != SETOP_NONE)
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 245b4cd..63017b9 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -108,6 +108,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
*/
switch (cur_token)
{
+ case AS:
+ cur_token_length = 2;
+ break;
case NOT:
cur_token_length = 3;
break;
@@ -155,6 +158,14 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
/* Replace cur_token if needed, based on lookahead */
switch (cur_token)
{
+ case AS:
+ if (next_token == OF)
+ {
+ cur_token = ASOF;
+ *(yyextra->lookahead_end) = yyextra->lookahead_hold_char;
+ yyextra->have_lookahead = false;
+ }
+ break;
case NOT:
/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
switch (next_token)
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index e93552a..1bb37b2 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2301,8 +2301,8 @@ view_query_is_auto_updatable(Query *viewquery, bool check_cols)
if (viewquery->cteList != NIL)
return gettext_noop("Views containing WITH are not automatically updatable.");
- if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL)
- return gettext_noop("Views containing LIMIT or OFFSET are not automatically updatable.");
+ if (viewquery->limitOffset != NULL || viewquery->limitCount != NULL || viewquery->asofTimestamp != NULL)
+ return gettext_noop("Views containing AS OF, LIMIT or OFFSET are not automatically updatable.");
/*
* We must not allow window functions or set returning functions in the
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8514c21..330ebfb 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5115,6 +5115,12 @@ get_select_query_def(Query *query, deparse_context *context,
else
get_rule_expr(query->limitCount, context, false);
}
+ if (query->asofTimestamp != NULL)
+ {
+ appendContextKeyword(context, " AS OF ",
+ -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+ get_rule_expr(query->asofTimestamp, context, false);
+ }
/* Add FOR [KEY] UPDATE/SHARE clauses if present */
if (query->hasForUpdate)
@@ -5503,10 +5509,11 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
Assert(subquery != NULL);
Assert(subquery->setOperations == NULL);
- /* Need parens if WITH, ORDER BY, FOR UPDATE, or LIMIT; see gram.y */
+ /* Need parens if WITH, ORDER BY, AS OF, FOR UPDATE, or LIMIT; see gram.y */
need_paren = (subquery->cteList ||
subquery->sortClause ||
subquery->rowMarks ||
+ subquery->asofTimestamp ||
subquery->limitOffset ||
subquery->limitCount);
if (need_paren)
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index 2b218e0..621c6a3 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -69,6 +69,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "access/commit_ts.h"
#include "storage/bufmgr.h"
#include "storage/procarray.h"
#include "utils/builtins.h"
@@ -81,7 +82,6 @@
SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
-
/*
* SetHintBits()
*
@@ -1476,7 +1476,17 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
{
uint32 i;
- /*
+ if (snapshot->asofTimestamp != 0)
+ {
+ TimestampTz ts;
+ if (TransactionIdGetCommitTsData(xid, &ts, NULL))
+ {
+ return timestamptz_cmp_internal(snapshot->asofTimestamp, ts) < 0;
+ }
+ }
+
+
+ /*
* Make a quick range check to eliminate most XIDs without looking at the
* xip arrays. Note that this is OK even if we convert a subxact XID to
* its parent below, because a subxact with XID < xmin has surely also got
diff --git a/src/include/executor/nodeAsof.h b/src/include/executor/nodeAsof.h
new file mode 100644
index 0000000..2f8e1a2
--- /dev/null
+++ b/src/include/executor/nodeAsof.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeLimit.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/executor/nodeLimit.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODEASOF_H
+#define NODEASOF_H
+
+#include "nodes/execnodes.h"
+
+extern AsofState *ExecInitAsof(Asof *node, EState *estate, int eflags);
+extern void ExecEndAsof(AsofState *node);
+extern void ExecReScanAsof(AsofState *node);
+
+#endif /* NODEASOF_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 1a35c5c..42cd037 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2109,4 +2109,12 @@ typedef struct LimitState
TupleTableSlot *subSlot; /* tuple last obtained from subplan */
} LimitState;
+typedef struct AsofState
+{
+ PlanState ps; /* its first field is NodeTag */
+ ExprState *asofExpr; /* AS OF expression */
+ TimestampTz asofTimestamp; /* AS OF timestamp or 0 if not set */
+ bool timestampCalculated; /* whether AS OF timestamp was calculated */
+} AsofState;
+
#endif /* EXECNODES_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index c5b5115..e69a189 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -83,6 +83,7 @@ typedef enum NodeTag
T_SetOp,
T_LockRows,
T_Limit,
+ T_Asof,
/* these aren't subclasses of Plan: */
T_NestLoopParam,
T_PlanRowMark,
@@ -135,6 +136,7 @@ typedef enum NodeTag
T_SetOpState,
T_LockRowsState,
T_LimitState,
+ T_AsofState,
/*
* TAGS FOR PRIMITIVE NODES (primnodes.h)
@@ -251,6 +253,7 @@ typedef enum NodeTag
T_LockRowsPath,
T_ModifyTablePath,
T_LimitPath,
+ T_AsofPath,
/* these aren't subclasses of Path: */
T_EquivalenceClass,
T_EquivalenceMember,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2eaa6b2..e592418 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -158,6 +158,7 @@ typedef struct Query
Node *limitOffset; /* # of result tuples to skip (int8 expr) */
Node *limitCount; /* # of result tuples to return (int8 expr) */
+ Node *asofTimestamp; /* ASOF timestamp */
List *rowMarks; /* a list of RowMarkClause's */
Node *setOperations; /* set-operation tree if this is top level of
@@ -1552,6 +1553,7 @@ typedef struct SelectStmt
struct SelectStmt *larg; /* left child */
struct SelectStmt *rarg; /* right child */
/* Eventually add fields for CORRESPONDING spec here */
+ Node* asofTimestamp;
} SelectStmt;
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 02fb366..60c66b5 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -931,6 +931,19 @@ typedef struct Limit
} Limit;
+/* ----------------
+ * asof node
+ *
+ */
+typedef struct Asof
+{
+ Plan plan;
+ Node *asofTimestamp;
+} Asof;
+
+
+
+
/*
* RowMarkType -
* enums for types of row-marking operations
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 1108b6a..d1ca25e9 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -1694,6 +1694,16 @@ typedef struct LimitPath
Node *limitCount; /* COUNT parameter, or NULL if none */
} LimitPath;
+/*
+ * AsofPath represents applying AS OF timestamp qualifier
+ */
+typedef struct AsofPath
+{
+ Path path;
+ Path *subpath; /* path representing input source */
+ Node *asofTimestamp; /* AS OF timestamp */
+} AsofPath;
+
/*
* Restriction clause info.
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 99f65b4..9da39f1 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -250,6 +250,9 @@ extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
Path *subpath,
Node *limitOffset, Node *limitCount,
int64 offset_est, int64 count_est);
+extern AsofPath *create_asof_path(PlannerInfo *root, RelOptInfo *rel,
+ Path *subpath,
+ Node *asofTimeout);
extern Path *reparameterize_path(PlannerInfo *root, Path *path,
Relids required_outer,
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index d613322..f5e5508 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -65,6 +65,7 @@ extern Agg *make_agg(List *tlist, List *qual,
List *groupingSets, List *chain,
double dNumGroups, Plan *lefttree);
extern Limit *make_limit(Plan *lefttree, Node *limitOffset, Node *limitCount);
+extern Asof *make_asof(Plan *lefttree, Node *asofTimeout);
/*
* prototypes for plan/initsplan.c
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index 1d205c6..d0d3681 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -25,6 +25,7 @@ extern Node *transformWhereClause(ParseState *pstate, Node *clause,
ParseExprKind exprKind, const char *constructName);
extern Node *transformLimitClause(ParseState *pstate, Node *clause,
ParseExprKind exprKind, const char *constructName);
+extern Node *transformAsofClause(ParseState *pstate, Node *clause);
extern List *transformGroupClause(ParseState *pstate, List *grouplist,
List **groupingSets,
List **targetlist, List *sortClause,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 565bb3d..46e9c0c 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -68,7 +68,8 @@ typedef enum ParseExprKind
EXPR_KIND_TRIGGER_WHEN, /* WHEN condition in CREATE TRIGGER */
EXPR_KIND_POLICY, /* USING or WITH CHECK expr in policy */
EXPR_KIND_PARTITION_EXPRESSION, /* PARTITION BY expression */
- EXPR_KIND_CALL /* CALL argument */
+ EXPR_KIND_CALL, /* CALL argument */
+ EXPR_KIND_ASOF /* AS OF */
} ParseExprKind;
diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h
index bf51977..a00f0d9 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -111,6 +111,7 @@ typedef struct SnapshotData
pairingheap_node ph_node; /* link in the RegisteredSnapshots heap */
TimestampTz whenTaken; /* timestamp when snapshot was taken */
+ TimestampTz asofTimestamp; /* select AS OF timestamp */
XLogRecPtr lsn; /* position in the WAL stream when taken */
} SnapshotData;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index fa4d573..2b65848 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -6513,6 +6513,7 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
query->sortClause ||
query->limitOffset ||
query->limitCount ||
+ query->asofTimestamp ||
query->setOperations)
return;