I wonder if Postgres community is interested in supporting time travel queries in PostgreSQL (something like AS OF queries in Oracle: https://docs.oracle.com/cd/B14117_01/appdev.101/b10795/adfns_fl.htm).
As far as I know something similar is now developed for MariaDB.

It seems to me that it will be not so difficult to implement them in Postgres - we already have versions of tuples.
Looks like we only need to do three things:
1. Disable autovacuum (autovacuum = off)
2. Enable commit timestamp (track_commit_timestamp = on)
3. Add asofTimestamp to snapshot and patch XidInMVCCSnapshot to compare commit timestamps when it is specified in snapshot.


Attached please find my prototype implementation of it.
Most of the efforts are needed to support asof timestamp in grammar and add it to query plan. I failed to support AS OF clause (as in Oracle) because of shift-reduce conflicts with aliases, so I have to introduce new ASOF keyword. May be yacc experts can propose how to solve this conflict without introducing new keyword...

Please notice that now ASOF timestamp is used only for data snapshot, not for catalog snapshot. I am not sure that it is possible (and useful) to travel through database schema history...

Below is an example of how it works:

postgres=# create table foo(pk serial primary key, ts timestamp default now(), val text);
CREATE TABLE
postgres=# insert into foo (val) values ('insert');
INSERT 0 1
postgres=# insert into foo (val) values ('insert');
INSERT 0 1
postgres=# insert into foo (val) values ('insert');
INSERT 0 1
postgres=# select * from foo;
 pk |             ts             |  val
----+----------------------------+--------
  1 | 2017-12-20 14:59:17.715453 | insert
  2 | 2017-12-20 14:59:22.933753 | insert
  3 | 2017-12-20 14:59:27.87712  | insert
(3 rows)

postgres=# select * from foo asof timestamp '2017-12-20 14:59:25';
 pk |             ts             |  val
----+----------------------------+--------
  1 | 2017-12-20 14:59:17.715453 | insert
  2 | 2017-12-20 14:59:22.933753 | insert
(2 rows)

postgres=# select * from foo asof timestamp '2017-12-20 14:59:20';
 pk |             ts             |  val
----+----------------------------+--------
  1 | 2017-12-20 14:59:17.715453 | insert
(1 row)

postgres=# update foo set val='upd',ts=now() where pk=1;
UPDATE 1
postgres=# select * from foo asof timestamp '2017-12-20 14:59:20';
 pk |             ts             |  val
----+----------------------------+--------
  1 | 2017-12-20 14:59:17.715453 | insert
(1 row)

postgres=# select * from foo;
 pk |             ts             |  val
----+----------------------------+--------
  2 | 2017-12-20 14:59:22.933753 | insert
  3 | 2017-12-20 14:59:27.87712  | insert
  1 | 2017-12-20 15:09:17.046047 | upd
(3 rows)

postgres=# update foo set val='upd2',ts=now() where pk=1;
UPDATE 1
postgres=# select * from foo asof timestamp '2017-12-20 15:10';
 pk |             ts             |  val
----+----------------------------+--------
  2 | 2017-12-20 14:59:22.933753 | insert
  3 | 2017-12-20 14:59:27.87712  | insert
  1 | 2017-12-20 15:09:17.046047 | upd
(3 rows)


Comments and feedback are welcome:)

--
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..d28755b 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; }
@@ -15311,6 +15318,7 @@ reserved_keyword:
 			| ARRAY
 			| AS
 			| ASC
+			| ASOF
 			| ASYMMETRIC
 			| BOTH
 			| CASE
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/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/kwlist.h b/src/include/parser/kwlist.h
index a932400..adaa71d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -45,6 +45,7 @@ PG_KEYWORD("any", ANY, RESERVED_KEYWORD)
 PG_KEYWORD("array", ARRAY, RESERVED_KEYWORD)
 PG_KEYWORD("as", AS, RESERVED_KEYWORD)
 PG_KEYWORD("asc", ASC, RESERVED_KEYWORD)
+PG_KEYWORD("asof", ASOF, RESERVED_KEYWORD)
 PG_KEYWORD("assertion", ASSERTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("assignment", ASSIGNMENT, UNRESERVED_KEYWORD)
 PG_KEYWORD("asymmetric", ASYMMETRIC, RESERVED_KEYWORD)
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