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;
 

Reply via email to