WIP: System Versioned Temporal Table

Started by Surafel Temesgenabout 6 years ago97 messages
#1Surafel Temesgen
surafel3000@gmail.com
1 attachment(s)

Hi all ,

Temporal table is one of the main new features added in sql standard 2011.
From that I will like to implement system versioned temporal table which
allows to keep past and present data so old data can be queried. Am propose
to implement it like below

CREATE

In create table only one table is create and both historical and current
data will be store in it. In order to make history and current data
co-exist row end time column will be added implicitly to primary key.
Regarding performance one can partition the table by row end time column
order to make history data didn't slowed performance.

INSERT

In insert row start time column and row end time column behave like a kind
of generated stored column except they store current transaction time and
highest value supported by the data type which is +infinity respectively.

DELETE and UPDATE

The old data is inserted with row end time column seated to current
transaction time

SELECT

If the query didn’t contain a filter condition that include system time
column, a filter condition will be added in early optimization that filter
history data.

Attached is WIP patch that implemented just the above and done on top of
commit b8e19b932a99a7eb5a. Temporal clause didn’t implemented yet so one
can use regular filter condition for the time being

NOTE: I implement sql standard syntax except it is PERIOD FOR SYSTEM TIME
rather than PERIOD FOR SYSTEM_TIME in CREATE TABLE statement and system
time is not selected unless explicitly asked

Any enlightenment?

regards

Surafel

Attachments:

WIP_system_version_temp_table.patchtext/x-patch; charset=US-ASCII; name=WIP_system_version_temp_table.patchDownload
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 6bc4e4c036..c229c90e2d 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -864,6 +867,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned= false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c9d024ead5..340f0340bd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -330,6 +330,120 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time in row start time columns for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end  time columns for a tuple because row end time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamp_in,
+					CStringGetDatum("infinity"),
+					ObjectIdGetDatum(InvalidOid),
+					Int32GetDatum(-1));
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+			values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time in row end time columns for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}else
+			values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -430,6 +544,12 @@ ExecInsert(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
 		/*
 		 * Check any RLS WITH CHECK policies.
 		 *
@@ -756,6 +876,31 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end  time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *oslot = NULL;
+
+			oslot = table_slot_create(resultRelationDesc, NULL);
+
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, SnapshotAny,
+										   oslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, oslot);
+				table_tuple_insert(resultRelationDesc, oslot,
+						   estate->es_output_cid,
+						   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(oslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1128,6 +1273,31 @@ ExecUpdate(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot);
 
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *oslot = NULL;
+
+			oslot = table_slot_create(resultRelationDesc, NULL);
+
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, SnapshotAny,
+											   oslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, oslot);
+				table_tuple_insert(resultRelationDesc, oslot,
+						   estate->es_output_cid,
+						   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(oslot);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..d81c4c8965 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -4746,6 +4746,16 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime *from)
+{
+	RowTime *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5634,6 +5644,9 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 18cb014373..cb6f59e4fa 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2911,6 +2911,15 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime *a, const RowTime *b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3730,6 +3739,9 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 17c5f086fb..198f061502 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -57,6 +57,8 @@
 #include "parser/analyze.h"
 #include "parser/parsetree.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/dsm_impl.h"
@@ -249,6 +251,9 @@ static bool group_by_has_partkey(RelOptInfo *input_rel,
 								 List *targetList,
 								 List *groupClause);
 static int	common_prefix_cmp(const void *a, const void *b);
+static bool check_system_versioned_columen( Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table( RangeTblEntry *rte);
+char * row_end_time_column_name(RangeTblEntry *rte);
 
 
 /*****************************************************************************
@@ -745,6 +750,67 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 											list_length(rte->securityQuals));
 	}
 
+	if (parse->commandType == CMD_SELECT)
+	{
+		foreach(l, parse->rtable)
+		{
+
+			RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+			if(!check_system_versioned_table(rte) ||
+				check_system_versioned_columen(parse->jointree->quals, rte))
+			{
+				continue;
+			}
+			else
+			{
+				Node	   *wClause;
+				ParseState *pstate;
+				Relation    relation;
+				char *endColNme;
+				ColumnRef  *c;
+				A_Const *n ;
+
+				endColNme = row_end_time_column_name(rte);
+
+				c = makeNode(ColumnRef);
+				c->location = 0;
+				c->fields = lcons(makeString(endColNme), NIL);
+
+				n = makeNode(A_Const);
+				n->val.type = T_String;
+				n->val.val.str = "infinity";
+				n->location = 0;
+
+				wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *)n , 0);
+				relation = heap_open(rte->relid, NoLock);
+
+				/*
+				 * Create a dummy ParseState and insert the target relation as its sole
+				 * rangetable entry.  We need a ParseState for transformExpr.
+				 */
+				pstate = make_parsestate(NULL);
+				rte = addRangeTableEntryForRelation(pstate,
+						relation,
+						AccessShareLock,
+						NULL,
+						false,
+						true);
+				addRTEtoQuery(pstate, rte, false, true, true);
+				wClause = transformWhereClause(pstate,
+						wClause,
+						EXPR_KIND_WHERE,
+						"WHERE");
+
+				if (parse->jointree->quals !=  NULL)
+					parse->jointree->quals =make_and_qual(parse->jointree->quals, wClause);
+				else
+					parse->jointree->quals = wClause;
+			}
+
+		}
+	}
+
 	/*
 	 * Preprocess RowMark information.  We need to do this after subquery
 	 * pullup, so that all base relations are present.
@@ -7404,3 +7470,87 @@ group_by_has_partkey(RelOptInfo *input_rel,
 
 	return true;
 }
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_columen_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	else if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result  = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			( result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+		else
+			return false;
+	}
+	else
+		return expression_tree_walker(node, check_system_versioned_columen_walker,
+				rte);
+}
+
+static bool
+check_system_versioned_columen( Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_columen_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation    rel;
+	TupleDesc	tupdesc;
+	bool        result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = heap_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	heap_close(rel, NoLock);
+
+	return result;
+}
+
+char *
+row_end_time_column_name(RangeTblEntry *rte)
+{
+	Relation    relation;
+	TupleDesc	tupdesc;
+	char*	name;
+	int			natts;
+
+	relation = heap_open(rte->relid, NoLock);
+
+	tupdesc = RelationGetDescr(relation);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	heap_close(relation, NoLock);
+
+	return name;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..8a7673342c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -127,6 +127,20 @@ typedef struct ImportQual
 	List	   *table_names;
 } ImportQual;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -242,6 +256,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionSpec		*partspec;
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -373,12 +389,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	import_qualification_type
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
+				distinct_clause opt_all_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -539,7 +557,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -667,7 +685,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -693,7 +711,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -3139,12 +3157,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3158,12 +3177,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3177,13 +3197,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3197,13 +3218,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3217,13 +3239,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3237,13 +3260,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3320,6 +3344,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3416,6 +3441,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR SYSTEM_P TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $6;
+					n->end_time = $8;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3498,12 +3533,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3521,6 +3556,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3543,6 +3579,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3922,9 +3982,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4060,7 +4145,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -15221,6 +15306,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15326,6 +15412,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4dd81507a7..480b71edfe 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2621,6 +2621,13 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 			continue;
 		}
 
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME || attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			if (aliascell)
+				aliascell = lnext(eref->colnames, aliascell);
+			continue;
+		}
+
 		if (colnames)
 		{
 			char	   *label;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee47547624..52fe6ce770 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -71,6 +71,8 @@
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
+#include<string.h>
+
 
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
@@ -96,6 +98,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSyetemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;		/* name of row start time column */
+	char	   *endTimeColName;		/* name of row end time column */
+	char	   *periodStart;		/* name of period start column */
+	char	   *periodEnd;		/* name of period end column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +126,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumen(CreateStmtContext *cxt,
+									 RowTime *cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -249,6 +258,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSyetemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -284,14 +297,15 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumen(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
 				break;
 		}
 	}
-
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -303,6 +317,20 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	if (cxt.isSyetemVersioned)
+	{
+		ListCell   *lc;
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if(constraint->contype == CONSTR_PRIMARY && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -716,6 +744,38 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				if (strcmp(strVal ( list_nth(column->typeName->names, 1)), "timestamp") != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the data type of row start time must be timestamp")));
+
+				if (cxt->startTimeColName)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("row start time can not be specified multiple time")));
+
+				column->generated = ATTRIBUTE_ROW_START_TIME;
+				cxt->startTimeColName = column->colname;
+				cxt->isSyetemVersioned = true;
+				break;
+
+			case CONSTR_ROW_END_TIME:
+				if (strcmp(strVal ( list_nth(column->typeName->names, 1)), "timestamp") != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the data type of row end time must be timestamp")));
+
+				if (cxt->endTimeColName)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("row end time can not be specified multiple time")));
+
+
+				column->generated = ATTRIBUTE_ROW_END_TIME;
+				cxt->endTimeColName = column->colname;
+				break;
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1267,6 +1327,27 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	table_close(relation, NoLock);
 }
 
+/*
+ * transformPeriodColumen
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumen(CreateStmtContext *cxt, RowTime *col)
+{
+	cxt->periodStart  = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period start time parameter must equal the name of row start time column")));
+
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period end time  parameter must equal the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 585dcee5db..14a9ccc988 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -515,6 +515,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned= false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -569,6 +570,8 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME)
+			constr->is_system_versioned= true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index a06800555c..f68fbb45f5 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 04004b5703..f8ea8bff7c 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -206,6 +206,9 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 891b119608..26d894a5cc 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,8 @@
 #include "nodes/execnodes.h"
 
 extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot);
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bce2d59b0d..a91a58fc26 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -477,6 +477,7 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..f93f677757 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -656,6 +656,8 @@ typedef struct ColumnDef
 	RangeVar   *identitySequence;	/* to store identity sequence name for
 									 * ALTER TABLE ... ADD COLUMN */
 	char		generated;		/* attgenerated setting */
+	char		starttime;		/* row star time */
+	char		endtime;		/* row end time */
 	CollateClause *collClause;	/* untransformed COLLATE spec, if any */
 	Oid			collOid;		/* collation OID (InvalidOid if not set) */
 	List	   *constraints;	/* other constraints on column */
@@ -2059,6 +2061,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned; /* true when its is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2108,7 +2111,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3533,4 +3538,11 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	  *start_time;		/* Row start time */
+	char	 *end_time;		/* Row end time */
+} RowTime;
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..8215443091 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -299,6 +299,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -439,6 +440,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD)
#2Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Surafel Temesgen (#1)
Re: WIP: System Versioned Temporal Table

On 23/10/2019 17:56, Surafel Temesgen wrote:

Hi all ,

Temporal table is one of the main new features added in sql standard
2011. From that I will like to implement system versioned temporal
table which allows to keep past and present data so old data can be
queried.

Excellent!  I've been wanting this feature for a long time now.  We're
the last major database to not have it.

I tried my hand at doing it in core, but ended up having better success
at an extension: https://github.com/xocolatl/periods/

Am propose to implement it like below

CREATE

In create table only one table is create and both historical and
current data will be store in it. In order to make history and current
data co-exist row end time column will be added implicitly to primary
key. Regarding performance one can partition the table by row end time
column order to make history data didn't slowed performance.

If we're going to be implicitly adding stuff to the PK, we also need to
add that stuff to the other unique constraints, no?  And I think it
would be better to add both the start and the end column to these keys. 
Most of the temporal queries will be accessing both.

INSERT

In insert row start time column and row end time column behave like a
kind of generated stored column except they store current transaction
time and highest value supported by the data type which is +infinity
respectively.

You're forcing these columns to be timestamp without time zone.  If
you're going to force a datatype here, it should absolutely be timestamp
with time zone.  However, I would like to see it handle both kinds of
timestamps as well as a simple date.

DELETE and UPDATE

The old data is inserted with row end time column seated to current
transaction time

I don't see any error handling for transaction anomalies.  In READ
COMMITTED, you can easily end up with a case where the end time comes
before the start time.  I don't even see anything constraining start
time to be strictly inferior to the end time.  Such a constraint will be
necessary for application-time periods (which your patch doesn't address
at all but that's okay).

SELECT

If the query didn’t contain a filter condition that include system
time column, a filter condition will be added in early optimization
that filter history data.

Attached is WIP patch that implemented just the above and done on top
of commit b8e19b932a99a7eb5a. Temporal clause didn’t implemented yet
so one can use regular filter condition for the time being

NOTE: I implement sql standard syntax except it is PERIOD FOR SYSTEM
TIME rather than PERIOD FOR SYSTEM_TIME in CREATE TABLE statement and
system time is not selected unless explicitly asked

Why aren't you following the standard syntax here?

Any enlightenment?

There are quite a lot of typos and other things that aren't written "the
Postgres way". But before I comment on any of that, I'd like to see the
features be implemented correctly according to the SQL standard.

#3Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#2)
Re: WIP: System Versioned Temporal Table

hi Vik,
On Wed, Oct 23, 2019 at 9:02 PM Vik Fearing <vik.fearing@2ndquadrant.com>
wrote:

If we're going to be implicitly adding stuff to the PK, we also need to
add that stuff to the other unique constraints, no? And I think it
would be better to add both the start and the end column to these keys.
Most of the temporal queries will be accessing both.

yes it have to be added to other constraint too but adding both system time
to PK will violate constraint because it allow multiple data in current
data

Why aren't you following the standard syntax here?

because we do have TIME and SYSTEM_P as a key word and am not sure of
whether
its a right thing to add other keyword that contain those two word
concatenated

Any enlightenment?

There are quite a lot of typos and other things that aren't written "the
Postgres way". But before I comment on any of that, I'd like to see the
features be implemented correctly according to the SQL standard.

it is almost in sql standard syntax except the above small difference. i
can correct it
and post more complete patch soon.

regards
Surafel

#4Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Surafel Temesgen (#3)
Re: WIP: System Versioned Temporal Table

On 24/10/2019 16:54, Surafel Temesgen wrote:

hi Vik,
On Wed, Oct 23, 2019 at 9:02 PM Vik Fearing
<vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>> wrote:
 

If we're going to be implicitly adding stuff to the PK, we also
need to
add that stuff to the other unique constraints, no?  And I think it
would be better to add both the start and the end column to these
keys. 
Most of the temporal queries will be accessing both.

 
yes it have to be added to other constraint too but adding both system
time 
to PK will violate constraint because it allow multiple data in
current data

I don't understand what you mean by this.

 

Why aren't you following the standard syntax here?

because we do have TIME and SYSTEM_P as a key word and am not sure of
whether
its a right thing to add other keyword that contain those two word
concatenated

Yes, we have to do that.

 
 

Any enlightenment?

There are quite a lot of typos and other things that aren't
written "the
Postgres way". But before I comment on any of that, I'd like to
see the
features be implemented correctly according to the SQL standard.

it is almost in sql standard syntax except the above small difference.
i can correct it 
and post more complete patch soon.

I don't mean just the SQL syntax, I also mean the behavior.

#5Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#4)
Re: WIP: System Versioned Temporal Table

On Thu, Oct 24, 2019 at 6:49 PM Vik Fearing <vik.fearing@2ndquadrant.com>
wrote:

On 24/10/2019 16:54, Surafel Temesgen wrote:

hi Vik,
On Wed, Oct 23, 2019 at 9:02 PM Vik Fearing
<vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>>

wrote:

If we're going to be implicitly adding stuff to the PK, we also
need to
add that stuff to the other unique constraints, no? And I think it
would be better to add both the start and the end column to these
keys.
Most of the temporal queries will be accessing both.

yes it have to be added to other constraint too but adding both system
time
to PK will violate constraint because it allow multiple data in
current data

I don't understand what you mean by this.

The primary purpose of adding row end time to primary key is to allow
duplicate value to be inserted into a table with keeping constraint in
current data but it can be duplicated in history data. Adding row start
time column to primary key will eliminate this uniqueness for current data
which is not correct

regards
Surafel

#6Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Surafel Temesgen (#5)
Re: WIP: System Versioned Temporal Table

On 25/10/2019 11:56, Surafel Temesgen wrote:

On Thu, Oct 24, 2019 at 6:49 PM Vik Fearing
<vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>> wrote:

On 24/10/2019 16:54, Surafel Temesgen wrote:

hi Vik,
On Wed, Oct 23, 2019 at 9:02 PM Vik Fearing
<vik.fearing@2ndquadrant.com

<mailto:vik.fearing@2ndquadrant.com>
<mailto:vik.fearing@2ndquadrant.com
<mailto:vik.fearing@2ndquadrant.com>>> wrote:

 

     If we're going to be implicitly adding stuff to the PK, we also
     need to
     add that stuff to the other unique constraints, no?  And I

think it

     would be better to add both the start and the end column to

these

     keys. 
     Most of the temporal queries will be accessing both.

 
yes it have to be added to other constraint too but adding both

system

time 
to PK will violate constraint because it allow multiple data in
current data

I don't understand what you mean by this.

The primary purpose of adding row end time to primary key is to allow
duplicate value to be inserted into a table with keeping constraint in
current data but it can be duplicated in history data. Adding row
start time column to primary key will eliminate this uniqueness for
current data which is not correct  

How?  The primary/unique keys must always be unique at every point in time.

#7Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#6)
Re: WIP: System Versioned Temporal Table

On Fri, Oct 25, 2019 at 10:45 PM Vik Fearing <vik.fearing@2ndquadrant.com>
wrote:

I don't understand what you mean by this.

The primary purpose of adding row end time to primary key is to allow
duplicate value to be inserted into a table with keeping constraint in
current data but it can be duplicated in history data. Adding row
start time column to primary key will eliminate this uniqueness for
current data which is not correct

How? The primary/unique keys must always be unique at every point in time.

From user prospect it is acceptable to delete and reinsert a record with
the same key value multiple time which means there will be multiple record
with the same key value in a history data but there is only one values in
current data as a table without system versioning do .I add row end time
column to primary key to allow user supplied primary key values to be
duplicated in history data which is acceptable

regards
Surafel

#8Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Surafel Temesgen (#7)
Re: WIP: System Versioned Temporal Table

On 28/10/2019 13:48, Surafel Temesgen wrote:

On Fri, Oct 25, 2019 at 10:45 PM Vik Fearing
<vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>> wrote:

     I don't understand what you mean by this.

The primary purpose of adding row end time to primary key is to

allow

duplicate value to be inserted into a table with keeping

constraint in

current data but it can be duplicated in history data. Adding row
start time column to primary key will eliminate this uniqueness for
current data which is not correct  

How?  The primary/unique keys must always be unique at every point
in time.

From user prospect it is acceptable to delete and reinsert a record
with the same key value multiple time which means there will be
multiple record with the same key value in a history data but there is
only one values in current data as a table without system versioning
do .I add row end time column to primary key to allow user supplied
primary key values to be duplicated in history data which is acceptable

Yes, I understand that.  I'm saying you should also add the row start
time column.

#9Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#8)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

Hi,
Attached is a complete patch and also contain a fix for your comments

regards
Surafel

Attachments:

0001-system-versioned-temporal-table.patchtext/x-patch; charset=US-ASCII; name=0001-system-versioned-temporal-table.patchDownload
From 0a1e51b6fcce03014e3ee355d42443add4da7a28 Mon Sep 17 00:00:00 2001
From: Surafel Temesgen <surafel3000@gmail.com>
Date: Wed, 1 Jan 2020 13:27:25 +0300
Subject: [PATCH] system versioned temporal table

---
 doc/src/sgml/ref/alter_table.sgml             |  23 ++
 doc/src/sgml/ref/create_table.sgml            |  47 ++++
 doc/src/sgml/ref/select.sgml                  |  37 +++
 src/backend/access/common/tupdesc.c           |   4 +
 src/backend/commands/copy.c                   |   5 +
 src/backend/commands/tablecmds.c              |  49 +++-
 src/backend/commands/view.c                   |   6 +
 src/backend/executor/nodeModifyTable.c        | 180 ++++++++++++++
 src/backend/nodes/copyfuncs.c                 |  31 +++
 src/backend/nodes/equalfuncs.c                |  28 +++
 src/backend/nodes/makefuncs.c                 | 120 ++++++++++
 src/backend/nodes/outfuncs.c                  |   1 +
 src/backend/optimizer/plan/planner.c          |   8 +
 src/backend/optimizer/plan/subselect.c        |  19 ++
 src/backend/optimizer/util/plancat.c          | 188 +++++++++++++++
 src/backend/parser/analyze.c                  |   9 +
 src/backend/parser/gram.y                     | 220 +++++++++++++-----
 src/backend/parser/parse_clause.c             |  73 +++++-
 src/backend/parser/parse_relation.c           |   4 +
 src/backend/parser/parse_utilcmd.c            | 133 ++++++++++-
 src/backend/tcop/utility.c                    |  61 +++++
 src/backend/utils/cache/relcache.c            |   3 +
 src/bin/pg_dump/pg_dump.c                     |   4 +
 src/bin/psql/describe.c                       |   6 +-
 src/include/access/tupdesc.h                  |   1 +
 src/include/catalog/pg_attribute.h            |   3 +
 src/include/executor/nodeModifyTable.h        |   2 +
 src/include/nodes/makefuncs.h                 |   6 +
 src/include/nodes/nodes.h                     |   2 +
 src/include/nodes/parsenodes.h                |  42 +++-
 src/include/optimizer/plancat.h               |   4 +-
 src/include/parser/kwlist.h                   |   3 +
 src/include/parser/parse_node.h               |   2 +-
 .../expected/system_versioned_table.out       | 188 +++++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/serial_schedule              |   1 +
 .../regress/sql/system_versioned_table.sql    | 104 +++++++++
 37 files changed, 1553 insertions(+), 66 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 8403c797e2..6182cda9cb 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -158,6 +160,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form adds system versioning columns to the table, using default column
+      name of system versioning which is StartTime and EndtTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -177,6 +189,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning columns from a table.  Indexes and
+      table constraints involving the columns will be automatically
+      dropped as well.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 4a2b6f0dae..cd1035b8d9 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -861,6 +865,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -953,6 +979,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1208,6 +1244,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 691e402803..ed228b781c 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,9 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME AS OF ] <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME BETWEEN ] <replaceable class="parameter">start_time</replaceable> [AND] <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME FROM ] <replaceable class="parameter">start_time</replaceable> [TO] <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -534,6 +537,40 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable>  [AND] <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between 
+        <replaceable class="parameter">start_time</replaceable> and 
+        <replaceable class="parameter">end_time</replaceable> including 
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable>  [TO] <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between 
+        <replaceable class="parameter">start_time</replaceable> and 
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 6bc4e4c036..bacd78deb5 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -864,6 +867,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 42a147b67d..2b6b5228bb 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3223,6 +3223,11 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
 					ExecComputeStoredGenerated(estate, myslot);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5b882f80bf..081682219b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -76,6 +76,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -1514,6 +1515,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1522,6 +1524,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3584,6 +3594,7 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
 				cmd_lockmode = AccessExclusiveLock;
@@ -3614,6 +3625,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -6123,6 +6135,12 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME || attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -6500,6 +6518,12 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME || attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -6600,6 +6624,12 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME || attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -6846,6 +6876,12 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME || attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -10624,6 +10660,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME || attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -11607,6 +11648,12 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME || atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15079,7 +15126,7 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME && attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 9b5148093b..27559300eb 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -420,6 +421,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * check and filter out historical data if necessary.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 9ba1d78344..1502a59de0 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -330,6 +330,129 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time in row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamp_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time in row end time columns for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -429,6 +552,13 @@ ExecInsert(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
 		/*
 		 * Check any RLS WITH CHECK policies.
 		 *
@@ -755,6 +885,31 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *sslot = NULL;
+
+			sslot = table_slot_create(resultRelationDesc, NULL);
+
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, SnapshotAny,
+											   sslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, sslot);
+				table_tuple_insert(resultRelationDesc, sslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(sslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1127,6 +1282,31 @@ ExecUpdate(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot);
 
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *sslot = NULL;
+
+			sslot = table_slot_create(resultRelationDesc, NULL);
+
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, SnapshotAny,
+											   sslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, sslot);
+				table_tuple_insert(resultRelationDesc, sslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(sslot);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a9b8b84b8f..4af36a1688 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3354,6 +3354,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4752,6 +4753,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5640,6 +5665,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2fcd4a3467..0578b24bf3 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1245,6 +1245,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2914,6 +2915,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3733,6 +3755,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 18466ac568..b454e7ab44 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -809,3 +809,123 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	Node	   *lexp = lexpr;
+
+	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
+	while (IsA(lexp, A_Expr) &&
+		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
+		lexp = ((A_Expr *) lexp)->lexpr;
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexp, BoolExpr))
+	{
+		BoolExpr   *blexpr = (BoolExpr *) lexp;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = 0;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeConstraint -
+ *       create a constraint node
+ */
+Constraint *
+makeConstraint(ConstrType type)
+{
+	Constraint *c = makeNode(Constraint);
+
+	c->contype = type;
+	c->raw_expr = NULL;
+	c->cooked_expr = NULL;
+	c->location = 1;
+
+	return c;
+}
+
+/*
+ * makeSystemColumnDef -
+ *       create a ColumnDef node for system column
+ */
+ColumnDef *
+makeSystemColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) makeConstraint(CONSTR_ROW_START_TIME));
+	}
+	else
+	{
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) makeConstraint(CONSTR_ROW_END_TIME));
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamp")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = 1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ac02e5ec8d..8efd8ce356 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2599,6 +2599,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index cb54b15507..68ac22b215 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -645,6 +647,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	if (parse->cteList)
 		SS_process_ctes(root);
 
+	/*
+	 * Check and filter out historical data if necessary.
+	 */
+	if (parse->commandType == CMD_SELECT)
+		add_history_data_filter(parse);
+
 	/*
 	 * If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so
 	 * that we don't need so many special cases to deal with that situation.
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 48b62a55de..55f2465cf3 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -27,6 +27,7 @@
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
+#include "optimizer/plancat.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
@@ -850,6 +851,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * check and filter out historical data if necessary.
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -896,6 +906,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * check and filter out historical data if necessary.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 5e889d1861..c1dfd04265 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,6 +34,7 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
@@ -41,6 +42,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -79,6 +81,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2342,3 +2346,187 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			ParseState *pstate;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = 0;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			rte = addRangeTableEntryForRelation(pstate,
+												relation,
+												AccessShareLock,
+												NULL,
+												false,
+												true);
+			addRTEtoQuery(pstate, rte, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+
+			if (query->jointree->quals != NULL)
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			else
+				query->jointree->quals = wClause;
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	else if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+		else
+			return false;
+	}
+	else
+		return expression_tree_walker(node, check_system_versioned_column_walker,
+									  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0656279654..d5bb72c7e8 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1228,6 +1228,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c5086846de..062bf984f7 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -127,6 +127,20 @@ typedef struct ImportQual
 	List	   *table_names;
 } ImportQual;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -170,7 +184,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -242,6 +255,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionSpec		*partspec;
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -374,12 +390,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	import_qualification_type
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
+				distinct_clause opt_all_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -433,7 +451,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 <list>	for_clause for_locking_clause opt_for_locking_clause for_locking_items
 %type <list>	locked_rels_list
 %type <boolean>	all_or_distinct
 
@@ -500,7 +518,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -541,7 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -669,7 +687,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -684,7 +702,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -695,7 +713,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -2101,7 +2119,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2242,6 +2268,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3141,12 +3176,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3160,12 +3196,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3179,13 +3216,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3199,13 +3237,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3219,13 +3258,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3239,13 +3279,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3322,6 +3363,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3418,6 +3460,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3500,12 +3552,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3523,6 +3575,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3545,6 +3598,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3924,9 +4001,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4062,7 +4164,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11321,7 +11423,7 @@ select_no_parens:
 										yyscanner);
 					$$ = $1;
 				}
-			| select_clause opt_sort_clause for_locking_clause opt_select_limit
+			| select_clause opt_sort_clause for_clause opt_select_limit
 				{
 					insertSelectOptions((SelectStmt *) $1, $2, $3,
 										list_nth($4, 0), list_nth($4, 1),
@@ -11353,7 +11455,7 @@ select_no_parens:
 										yyscanner);
 					$$ = $2;
 				}
-			| with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit
+			| with_clause select_clause opt_sort_clause for_clause opt_select_limit
 				{
 					insertSelectOptions((SelectStmt *) $2, $3, $4,
 										list_nth($5, 0), list_nth($5, 1),
@@ -11833,11 +11935,15 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
+		;
+
+for_clause:
+			FOR for_locking_clause						{ $$ = $2; }
 			| FOR READ ONLY							{ $$ = NIL; }
 		;
 
 opt_for_locking_clause:
-			for_locking_clause						{ $$ = $1; }
+			for_clause						{ $$ = $1; }
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
@@ -11858,10 +11964,10 @@ for_locking_item:
 		;
 
 for_locking_strength:
-			FOR UPDATE 							{ $$ = LCS_FORUPDATE; }
-			| FOR NO KEY UPDATE 				{ $$ = LCS_FORNOKEYUPDATE; }
-			| FOR SHARE 						{ $$ = LCS_FORSHARE; }
-			| FOR KEY SHARE 					{ $$ = LCS_FORKEYSHARE; }
+			 UPDATE 							{ $$ = LCS_FORUPDATE; }
+			| NO KEY UPDATE 				{ $$ = LCS_FORNOKEYUPDATE; }
+			| SHARE 						{ $$ = LCS_FORSHARE; }
+			| KEY SHARE 					{ $$ = LCS_FORKEYSHARE; }
 		;
 
 locked_rels_list:
@@ -11900,7 +12006,7 @@ values_clause:
  *****************************************************************************/
 
 from_clause:
-			FROM from_list							{ $$ = $2; }
+			FROM from_list							{ $$ = $2 ; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
@@ -11925,6 +12031,11 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+			| FOR relation_expr temporal_clause
+				{
+					$3->relation = (Node *)$2;
+					$$ = (Node *)$3;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -12023,7 +12134,28 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
-
+temporal_clause:  SYSTEM_TIME AS OF b_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = $4;
+				}
+			| SYSTEM_TIME BETWEEN b_expr AND b_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_SYMMETRIC;
+					$$->from = $3;
+					$$->to = $5;
+				}
+			| SYSTEM_TIME FROM b_expr TO b_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = $3;
+					$$->to = $5;
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15284,6 +15416,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15360,6 +15493,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15389,6 +15523,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16083,29 +16218,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	Node	   *lexp = lexpr;
-
-	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
-	while (IsA(lexp, A_Expr) &&
-		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
-		lexp = ((A_Expr *) lexp)->lexpr;
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexp, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexp;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ebbba2d7b5..5cd98b0f57 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -101,6 +102,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void addTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1082,7 +1084,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		/* if not found above, must be a table reference */
 		if (!rte)
 			rte = transformTableEntry(pstate, rv);
-
 		/* assume new rte is at end */
 		rtindex = list_length(pstate->p_rtable);
 		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
@@ -1174,6 +1175,31 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return (Node *) rtr;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		RangeTblEntry *rte;
+		int			rtindex;
+
+		rte = transformTableEntry(pstate, rv);
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		addTempToWhereClause(pstate, tc, rte);
+		/* assume new rte is at end */
+		rtindex = list_length(pstate->p_rtable);
+		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
+		*top_rte = rte;
+		*top_rti = rtindex;
+		*namespace = list_make1(makeDefaultNSItem(rte, rtindex));
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3651,3 +3677,48 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		add temporal clause specification to where clause.
+ */
+static void
+addTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause;
+	Node	   *tClause;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4888311f44..1251dc80c9 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1270,6 +1270,7 @@ addRangeTableEntry(ParseState *pstate,
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
+	TupleDesc	tupdesc;
 
 	Assert(pstate != NULL);
 
@@ -1294,6 +1295,9 @@ addRangeTableEntry(ParseState *pstate,
 	rte->relkind = rel->rd_rel->relkind;
 	rte->rellockmode = lockmode;
 
+	tupdesc = RelationGetDescr(rel);
+	rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+
 	/*
 	 * Build the list of effective column names using user-supplied aliases
 	 * and/or actual column names.
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 45bb31ecf8..c7fca3cbfb 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -71,6 +72,7 @@
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
+#include <string.h>
 
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
@@ -96,6 +98,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start column */
+	char	   *periodEnd;		/* name of period end column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +126,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -249,6 +258,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -284,7 +297,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -292,6 +307,27 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column  and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeSystemColumnDef("StartTime");
+		endCol = makeSystemColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -303,6 +339,25 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history data and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -716,6 +771,40 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				if (strcmp(strVal(list_nth(column->typeName->names, 1)), "timestamp") != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the data type of row start time must be timestamp")));
+
+				if (cxt->startTimeColName)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("row start time can not be specified multiple time")));
+
+				column->generated = ATTRIBUTE_ROW_START_TIME;
+				cxt->startTimeColName = column->colname;
+				cxt->isSystemVersioned = true;
+				column->is_not_null = true;
+				break;
+
+			case CONSTR_ROW_END_TIME:
+				if (strcmp(strVal(list_nth(column->typeName->names, 1)), "timestamp") != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the data type of row end time must be timestamp")));
+
+				if (cxt->endTimeColName)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("row end time can not be specified multiple time")));
+
+
+				column->generated = ATTRIBUTE_ROW_END_TIME;
+				cxt->endTimeColName = column->colname;
+				column->is_not_null = true;
+				break;
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1267,6 +1356,27 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	table_close(relation, NoLock);
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period start time parameter must equal the name of row start time column")));
+
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period end time  parameter must equal the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3076,6 +3186,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -3119,6 +3233,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) && (constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3a03ca7e2f..7c618ee3ba 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -58,7 +59,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1094,6 +1097,9 @@ ProcessUtilitySlow(ParseState *pstate,
 					Oid			relid;
 					List	   *stmts;
 					ListCell   *l;
+					ListCell   *s;
+					Relation	rel;
+
 					LOCKMODE	lockmode;
 
 					/*
@@ -1105,6 +1111,61 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE statment
+							 */
+							startTimeCol = makeSystemColumnDef("StartTime");
+							endTimeCol = makeSystemColumnDef("EndTime");
+
+							/*
+							 * create alter table cmd and append to the ende
+							 * of commands and remove current listCell because
+							 * we don't want it anymore.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						/* Run parse analysis ... */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 969cb5a4af..f5a18072c1 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -509,6 +509,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -563,6 +564,8 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME || attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 08658c8e86..f5f8f0cb8d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15727,6 +15727,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index b3b9313b36..c9781df0f4 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2057,11 +2057,15 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated by default as identity";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 				default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col));
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else
 				/* (note: above we cut off the 'default' string at 128) */
 				default_str = PQgetvalue(res, i, attrdef_col);
 
-			printTableAddCell(&cont, default_str, false, generated[0] ? true : false);
+			printTableAddCell(&cont, default_str, false, generated[0] == ATTRIBUTE_GENERATED_STORED ? true : false);
 		}
 
 		/* Info for index columns */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index a06800555c..f68fbb45f5 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 04004b5703..f8ea8bff7c 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -206,6 +206,9 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 891b119608..26d894a5cc 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,8 @@
 #include "nodes/execnodes.h"
 
 extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot);
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 8032bb7aa2..0107aa2e32 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -104,5 +104,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern Constraint *makeConstraint(ConstrType type);
+extern ColumnDef *makeSystemColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bce2d59b0d..2c5d8c21bb 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -477,6 +477,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ff626cbe61..cad578f6c9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1744,7 +1745,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1824,7 +1825,10 @@ typedef enum AlterTableType
 	AT_DetachPartition,			/* DETACH PARTITION */
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
-	AT_DropIdentity				/* DROP IDENTITY */
+	AT_DropIdentity,			/* DROP IDENTITY */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_DropSystemVersioning		/* DROP system versioning */
+
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2059,6 +2063,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when its is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2108,7 +2113,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3313,8 +3320,8 @@ typedef struct ConstraintsSetStmt
  */
 
 /* Reindex options */
-#define REINDEXOPT_VERBOSE (1 << 0)	/* print progress info */
-#define REINDEXOPT_REPORT_PROGRESS (1 << 1)	/* report pgstat progress */
+#define REINDEXOPT_VERBOSE (1 << 0)    /* print progress info */
+#define REINDEXOPT_REPORT_PROGRESS (1 << 1)    /* report pgstat progress */
 
 typedef enum ReindexObjectType
 {
@@ -3534,4 +3541,29 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_SYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index bbb27f8779..b0b68be528 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,7 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
-
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..9d68973b09 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -299,6 +299,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -392,6 +393,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD)
@@ -439,6 +441,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 674acc5d3c..2ead171ce7 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..6b5ceec910
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,188 @@
+CREATE TABLE stest0(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+--invalid datatype
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp integer GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  the data type of row end time must be timestamp
+-- references to other column in period columns
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  The period start time parameter must equal the name of row start time column
+-- duplicate system time column
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   end_timestamp1 TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time can not be specified multiple time
+-- default system time column usage
+CREATE TABLE stest2(a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                     Table "public.stest2"
+  Column   |            Type             | Collation | Nullable |            Default            
+-----------+-----------------------------+-----------+----------+-------------------------------
+ a         | integer                     |           |          | 
+ StartTime | timestamp without time zone |           | not null | generated always as row start
+ EndTime   | timestamp without time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3(a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                     Table "public.stest3"
+  Column   |            Type             | Collation | Nullable |            Default            
+-----------+-----------------------------+-----------+----------+-------------------------------
+ a         | integer                     |           |          | 
+ StartTime | timestamp without time zone |           | not null | generated always as row start
+ EndTime   | timestamp without time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp drop not null;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER start_timestamp drop not null;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE char;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 where a = 1;
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- test with joins
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a FROM stestx, stest0 WHERE stestx.y = stest0.a;
+ a 
+---
+ 3
+(1 row)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+WITH foo AS (select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                          Table "public.stest1"
+     Column      |              Type              | Collation | Nullable |            Default            
+-----------------+--------------------------------+-----------+----------+-------------------------------
+ a               | integer                        |           | not null | 
+ start_timestamp | timestamp(6) without time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp(6) without time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e143d..b2c6904751 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan collate.icu.utf8
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan collate.icu.utf8 system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index f86f5c5682..80023ac9c1 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -122,6 +122,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..76ad03c9b9
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,104 @@
+
+CREATE TABLE stest0(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+--invalid datatype
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp integer GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   end_timestamp1 TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2(a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3(a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp drop not null;
+
+ALTER TABLE stest0 ALTER start_timestamp drop not null;
+
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE char;
+
+
+
+--truncation
+truncate table stest0;
+
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+
+SELECT a FROM stest0 ORDER BY a;
+
+UPDATE stest0 SET a = 4 where a = 1;
+
+SELECT a FROM stest0 ORDER BY a;
+
+select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+
+SELECT a FROM stest0 ORDER BY a;
+select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+
+-- test with joins
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a FROM stestx, stest0 WHERE stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
-- 
2.17.1

#10Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Surafel Temesgen (#9)
Re: WIP: System Versioned Temporal Table

On 01/01/2020 11:50, Surafel Temesgen wrote:

Hi,
Attached is a complete patch and also contain a fix for your comments

This does not compile against current head (0ce38730ac).

gram.y: error: shift/reduce conflicts: 6 found, 0 expected

--

Vik Fearing

#11Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#10)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Thu, Jan 2, 2020 at 12:12 AM Vik Fearing <vik.fearing@2ndquadrant.com>
wrote:

This does not compile against current head (0ce38730ac).

gram.y: error: shift/reduce conflicts: 6 found, 0 expected

Rebased and conflict resolved i hope it build clean this time

regards
Surafel

Attachments:

0002-system-versioned-temporal-table.patchtext/x-patch; charset=US-ASCII; name=0002-system-versioned-temporal-table.patchDownload
From 640f8fc466acc90f0e46d65f21077f652651f34f Mon Sep 17 00:00:00 2001
From: Surafel Temesgen <surafel3000@gmail.com>
Date: Fri, 3 Jan 2020 13:50:19 +0300
Subject: [PATCH] system versioned temporal table

---
 doc/src/sgml/ref/alter_table.sgml             |  23 ++
 doc/src/sgml/ref/create_table.sgml            |  47 ++++
 doc/src/sgml/ref/select.sgml                  |  37 +++
 src/backend/access/common/tupdesc.c           |   4 +
 src/backend/commands/copy.c                   |   5 +
 src/backend/commands/tablecmds.c              |  49 +++-
 src/backend/commands/view.c                   |   6 +
 src/backend/executor/nodeModifyTable.c        | 180 +++++++++++++
 src/backend/nodes/copyfuncs.c                 |  31 +++
 src/backend/nodes/equalfuncs.c                |  28 ++
 src/backend/nodes/makefuncs.c                 | 120 +++++++++
 src/backend/nodes/outfuncs.c                  |   1 +
 src/backend/optimizer/plan/planner.c          |   8 +
 src/backend/optimizer/plan/subselect.c        |  19 ++
 src/backend/optimizer/util/plancat.c          | 189 +++++++++++++
 src/backend/parser/analyze.c                  |   9 +
 src/backend/parser/gram.y                     | 252 ++++++++++++++----
 src/backend/parser/parse_clause.c             |  76 ++++++
 src/backend/parser/parse_utilcmd.c            | 133 ++++++++-
 src/backend/tcop/utility.c                    |  61 +++++
 src/backend/utils/cache/relcache.c            |   3 +
 src/bin/pg_dump/pg_dump.c                     |   4 +
 src/bin/psql/describe.c                       |   6 +-
 src/include/access/tupdesc.h                  |   1 +
 src/include/catalog/pg_attribute.h            |   3 +
 src/include/executor/nodeModifyTable.h        |   2 +
 src/include/nodes/makefuncs.h                 |   6 +
 src/include/nodes/nodes.h                     |   2 +
 src/include/nodes/parsenodes.h                |  42 ++-
 src/include/optimizer/plancat.h               |   4 +-
 src/include/parser/kwlist.h                   |   3 +
 src/include/parser/parse_node.h               |   2 +-
 .../expected/system_versioned_table.out       | 188 +++++++++++++
 src/test/regress/parallel_schedule            |   2 +-
 src/test/regress/serial_schedule              |   1 +
 .../regress/sql/system_versioned_table.sql    | 104 ++++++++
 36 files changed, 1583 insertions(+), 68 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 8403c797e2..6182cda9cb 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -158,6 +160,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form adds system versioning columns to the table, using default column
+      name of system versioning which is StartTime and EndtTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -177,6 +189,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning columns from a table.  Indexes and
+      table constraints involving the columns will be automatically
+      dropped as well.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 4a2b6f0dae..cd1035b8d9 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -861,6 +865,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -953,6 +979,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1208,6 +1244,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 691e402803..ed228b781c 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,9 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME AS OF ] <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME BETWEEN ] <replaceable class="parameter">start_time</replaceable> [AND] <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME FROM ] <replaceable class="parameter">start_time</replaceable> [TO] <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -534,6 +537,40 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable>  [AND] <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between 
+        <replaceable class="parameter">start_time</replaceable> and 
+        <replaceable class="parameter">end_time</replaceable> including 
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable>  [TO] <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between 
+        <replaceable class="parameter">start_time</replaceable> and 
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 00bb4cb53d..7de3fdbd11 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -864,6 +867,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index c93a788798..1a39d35f5a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3225,6 +3225,11 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
 					ExecComputeStoredGenerated(estate, myslot);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1c4394abea..a83085372c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -76,6 +76,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -1515,6 +1516,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1523,6 +1525,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3585,6 +3595,7 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
 				cmd_lockmode = AccessExclusiveLock;
@@ -3615,6 +3626,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -6124,6 +6136,12 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME || attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -6501,6 +6519,12 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME || attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -6601,6 +6625,12 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME || attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -6847,6 +6877,12 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME || attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -10625,6 +10661,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME || attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -11608,6 +11649,12 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME || atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15080,7 +15127,7 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME && attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 06bc2b76ed..b028e5dcd8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -424,6 +425,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * check and filter out historical data if necessary.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 59d1a31c97..7abe795ba4 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -330,6 +330,129 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot)
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time in row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamp_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time in row end time columns for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -429,6 +552,13 @@ ExecInsert(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
 		/*
 		 * Check any RLS WITH CHECK policies.
 		 *
@@ -755,6 +885,31 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *sslot = NULL;
+
+			sslot = table_slot_create(resultRelationDesc, NULL);
+
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, SnapshotAny,
+											   sslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, sslot);
+				table_tuple_insert(resultRelationDesc, sslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(sslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1127,6 +1282,31 @@ ExecUpdate(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot);
 
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *sslot = NULL;
+
+			sslot = table_slot_create(resultRelationDesc, NULL);
+
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, SnapshotAny,
+											   sslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, sslot);
+				table_tuple_insert(resultRelationDesc, sslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(sslot);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 8034d5a51c..3b697d6073 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3354,6 +3354,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4752,6 +4753,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5640,6 +5665,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 9c8070c640..3df01ef2d4 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1245,6 +1245,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2914,6 +2915,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3733,6 +3755,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index f7d63df6e1..7fbc552ef4 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -809,3 +809,123 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	Node	   *lexp = lexpr;
+
+	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
+	while (IsA(lexp, A_Expr) &&
+		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
+		lexp = ((A_Expr *) lexp)->lexpr;
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexp, BoolExpr))
+	{
+		BoolExpr   *blexpr = (BoolExpr *) lexp;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = 0;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeConstraint -
+ *       create a constraint node
+ */
+Constraint *
+makeConstraint(ConstrType type)
+{
+	Constraint *c = makeNode(Constraint);
+
+	c->contype = type;
+	c->raw_expr = NULL;
+	c->cooked_expr = NULL;
+	c->location = 1;
+
+	return c;
+}
+
+/*
+ * makeSystemColumnDef -
+ *       create a ColumnDef node for system column
+ */
+ColumnDef *
+makeSystemColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) makeConstraint(CONSTR_ROW_START_TIME));
+	}
+	else
+	{
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) makeConstraint(CONSTR_ROW_END_TIME));
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamp")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = 1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index a53d47371b..35f0a1cf8a 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2599,6 +2599,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d6f2153593..73413ca6ae 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -645,6 +647,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
 	if (parse->cteList)
 		SS_process_ctes(root);
 
+	/*
+	 * Check and filter out historical data if necessary.
+	 */
+	if (parse->commandType == CMD_SELECT)
+		add_history_data_filter(parse);
+
 	/*
 	 * If the FROM clause is empty, replace it with a dummy RTE_RESULT RTE, so
 	 * that we don't need so many special cases to deal with that situation.
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3650e8329d..157078ec9e 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -27,6 +27,7 @@
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
+#include "optimizer/plancat.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
@@ -850,6 +851,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * check and filter out historical data if necessary.
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -896,6 +906,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * check and filter out historical data if necessary.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5ab8b..7ed09f83c7 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,6 +34,7 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
@@ -41,6 +42,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -79,6 +81,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2342,3 +2346,188 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			ParseState *pstate;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseNamespaceItem *newnsitem;
+
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = 0;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+												relation,
+												AccessShareLock,
+												NULL,
+												false,
+												true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+
+			if (query->jointree->quals != NULL)
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			else
+				query->jointree->quals = wClause;
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	else if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+		else
+			return false;
+	}
+	else
+		return expression_tree_walker(node, check_system_versioned_column_walker,
+									  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 447a61ef8c..e57ecf2257 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1220,6 +1220,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ad5be902b0..2356a2be2e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -127,6 +127,20 @@ typedef struct ImportQual
 	List	   *table_names;
 } ImportQual;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -170,7 +184,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -242,6 +255,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionSpec		*partspec;
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -374,12 +390,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	import_qualification_type
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
+				distinct_clause opt_all_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -433,7 +451,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 <list>	for_clause for_locking_clause opt_for_locking_clause for_locking_items
 %type <list>	locked_rels_list
 %type <boolean>	all_or_distinct
 
@@ -500,7 +518,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -541,7 +559,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -669,7 +687,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -684,7 +702,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -695,7 +713,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -731,6 +749,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
 %left		POSTFIXOP		/* dummy for postfix Op rules */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
 /*
  * To support target_el without AS, we must give IDENT an explicit priority
  * between POSTFIXOP and Op.  We can safely assign the same priority to
@@ -2101,7 +2121,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2223,7 +2251,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2232,8 +2272,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2242,6 +2294,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3141,12 +3202,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3160,12 +3222,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3179,13 +3242,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3199,13 +3263,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3219,13 +3284,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3239,13 +3305,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3322,6 +3389,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3418,6 +3486,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3500,12 +3578,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3523,6 +3601,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3545,6 +3624,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3924,9 +4027,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4062,7 +4190,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11321,7 +11449,7 @@ select_no_parens:
 										yyscanner);
 					$$ = $1;
 				}
-			| select_clause opt_sort_clause for_locking_clause opt_select_limit
+			| select_clause opt_sort_clause for_clause opt_select_limit
 				{
 					insertSelectOptions((SelectStmt *) $1, $2, $3,
 										list_nth($4, 0), list_nth($4, 1),
@@ -11353,7 +11481,7 @@ select_no_parens:
 										yyscanner);
 					$$ = $2;
 				}
-			| with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit
+			| with_clause select_clause opt_sort_clause for_clause opt_select_limit
 				{
 					insertSelectOptions((SelectStmt *) $2, $3, $4,
 										list_nth($5, 0), list_nth($5, 1),
@@ -11833,11 +11961,15 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
+		;
+
+for_clause:
+			FOR for_locking_clause						{ $$ = $2; }
 			| FOR READ ONLY							{ $$ = NIL; }
 		;
 
 opt_for_locking_clause:
-			for_locking_clause						{ $$ = $1; }
+			for_clause						{ $$ = $1; }
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
@@ -11858,10 +11990,10 @@ for_locking_item:
 		;
 
 for_locking_strength:
-			FOR UPDATE 							{ $$ = LCS_FORUPDATE; }
-			| FOR NO KEY UPDATE 				{ $$ = LCS_FORNOKEYUPDATE; }
-			| FOR SHARE 						{ $$ = LCS_FORSHARE; }
-			| FOR KEY SHARE 					{ $$ = LCS_FORKEYSHARE; }
+			 UPDATE 							{ $$ = LCS_FORUPDATE; }
+			| NO KEY UPDATE 				{ $$ = LCS_FORNOKEYUPDATE; }
+			| SHARE 						{ $$ = LCS_FORSHARE; }
+			| KEY SHARE 					{ $$ = LCS_FORKEYSHARE; }
 		;
 
 locked_rels_list:
@@ -11900,7 +12032,7 @@ values_clause:
  *****************************************************************************/
 
 from_clause:
-			FROM from_list							{ $$ = $2; }
+			FROM from_list							{ $$ = $2 ; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
@@ -11925,6 +12057,11 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+			| FOR relation_expr temporal_clause
+				{
+					$3->relation = (Node *)$2;
+					$$ = (Node *)$3;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -12023,7 +12160,28 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
-
+temporal_clause:  SYSTEM_TIME AS OF b_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = $4;
+				}
+			| SYSTEM_TIME BETWEEN b_expr AND b_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_SYMMETRIC;
+					$$->from = $3;
+					$$->to = $5;
+				}
+			| SYSTEM_TIME FROM Sconst TO Sconst
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = makeStringConst($3, @3);
+					$$->to = makeStringConst($5, @5);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15284,6 +15442,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15360,6 +15519,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15389,6 +15549,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16083,29 +16244,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	Node	   *lexp = lexpr;
-
-	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
-	while (IsA(lexp, A_Expr) &&
-		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
-		lexp = ((A_Expr *) lexp)->lexpr;
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexp, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexp;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 5fa42d307a..d99001e106 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -97,6 +98,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void addTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1141,6 +1143,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation		rel;
+		TupleDesc		tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		addTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3669,3 +3700,48 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		add temporal clause specification to where clause.
+ */
+static void
+addTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause;
+	Node	   *tClause;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 42095ab830..8144d58d70 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -71,6 +72,7 @@
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
+#include <string.h>
 
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
@@ -96,6 +98,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start column */
+	char	   *periodEnd;		/* name of period end column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +126,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -249,6 +258,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -284,7 +297,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -292,6 +307,27 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column  and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeSystemColumnDef("StartTime");
+		endCol = makeSystemColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -303,6 +339,25 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history data and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -716,6 +771,40 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				if (strcmp(strVal(list_nth(column->typeName->names, 1)), "timestamp") != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the data type of row start time must be timestamp")));
+
+				if (cxt->startTimeColName)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("row start time can not be specified multiple time")));
+
+				column->generated = ATTRIBUTE_ROW_START_TIME;
+				cxt->startTimeColName = column->colname;
+				cxt->isSystemVersioned = true;
+				column->is_not_null = true;
+				break;
+
+			case CONSTR_ROW_END_TIME:
+				if (strcmp(strVal(list_nth(column->typeName->names, 1)), "timestamp") != 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("the data type of row end time must be timestamp")));
+
+				if (cxt->endTimeColName)
+					ereport(ERROR,
+							(errcode(ERRCODE_SYNTAX_ERROR),
+							 errmsg("row end time can not be specified multiple time")));
+
+
+				column->generated = ATTRIBUTE_ROW_END_TIME;
+				cxt->endTimeColName = column->colname;
+				column->is_not_null = true;
+				break;
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1267,6 +1356,27 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	table_close(relation, NoLock);
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period start time parameter must equal the name of row start time column")));
+
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period end time  parameter must equal the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3078,6 +3188,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * The only subtypes that currently require parse transformation handling
@@ -3121,6 +3235,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) && (constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4474658ddf..0dc1fe9587 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -58,7 +59,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1094,6 +1097,9 @@ ProcessUtilitySlow(ParseState *pstate,
 					Oid			relid;
 					List	   *stmts;
 					ListCell   *l;
+					ListCell   *s;
+					Relation	rel;
+
 					LOCKMODE	lockmode;
 
 					/*
@@ -1105,6 +1111,61 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE statment
+							 */
+							startTimeCol = makeSystemColumnDef("StartTime");
+							endTimeCol = makeSystemColumnDef("EndTime");
+
+							/*
+							 * create alter table cmd and append to the ende
+							 * of commands and remove current listCell because
+							 * we don't want it anymore.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						/* Run parse analysis ... */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index df025a5a30..ff7843cd37 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -509,6 +509,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -563,6 +564,8 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME || attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b6988b7..9f8704b498 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15727,6 +15727,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb96fa..8cb6eb8d3d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2057,11 +2057,15 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated by default as identity";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 				default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col));
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else
 				/* (note: above we cut off the 'default' string at 128) */
 				default_str = PQgetvalue(res, i, attrdef_col);
 
-			printTableAddCell(&cont, default_str, false, generated[0] ? true : false);
+			printTableAddCell(&cont, default_str, false, generated[0] == ATTRIBUTE_GENERATED_STORED ? true : false);
 		}
 
 		/* Info for index columns */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 591e0d65ae..500a5441b8 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -206,6 +206,9 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 0495cae051..ab5be9ad2a 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -16,6 +16,8 @@
 #include "nodes/execnodes.h"
 
 extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot);
 
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..585ad10ce7 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -104,5 +104,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern Constraint *makeConstraint(ConstrType type);
+extern ColumnDef *makeSystemColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index baced7eec0..245e8aa3d0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -477,6 +477,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index f67bd9fad5..6156aa5376 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1744,7 +1745,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1824,7 +1825,10 @@ typedef enum AlterTableType
 	AT_DetachPartition,			/* DETACH PARTITION */
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
-	AT_DropIdentity				/* DROP IDENTITY */
+	AT_DropIdentity,			/* DROP IDENTITY */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_DropSystemVersioning		/* DROP system versioning */
+
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2059,6 +2063,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when its is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2108,7 +2113,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3313,8 +3320,8 @@ typedef struct ConstraintsSetStmt
  */
 
 /* Reindex options */
-#define REINDEXOPT_VERBOSE (1 << 0)	/* print progress info */
-#define REINDEXOPT_REPORT_PROGRESS (1 << 1)	/* report pgstat progress */
+#define REINDEXOPT_VERBOSE (1 << 0)    /* print progress info */
+#define REINDEXOPT_REPORT_PROGRESS (1 << 1)    /* report pgstat progress */
 
 typedef enum ReindexObjectType
 {
@@ -3534,4 +3541,29 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_SYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..fdce5d01db 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,7 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
-
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 0fe4e6cb20..f8f80afc0a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -299,6 +299,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -392,6 +393,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD)
@@ -439,6 +441,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..fdb4da1b70 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..6b5ceec910
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,188 @@
+CREATE TABLE stest0(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+--invalid datatype
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp integer GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  the data type of row end time must be timestamp
+-- references to other column in period columns
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  The period start time parameter must equal the name of row start time column
+-- duplicate system time column
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   end_timestamp1 TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time can not be specified multiple time
+-- default system time column usage
+CREATE TABLE stest2(a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                     Table "public.stest2"
+  Column   |            Type             | Collation | Nullable |            Default            
+-----------+-----------------------------+-----------+----------+-------------------------------
+ a         | integer                     |           |          | 
+ StartTime | timestamp without time zone |           | not null | generated always as row start
+ EndTime   | timestamp without time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3(a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                     Table "public.stest3"
+  Column   |            Type             | Collation | Nullable |            Default            
+-----------+-----------------------------+-----------+----------+-------------------------------
+ a         | integer                     |           |          | 
+ StartTime | timestamp without time zone |           | not null | generated always as row start
+ EndTime   | timestamp without time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp drop not null;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER start_timestamp drop not null;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE char;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 where a = 1;
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- test with joins
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a FROM stestx, stest0 WHERE stestx.y = stest0.a;
+ a 
+---
+ 3
+(1 row)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+WITH foo AS (select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                          Table "public.stest1"
+     Column      |              Type              | Collation | Nullable |            Default            
+-----------------+--------------------------------+-----------+----------+-------------------------------
+ a               | integer                        |           | not null | 
+ start_timestamp | timestamp(6) without time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp(6) without time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d33a4e143d..b2c6904751 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan collate.icu.utf8
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan collate.icu.utf8 system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index f86f5c5682..80023ac9c1 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -122,6 +122,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..76ad03c9b9
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,104 @@
+
+CREATE TABLE stest0(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+--invalid datatype
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp integer GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1(a integer PRIMARY KEY, start_timestamp TIMESTAMP(6) GENERATED ALWAYS AS row START,
+   end_timestamp TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   end_timestamp1 TIMESTAMP(6) GENERATED ALWAYS AS ROW END,
+   PERIOD FOR SYSTEM_TIME(start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2(a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3(a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp drop not null;
+
+ALTER TABLE stest0 ALTER start_timestamp drop not null;
+
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE char;
+
+
+
+--truncation
+truncate table stest0;
+
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+
+SELECT a FROM stest0 ORDER BY a;
+
+UPDATE stest0 SET a = 4 where a = 1;
+
+SELECT a FROM stest0 ORDER BY a;
+
+select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+
+SELECT a FROM stest0 ORDER BY a;
+select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+
+-- test with joins
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a FROM stestx, stest0 WHERE stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
-- 
2.17.1

#12Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Surafel Temesgen (#11)
Re: WIP: System Versioned Temporal Table

On 03/01/2020 11:57, Surafel Temesgen wrote:

On Thu, Jan 2, 2020 at 12:12 AM Vik Fearing
<vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>> wrote:

This does not compile against current head (0ce38730ac).

gram.y: error: shift/reduce conflicts: 6 found, 0 expected

Rebased and conflict resolved i hope it build clean this time

It does but you haven't included your tests file so `make check` fails.

It seems clear to me that you haven't tested it at all anyway.  The
temporal conditions do not return the correct results, and the syntax is
wrong, too.  Also, none of my previous comments have been addressed
except for "system versioning" instead of "system_versioning".  Why?

--

Vik Fearing

#13Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#12)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 3, 2020 at 4:22 PM Vik Fearing <vik.fearing@2ndquadrant.com>
wrote:

Rebased and conflict resolved i hope it build clean this time

It does but you haven't included your tests file so `make check` fails.

what tests file? i add system_versioned_table.sql and
system_versioned_table.out
test files and it tested and pass on appveyor[1]. https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.73247 only failed on travis
because of warning. i will add more test

It seems clear to me that you haven't tested it at all anyway. The
temporal conditions do not return the correct results, and the syntax is
wrong, too. Also, none of my previous comments have been addressed
except for "system versioning" instead of "system_versioning". Why?

I also correct typo and add row end column time to unique
key that make it unique for current data. As you mentioned
other comment is concerning about application-time periods
which the patch not addressing . i refer sql 2011 standard for
syntax can you tell me which syntax you find it wrong?
[1]: . https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.73247
https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.73247

regards
Surafel

#14Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#8)
Re: WIP: System Versioned Temporal Table

On Mon, Oct 28, 2019 at 6:36 PM Vik Fearing <vik.fearing@2ndquadrant.com>
wrote:

On 28/10/2019 13:48, Surafel Temesgen wrote:

On Fri, Oct 25, 2019 at 10:45 PM Vik Fearing
<vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>>

wrote:

I don't understand what you mean by this.

The primary purpose of adding row end time to primary key is to

allow

duplicate value to be inserted into a table with keeping

constraint in

current data but it can be duplicated in history data. Adding row
start time column to primary key will eliminate this uniqueness for
current data which is not correct

How? The primary/unique keys must always be unique at every point
in time.

From user prospect it is acceptable to delete and reinsert a record
with the same key value multiple time which means there will be
multiple record with the same key value in a history data but there is
only one values in current data as a table without system versioning
do .I add row end time column to primary key to allow user supplied
primary key values to be duplicated in history data which is acceptable

Yes, I understand that. I'm saying you should also add the row start
time column.

that allow the same primary key value row to be insert as long
as insertion time is different

regards
Surafel

#15Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Surafel Temesgen (#13)
Re: WIP: System Versioned Temporal Table

On 05/01/2020 11:16, Surafel Temesgen wrote:

On Fri, Jan 3, 2020 at 4:22 PM Vik Fearing
<vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>> wrote:

Rebased and conflict resolved i hope it build clean this time

It does but you haven't included your tests file so `make check`
fails.

what tests file?

Exactly.

i add system_versioned_table.sql and system_versioned_table.out
test files

Those are not included in the patch.

<checks again>

Okay, that was user error on my side.  I apologize.

 

It seems clear to me that you haven't tested it at all anyway.  The
temporal conditions do not return the correct results, and the
syntax is
wrong, too.  Also, none of my previous comments have been addressed
except for "system versioning" instead of "system_versioning".  Why?

I also correct typo and add row end column time to unique
key that make it unique for current data. As you mentioned
other comment is concerning about application-time periods
which the patch not addressing .

- For performance, you must put the start column in the indexes also.

- You only handle timestamp when you should also handle timestamptz and
date.

- You don't throw 2201H for anomalies

i refer sql 2011 standard for
syntax can you tell me which syntax you find it wrong?

Okay, now that I see your tests, I understand why everything is broken. 
You only test FROM-TO and with a really wide interval.  There are no
tests for AS OF and no tests for BETWEEN-AND.

As for the syntax, you have:

select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to
'infinity' ORDER BY a;

when you should have:

select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to
'infinity' ORDER BY a;

That is, the FOR should be on the other side of the table name.

In addition, there are many rules in the standard that are not respected
here.  For example, this query works and should not:

CREATE TABLE t (system_time integer) WITH SYSTEM VERSIONING;

This syntax is not supported:

ALTER TABLE t
    ADD PERIOD FOR SYSTEM_TIME (s, e)
        ADD COLUMN s timestamp
        ADD COLUMN e timestamp;

psql's \d does not show that the table is system versioned, and doesn't
show the columns of the system_time period.

I can drop columns used in the period.

Please don't hesitate to take inspiration from my extension that does
this.  The extension is under the PostgreSQL license for that reason. 
Take from it whatever you need.

https://github.com/xocolatl/periods/

--

Vik Fearing

#16legrand legrand
legrand_legrand@hotmail.com
In reply to: Vik Fearing (#15)
Re: WIP: System Versioned Temporal Table

Vik Fearing-4 wrote

On 05/01/2020 11:16, Surafel Temesgen wrote:

On Fri, Jan 3, 2020 at 4:22 PM Vik Fearing
&lt;

vik.fearing@

&lt;mailto:

vik.fearing@

&gt;> wrote:

[...]

You only test FROM-TO and with a really wide interval.  There are no
tests for AS OF and no tests for BETWEEN-AND.

As for the syntax, you have:

select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to
'infinity' ORDER BY a;

when you should have:

select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to
'infinity' ORDER BY a;

That is, the FOR should be on the other side of the table name.

[...]

Vik Fearing

Hello,

I though that standard syntax was "AS OF SYSTEM TIME"
as discussed here
/messages/by-id/A254CDC3-D308-4822-8928-8CC584E0CC71@elusive.cx
, also explaining how to parse such a syntax .

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#17Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: legrand legrand (#16)
Re: WIP: System Versioned Temporal Table

On 05/01/2020 16:01, legrand legrand wrote:

As for the syntax, you have:

select a from for stest0 system_time from '2000-01-01 00:00:00.00000' to
'infinity' ORDER BY a;

when you should have:

select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to
'infinity' ORDER BY a;

That is, the FOR should be on the other side of the table name.

[...]

Vik Fearing

Hello,

I though that standard syntax was "AS OF SYSTEM TIME"
as discussed here
/messages/by-id/A254CDC3-D308-4822-8928-8CC584E0CC71@elusive.cx
, also explaining how to parse such a syntax .

No, that is incorrect.  The standard syntax is:

    FROM tablename FOR SYSTEM_TIME AS OF '...'

    FROM tablename FOR SYSTEM_TIME BETWEEN '...' AND '...'

    FROM tablename FOR SYSTEM_TIME FROM '...' TO '...'

--

Vik Fearing

#18legrand legrand
legrand_legrand@hotmail.com
In reply to: Vik Fearing (#17)
Re: WIP: System Versioned Temporal Table

Vik Fearing-4 wrote

On 05/01/2020 16:01, legrand legrand wrote:

No, that is incorrect.  The standard syntax is:

    FROM tablename FOR SYSTEM_TIME AS OF '...'

    FROM tablename FOR SYSTEM_TIME BETWEEN '...' AND '...'

    FROM tablename FOR SYSTEM_TIME FROM '...' TO '...'

--

Vik Fearing

oups, I re-read links and docs and I'm wrong.
Sorry for the noise

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#19Kyotaro Horiguchi
horikyota.ntt@gmail.com
In reply to: Vik Fearing (#8)
Re: WIP: System Versioned Temporal Table

Hello.

Isn't this patch somehow broken?

At Mon, 28 Oct 2019 16:36:09 +0100, Vik Fearing <vik.fearing@2ndquadrant.com> wrote in

On 28/10/2019 13:48, Surafel Temesgen wrote:

On Fri, Oct 25, 2019 at 10:45 PM Vik Fearing
<vik.fearing@2ndquadrant.com <mailto:vik.fearing@2ndquadrant.com>> wrote:

     I don't understand what you mean by this.

The primary purpose of adding row end time to primary key is to

allow

duplicate value to be inserted into a table with keeping

constraint in

current data but it can be duplicated in history data. Adding row
start time column to primary key will eliminate this uniqueness for
current data which is not correct  

How?  The primary/unique keys must always be unique at every point
in time.

From user prospect it is acceptable to delete and reinsert a record
with the same key value multiple time which means there will be
multiple record with the same key value in a history data but there is
only one values in current data as a table without system versioning
do .I add row end time column to primary key to allow user supplied
primary key values to be duplicated in history data which is acceptable

Yes, I understand that.  I'm saying you should also add the row start
time column.

I think that the start and end timestamps represent the period where
that version of the row was active. So UPDATE should modify the start
timestamp of the new version to the same value with the end timestamp
of the old version to the updated time. Thus, I don't think adding
start timestamp to PK doesn't work as expected. That hinders us from
rejecting rows with the same user-defined unique key because start
timestams is different each time of inserts. I think what Surafel is
doing in the current patch is correct. Only end_timestamp = +infinity
rejects another non-historical (= live) version with the same
user-defined unique key.

I'm not sure why the patch starts from "0002, but anyway it applied
on e369f37086. Then I ran make distclean, ./configure then make all,
make install, initdb and started server after all of them.

First, I tried to create a temporal table.

When I used timestamptz as the type of versioning columns, ALTER,
CREATE commands ended with server crash.

"CREATE TABLE t (a int, s timestamptz GENERATED ALWAYS AS ROW START, e timestamptz GENERATED ALWAYS AS ROW END);"
(CREATE TABLE t (a int);)
"ALTER TABLE t ADD COLUMN s timestamptz GENERATED ALWAYS AS ROW START"
"ALTER TABLE t ADD COLUMN s timestamptz GENERATED ALWAYS AS ROW START, ADD COLUMN e timestamptz GENERATED ALWAYS AS ROW END"

If I added the start/end timestamp columns to an existing table, it
returns uncertain error.

ALTER TABLE t ADD COLUMN s timestamp(6) GENERATED ALWAYS AS ROW START;
ERROR: column "s" contains null values
ALTER TABLE t ADD COLUMN s timestamp(6) GENERATED ALWAYS AS ROW START, ADD COLUMN e timestamp(6) GENERATED ALWAYS AS ROW END;
ERROR: column "s" contains null values

When I defined only start column, SELECT on the table crashed.

"CREATE TABLE t (s timestamp(6) GENERATED ALWAYS AS ROW START);"
"SELECT * from t;"
(crashed)

The following command ended with ERROR which I cannot understand the
cause, but I expected the command to be accepted.

ALTER TABLE t ADD COLUMN start timestamp(6) GENERATED ALWAYS AS ROW START, ADD COLUMN end timestamp(6) GENERATED ALWAYS AS ROW END;
ERROR: syntax error at or near "end"

I didin't examined further but the syntax part doesn't seem designed
well, and the body part seems vulnerable to unexpected input.

I ran a few queries:

SELECT * shows the timestamp columns, don't we need to hide the period
timestamp columns from this query?

I think UPDATE needs to update the start timestamp, but it doesn't. As
the result the timestamps doesn't represent the correct lifetime of
the row version and we wouldn't be able to pick up correct versions of
a row that exprerienced updates. (I didn't confirmed that because I
couldn't do "FOR SYSTEM_TIME AS OF" query due to syntax error..)

(Sorry in advance for possible pointless comments due to my lack of
access to the SQL11 standard.) If we have the period-timestamp
columns before the last two columns, INSERT in a common way on the
table fails, which doesn't seem to me to be expected behavior:

CREATE TABLE t (s timestamp(6) GENERATED ALWAYS AS ROW START, e timestamp(6) GENERATED ALWAYS AS ROW END, a int) WITH SYSTEM VERSIONING;
INSERT INTO t (SELECT a FROM generate_series(0, 99) a);
ERROR: column "s" is of type timestamp without time zone but expression is of type integer

Some queries using SYSTEM_TIME which I think should be accepted ends
with error. Is the grammar part missing something?

SELECT * FROM t FOR SYSTEM_TIME AS OF '2020-01-07 09:57:55';
ERROR: syntax error at or near "system_time"
LINE 1: SELECT * FROM t FOR SYSTEM_TIME AS OF '2020-01-07 09:57:55';

SELECT * FROM t FOR SYSTEM_TIME BETWEEN '2020-01-07 0:00:00' AND '2020-01-08 0:00:00';
ERROR: syntax error at or near "system_time"
LINE 1: SELECT * FROM t FOR SYSTEM_TIME BETWEEN '2020-01-07 0:00:00'...

Other random comments (sorry for it not being comprehensive):

The patch at worst loops over all columns at every parse time. It is
quite ineffecient if there are many columns. We can store the column
indexes in relcache.

If I'm not missing anything, alter table doesn't properly modify
existing data in the target table. AddSystemVersioning should fill in
start/end_timestamp with proper values and DropSystemVersioning shuold
remove rows no longer useful.

+makeAndExpr(Node *lexpr, Node *rexpr, int location)

I believe that planner flattenes nested AND/ORs in
eval_const_expressions(). Shouldn't we leave the work to the planner?

+makeConstraint(ConstrType type)

We didn't use such a function to make that kind of nodes. Maybe we
should use makeNode directly, or we need to change similar coding into
that using the function. Addition to that, setting .location to 1 is
wrong. "Unknown" location is -1.

Separately from that, temporal clauses is not restriction of a
table. So it seems wrong to me to use constraint mechamism for this
purpose.

+makeSystemColumnDef(char *name)

"system column (or attribute)" is a column specially treated outside
of tuple descriptor. The temporal-period columns are not system
columns in that sense.

regards.

--
Kyotaro Horiguchi
NTT Open Source Software Center

#20Vik Fearing
vik.fearing@2ndquadrant.com
In reply to: Vik Fearing (#15)
2 attachment(s)
Re: WIP: System Versioned Temporal Table

On 05/01/2020 13:50, Vik Fearing wrote:

Okay, now that I see your tests, I understand why everything is broken. 
You only test FROM-TO and with a really wide interval.  There are no
tests for AS OF and no tests for BETWEEN-AND.

I have started working on some better test cases for you.  The attached
.sql and .out tests should pass, and they are some of the tests that
I'll be putting your next version through.  There are many more tests
that need to be added.

Once all the desired functionality is there, I'll start reviewing the
code itself.

Keep up the good work, and let me know if I can do anything to help you.

--

Vik Fearing

Attachments:

system_versioned_table.sqlapplication/sql; name=system_versioned_table.sqlDownload
system_versioned_table.outtext/plain; charset=UTF-8; name=system_versioned_table.outDownload
#21David Steele
david@pgmasters.net
In reply to: Surafel Temesgen (#11)
Re: WIP: System Versioned Temporal Table

Hi Surafel,

On 1/3/20 5:57 AM, Surafel Temesgen wrote:

Rebased and conflict resolved i hope it build clean this time

This patch no longer applies according to cfbot and there are a number
of review comments that don't seem to have been addressed yet.

The patch is not exactly new for this CF but since the first version was
posted 2020-01-01 and there have been no updates (except a rebase) since
then it comes pretty close.

Were you planning to work on this for PG13? If so we'll need to see a
rebased and updated patch pretty soon. My recommendation is that we
move this patch to PG14.

Regards,
--
-David
david@pgmasters.net

#22Vik Fearing
vik@postgresfriends.org
In reply to: David Steele (#21)
Re: WIP: System Versioned Temporal Table

On 03/03/2020 19:33, David Steele wrote:

Hi Surafel,

On 1/3/20 5:57 AM, Surafel Temesgen wrote:

Rebased and conflict resolved i hope it build clean this time

This patch no longer applies according to cfbot and there are a number
of review comments that don't seem to have been addressed yet.

The patch is not exactly new for this CF but since the first version was
posted 2020-01-01 and there have been no updates (except a rebase) since
then it comes pretty close.

Were you planning to work on this for PG13?  If so we'll need to see a
rebased and updated patch pretty soon.  My recommendation is that we
move this patch to PG14.

I strongly second that motion.
--
Vik Fearing

#23Surafel Temesgen
surafel3000@gmail.com
In reply to: Kyotaro Horiguchi (#19)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

Hi,
Thank you very much looking at it
On Tue, Jan 7, 2020 at 1:33 PM Kyotaro Horiguchi <horikyota.ntt@gmail.com>
wrote:

Hello.

Isn't this patch somehow broken?

First, I tried to create a temporal table.

When I used timestamptz as the type of versioning columns, ALTER,
CREATE commands ended with server crash.

"CREATE TABLE t (a int, s timestamptz GENERATED ALWAYS AS ROW START, e
timestamptz GENERATED ALWAYS AS ROW END);"
(CREATE TABLE t (a int);)
"ALTER TABLE t ADD COLUMN s timestamptz GENERATED ALWAYS AS ROW START"
"ALTER TABLE t ADD COLUMN s timestamptz GENERATED ALWAYS AS ROW START,
ADD COLUMN e timestamptz GENERATED ALWAYS AS ROW END"

If I added the start/end timestamp columns to an existing table, it
returns uncertain error.

ALTER TABLE t ADD COLUMN s timestamp(6) GENERATED ALWAYS AS ROW START;
ERROR: column "s" contains null values
ALTER TABLE t ADD COLUMN s timestamp(6) GENERATED ALWAYS AS ROW START,
ADD COLUMN e timestamp(6) GENERATED ALWAYS AS ROW END;
ERROR: column "s" contains null values

When I defined only start column, SELECT on the table crashed.

"CREATE TABLE t (s timestamp(6) GENERATED ALWAYS AS ROW START);"
"SELECT * from t;"
(crashed)

The following command ended with ERROR which I cannot understand the
cause, but I expected the command to be accepted.

Fixed

ALTER TABLE t ADD COLUMN start timestamp(6) GENERATED ALWAYS AS ROW

START, ADD COLUMN end timestamp(6) GENERATED ALWAYS AS ROW END;
ERROR: syntax error at or near "end"

end is a keyword

I didin't examined further but the syntax part doesn't seem designed
well, and the body part seems vulnerable to unexpected input.

I ran a few queries:

SELECT * shows the timestamp columns, don't we need to hide the period
timestamp columns from this query?

The sql standard didn't dictate hiding the column but i agree hiding it by
default is good thing because this columns are used by the system
to classified the data and not needed in user side frequently. I can
change to that if we have consensus

I think UPDATE needs to update the start timestamp, but it doesn't. As
the result the timestamps doesn't represent the correct lifetime of
the row version and we wouldn't be able to pick up correct versions of
a row that exprerienced updates. (I didn't confirmed that because I
couldn't do "FOR SYSTEM_TIME AS OF" query due to syntax error..)

Right. It have to set both system time for inserted row and set row end
time for
deleted row. I fix it

(Sorry in advance for possible pointless comments due to my lack of
access to the SQL11 standard.) If we have the period-timestamp
columns before the last two columns, INSERT in a common way on the
table fails, which doesn't seem to me to be expected behavior:

CREATE TABLE t (s timestamp(6) GENERATED ALWAYS AS ROW START, e
timestamp(6) GENERATED ALWAYS AS ROW END, a int) WITH SYSTEM VERSIONING;
INSERT INTO t (SELECT a FROM generate_series(0, 99) a);
ERROR: column "s" is of type timestamp without time zone but expression
is of type integer

Its the same without the patch too
CREATE TABLE t (s timestamptz , e timestamptz, a int);
INSERT INTO t (SELECT a FROM generate_series(0, 99) a);
ERROR: column "s" is of type timestamp with time zone but expression is of
type integer
LINE 1: INSERT INTO t (SELECT a FROM generate_series(0, 99) a);

Some queries using SYSTEM_TIME which I think should be accepted ends
with error. Is the grammar part missing something?

SELECT * FROM t FOR SYSTEM_TIME AS OF '2020-01-07 09:57:55';
ERROR: syntax error at or near "system_time"
LINE 1: SELECT * FROM t FOR SYSTEM_TIME AS OF '2020-01-07 09:57:55';

SELECT * FROM t FOR SYSTEM_TIME BETWEEN '2020-01-07 0:00:00' AND
'2020-01-08 0:00:00';
ERROR: syntax error at or near "system_time"
LINE 1: SELECT * FROM t FOR SYSTEM_TIME BETWEEN '2020-01-07 0:00:00'...

fixed

Other random comments (sorry for it not being comprehensive):

The patch at worst loops over all columns at every parse time. It is
quite ineffecient if there are many columns. We can store the column
indexes in relcache.

but its only for system versioned table.

If I'm not missing anything, alter table doesn't properly modify
existing data in the target table. AddSystemVersioning should fill in
start/end_timestamp with proper values and DropSystemVersioning shuold
remove rows no longer useful.

fixed

+makeAndExpr(Node *lexpr, Node *rexpr, int location)

I believe that planner flattenes nested AND/ORs in
eval_const_expressions(). Shouldn't we leave the work to the planner?

filter clause is added using makeAndExpr and planner can flat that if it
sees fit

+makeConstraint(ConstrType type)

We didn't use such a function to make that kind of nodes. Maybe we
should use makeNode directly, or we need to change similar coding into
that using the function. Addition to that, setting .location to 1 is
wrong. "Unknown" location is -1.

done

Separately from that, temporal clauses is not restriction of a
table. So it seems wrong to me to use constraint mechamism for this
purpose.

we use constraint mechanism for similar thing like default value and
generated column

+makeSystemColumnDef(char *name)

"system column (or attribute)" is a column specially treated outside
of tuple descriptor. The temporal-period columns are not system
columns in that sense.

changed to makeTemporalColumnDef and use timestamptz for all
versioning purpose.

Attach is the patch that fix the above and uses Vik's regression test

regards
Surafel

Attachments:

system-versioned-temporal-table_v3.patchtext/x-patch; charset=US-ASCII; name=system-versioned-temporal-table_v3.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index e486196477..080187f12c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form adds system versioning columns to the table, using default column
+      name of system versioning which is StartTime and EndtTime. If the table is
+      not empty StartTime and EndtTime columns will be filled with current time and
+      infinity respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +192,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning columns from a table.
+      Indexes and table constraints involving the columns will be
+      automatically dropped as well. If the table is not empty history
+      data also removed along the columns.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 4a2b6f0dae..cd1035b8d9 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -861,6 +865,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -953,6 +979,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1208,6 +1244,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 691e402803..4ebe5ee12a 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME AS OF ] <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME BETWEEN ] <replaceable class="parameter">start_time</replaceable> [AND] <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME BETWEEN ASYMMETRIC] <replaceable class="parameter">start_time</replaceable> [AND] <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME BETWEEN SYMMETRIC] <replaceable class="parameter">start_time</replaceable> [AND] <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> [ FOR SYSTEM_TIME FROM ] <replaceable class="parameter">start_time</replaceable> [TO] <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -534,6 +539,66 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable>  [AND] <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between 
+        <replaceable class="parameter">start_time</replaceable> and 
+        <replaceable class="parameter">end_time</replaceable> including 
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable>  [AND] <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between 
+        <replaceable class="parameter">start_time</replaceable> and 
+        <replaceable class="parameter">end_time</replaceable> including 
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN SYMMETRIC<replaceable class="parameter">start_time</replaceable>  [AND] <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between 
+        the least of <replaceable class="parameter">start_time</replaceable> and 
+        <replaceable class="parameter">end_time</replaceable> and the greatest of
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable>  [TO] <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between 
+        <replaceable class="parameter">start_time</replaceable> and 
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 1e743d7d86..be4b4abab9 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -864,6 +867,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index e79ede4cb8..759c8ff5f0 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3224,6 +3224,11 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
 					ExecComputeStoredGenerated(estate, myslot, CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 3eb861bfbf..48b5f3b6a6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -59,6 +59,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -76,6 +77,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -169,6 +171,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -419,11 +424,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1557,6 +1563,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1565,6 +1572,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3653,8 +3668,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3683,6 +3700,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4231,6 +4249,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4287,6 +4306,37 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to specified otherwise its
+			 * useless
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("The period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("The period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("The period start time parameter must equal the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("The period end time  parameter must equal the name of row end time column")));
+
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4383,16 +4433,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4604,6 +4654,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 			ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4660,7 +4711,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5025,6 +5076,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
+
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5067,6 +5120,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5134,6 +5188,14 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
+		estate->es_result_relation_info = resultRelInfo;
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5222,6 +5284,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5292,6 +5361,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5327,6 +5400,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6141,6 +6215,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6192,6 +6274,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			/* must do a rewrite for identity columns */
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 		}
+
 		else
 			defval = (Expr *) build_column_default(rel, attribute.attnum);
 
@@ -6460,6 +6543,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -6839,6 +6929,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -6939,6 +7036,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7330,6 +7434,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7488,11 +7599,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7535,6 +7647,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7595,11 +7717,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7620,9 +7746,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11130,6 +11256,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -11788,10 +11920,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12121,6 +12256,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15594,7 +15736,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103feb..c3a88b9db8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4322..ced50c0429 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -355,6 +355,131 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time in row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time in row end time columns for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+
+
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -454,6 +579,13 @@ ExecInsert(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
 		/*
 		 * Check any RLS WITH CHECK policies.
 		 *
@@ -780,6 +912,31 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *sslot = NULL;
+
+			sslot = table_slot_create(resultRelationDesc, NULL);
+
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   sslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, sslot);
+				table_tuple_insert(resultRelationDesc, sslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(sslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1152,6 +1309,38 @@ ExecUpdate(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *sslot = NULL;
+
+			sslot = table_slot_create(resultRelationDesc, NULL);
+
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   sslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, sslot);
+				table_tuple_insert(resultRelationDesc, sslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(sslot);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index eaab97f753..cc52800e29 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3358,6 +3358,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4767,6 +4768,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5658,6 +5683,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 88b912977e..a193776fd9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1249,6 +1249,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2930,6 +2931,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3752,6 +3774,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index e8cdc90c31..d48a1e10d3 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -809,3 +809,130 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	Node	   *lexp = lexpr;
+
+	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
+	while (IsA(lexp, A_Expr) &&
+		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
+		lexp = ((A_Expr *) lexp)->lexpr;
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexp, BoolExpr))
+	{
+		BoolExpr   *blexpr = (BoolExpr *) lexp;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = 0;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = 1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e084c3f069..4619463180 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2602,6 +2602,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6314..8847679f74 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 3650e8329d..ce404ce4c6 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -850,6 +851,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to filter out historical data .
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -896,6 +906,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and filter out historical data.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5ab8b..91939cc1b6 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2342,3 +2347,194 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = 0;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412842..be04dad8b8 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -45,6 +45,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -445,6 +446,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1220,6 +1226,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1296,7 +1311,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
-
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1314,6 +1328,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2271,6 +2290,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f956c..768c67306f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -127,6 +127,20 @@ typedef struct ImportQual
 	List	   *table_names;
 } ImportQual;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -145,7 +159,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -170,7 +183,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -242,6 +254,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionSpec		*partspec;
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -374,12 +389,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <ival>	import_qualification_type
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
+				distinct_clause opt_all_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -433,7 +450,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 <list>	for_clause for_locking_clause opt_for_locking_clause for_locking_items
 %type <list>	locked_rels_list
 %type <boolean>	all_or_distinct
 
@@ -500,7 +517,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -541,7 +558,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -672,7 +689,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -687,7 +704,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -698,7 +715,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -719,7 +736,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 
 /* Precedence: lowest to highest */
@@ -734,6 +751,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
 %left		POSTFIXOP		/* dummy for postfix Op rules */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		FOR
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, we must give IDENT an explicit priority
  * between POSTFIXOP and Op.  We can safely assign the same priority to
@@ -775,6 +796,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2078,6 +2104,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2105,7 +2139,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2244,7 +2286,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2253,8 +2307,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2263,6 +2329,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3162,12 +3237,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3181,12 +3257,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3200,13 +3277,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3220,13 +3298,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3240,13 +3319,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3260,13 +3340,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3343,6 +3424,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3439,6 +3521,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3521,12 +3613,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3544,6 +3636,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3566,6 +3659,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3945,9 +4062,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4083,7 +4225,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11366,7 +11508,7 @@ select_no_parens:
 										yyscanner);
 					$$ = $1;
 				}
-			| select_clause opt_sort_clause for_locking_clause opt_select_limit
+			| select_clause opt_sort_clause for_clause opt_select_limit
 				{
 					insertSelectOptions((SelectStmt *) $1, $2, $3,
 										list_nth($4, 0), list_nth($4, 1),
@@ -11398,7 +11540,7 @@ select_no_parens:
 										yyscanner);
 					$$ = $2;
 				}
-			| with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit
+			| with_clause select_clause opt_sort_clause for_clause opt_select_limit
 				{
 					insertSelectOptions((SelectStmt *) $2, $3, $4,
 										list_nth($5, 0), list_nth($5, 1),
@@ -11878,11 +12020,15 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
+		;
+
+for_clause:
+			FOR for_locking_clause						{ $$ = $2; }
 			| FOR READ ONLY							{ $$ = NIL; }
 		;
 
 opt_for_locking_clause:
-			for_locking_clause						{ $$ = $1; }
+			for_clause						{ $$ = $1; }
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
@@ -11903,10 +12049,10 @@ for_locking_item:
 		;
 
 for_locking_strength:
-			FOR UPDATE 							{ $$ = LCS_FORUPDATE; }
-			| FOR NO KEY UPDATE 				{ $$ = LCS_FORNOKEYUPDATE; }
-			| FOR SHARE 						{ $$ = LCS_FORSHARE; }
-			| FOR KEY SHARE 					{ $$ = LCS_FORKEYSHARE; }
+			 UPDATE 							{ $$ = LCS_FORUPDATE; }
+			| NO KEY UPDATE 				{ $$ = LCS_FORNOKEYUPDATE; }
+			| SHARE 						{ $$ = LCS_FORSHARE; }
+			| KEY SHARE 					{ $$ = LCS_FORKEYSHARE; }
 		;
 
 locked_rels_list:
@@ -11945,7 +12091,7 @@ values_clause:
  *****************************************************************************/
 
 from_clause:
-			FROM from_list							{ $$ = $2; }
+			FROM from_list							{ $$ = $2 ; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
@@ -11957,12 +12103,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11970,6 +12120,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -12068,7 +12231,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
 
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
+
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15330,6 +15540,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15406,6 +15617,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15436,6 +15648,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15728,16 +15941,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16130,29 +16333,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	Node	   *lexp = lexpr;
-
-	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
-	while (IsA(lexp, A_Expr) &&
-		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
-		lexp = ((A_Expr *) lexp)->lexpr;
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexp, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexp;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 36a3efff87..ed2dd152c6 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void addTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1139,6 +1141,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		addTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3686,3 +3717,74 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		add temporal clause specification to where clause.
+ */
+static void
+addTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af77f1890f..7a1542e4d2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start column */
+	char	   *periodEnd;		/* name of period end column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -249,6 +259,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -284,7 +298,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -292,6 +308,41 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column  and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("The period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("The period end time column not specified")));
+
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -303,6 +354,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history data and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -726,6 +797,64 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row start time must be timestamptz ")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time can not be specified multiple time")));
+
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1278,6 +1407,39 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	table_close(relation, NoLock);
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period start time column not specified")));
+
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period start time parameter must equal the name of row start time column")));
+
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("The period end time  parameter must equal the name of row end time column")));
+
+
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3025,7 +3187,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3052,7 +3214,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3089,6 +3251,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3123,6 +3289,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3132,6 +3306,24 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3299,6 +3491,22 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3330,7 +3538,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index be86eb37fe..3177e52851 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -118,6 +118,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -169,6 +172,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index b1f7f6e2d0..e218bfc7cd 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -58,7 +59,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1238,6 +1241,12 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
+
+
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1248,6 +1257,86 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already system versioned")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning for table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE statment
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table cmd and append to the ende
+							 * of commands and remove current listCell because
+							 * we don't want it anymore.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not system versioned")));
+
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1258,6 +1347,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326474..c24f43224c 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -509,6 +509,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -563,6 +564,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a12c8d011b..0900655663 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15739,6 +15739,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 109245fea7..98080a8e98 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2058,11 +2058,15 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated by default as identity";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 				default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col));
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else
 				/* (note: above we cut off the 'default' string at 128) */
 				default_str = PQgetvalue(res, i, attrdef_col);
 
-			printTableAddCell(&cont, default_str, false, generated[0] ? true : false);
+			printTableAddCell(&cont, default_str, false, generated[0] == ATTRIBUTE_GENERATED_STORED ? true : false);
 		}
 
 		/* Info for index columns */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index a4cc80adad..cf8e5cecff 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -199,6 +199,9 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 4ec4ebdabc..b27f1dcd0e 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -20,5 +20,7 @@ extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, Cmd
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..ac7264bb0d 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -104,5 +104,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 8a76afe8cc..912c086a97 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -478,6 +478,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2039b42449..53c068233f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1002,6 +1002,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1765,7 +1766,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1844,7 +1845,11 @@ typedef enum AlterTableType
 	AT_DetachPartition,			/* DETACH PARTITION */
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
-	AT_DropIdentity				/* DROP IDENTITY */
+	AT_DropIdentity,			/* DROP IDENTITY */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
+
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2079,6 +2084,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when its is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2128,7 +2134,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3563,4 +3571,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..d5125db2a7 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b1184c2d15..824200f0b4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -300,6 +300,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -393,6 +394,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD)
@@ -441,6 +443,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..fdb4da1b70 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1a5e0b83a7..3080edfb90 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 4aec19a008..366eec01fd 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..cb7c21dc03
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,429 @@
+/*
+ * CREATE TABLE
+ */
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  the data type of row start time must be timestamptz 
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  The period start time parameter must equal the name of row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  The period end time  parameter must equal the name of row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  The period start time parameter must equal the name of row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time can not be specified multiple time
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time can not be specified multiple time
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                    Table "public.stest2"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                    Table "public.stest3"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+INSERT INTO stest0 VALUES (5);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index d2b17dd3ea..f3a10f85a1 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan collate.icu.utf8
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tidscan collate.icu.utf8 system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index acba391332..c7af5ed904 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -122,6 +122,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..4f7439eb46
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,195 @@
+/*
+ * CREATE TABLE
+ */
+
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+
+
+--truncation
+truncate table stest0;
+
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+INSERT INTO stest0 VALUES (5);
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
#24Surafel Temesgen
surafel3000@gmail.com
In reply to: David Steele (#21)
Re: WIP: System Versioned Temporal Table

Hi,

On Tue, Mar 3, 2020 at 9:33 PM David Steele <david@pgmasters.net> wrote:

Hi Surafel,

On 1/3/20 5:57 AM, Surafel Temesgen wrote:

Rebased and conflict resolved i hope it build clean this time

This patch no longer applies according to cfbot and there are a number
of review comments that don't seem to have been addressed yet.

The patch is not exactly new for this CF but since the first version was
posted 2020-01-01 and there have been no updates (except a rebase) since
then it comes pretty close.

Were you planning to work on this for PG13? If so we'll need to see a
rebased and updated patch pretty soon. My recommendation is that we
move this patch to PG14.

I agree with moving to PG14 . Its hard to make it to PG13.

regards
Surafel

#25David Steele
david@pgmasters.net
In reply to: Surafel Temesgen (#24)
Re: WIP: System Versioned Temporal Table

On 3/10/20 9:00 AM, Surafel Temesgen wrote:

On Tue, Mar 3, 2020 at 9:33 PM David Steele <david@pgmasters.net
<mailto:david@pgmasters.net>> wrote:

The patch is not exactly new for this CF but since the first version
was
posted 2020-01-01 and there have been no updates (except a rebase)
since
then it comes pretty close.

Were you planning to work on this for PG13?  If so we'll need to see a
rebased and updated patch pretty soon.  My recommendation is that we
move this patch to PG14.

I agree with moving to  PG14 . Its hard to make it to PG13.

The target version is now PG14.

Regards,
--
-David
david@pgmasters.net

#26Eli Marmor
eli@netmask.it
In reply to: Surafel Temesgen (#24)
Re: WIP: System Versioned Temporal Table

Hi Surafel and the rest,

I'm the owner of the Israeli meetup group of PostgreSQL, and I'm interested
in Temporality and have been trying for several years a few ways to add it
to PostgreSQL
(all of them through extensions and external ways).
I'm happy that this is done by you internally (and a little bit
disappointed that it's delayed again and again, but that's life...).
I'll be happy to join this effort.
I can't promise that I'll succeed to contribute anything, but first I want
to play with it a little.
To save me several hours, can you advise me what is the best way to install
it?
Which exact version of PG should I apply this patch to?

Thanks in advance, and thanks for your great work!
Eli

On Tue, Mar 31, 2020 at 10:04 PM Surafel Temesgen <surafel3000@gmail.com>
wrote:

Show quoted text

Hi,

On Tue, Mar 3, 2020 at 9:33 PM David Steele <david@pgmasters.net> wrote:

Hi Surafel,

On 1/3/20 5:57 AM, Surafel Temesgen wrote:

Rebased and conflict resolved i hope it build clean this time

This patch no longer applies according to cfbot and there are a number
of review comments that don't seem to have been addressed yet.

The patch is not exactly new for this CF but since the first version was
posted 2020-01-01 and there have been no updates (except a rebase) since
then it comes pretty close.

Were you planning to work on this for PG13? If so we'll need to see a
rebased and updated patch pretty soon. My recommendation is that we
move this patch to PG14.

I agree with moving to PG14 . Its hard to make it to PG13.

regards
Surafel

#27Surafel Temesgen
surafel3000@gmail.com
In reply to: Eli Marmor (#26)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Tue, Mar 31, 2020 at 10:12 PM Eli Marmor <eli@netmask.it> wrote:

Hi Surafel and the rest,

I'm the owner of the Israeli meetup group of PostgreSQL, and I'm
interested in Temporality and have been trying for several years a few ways
to add it to PostgreSQL
(all of them through extensions and external ways).
I'm happy that this is done by you internally (and a little bit
disappointed that it's delayed again and again, but that's life...).
I'll be happy to join this effort.
I can't promise that I'll succeed to contribute anything, but first I want
to play with it a little.
To save me several hours, can you advise me what is the best way to
install it?
Which exact version of PG should I apply this patch to?

Thanks in advance, and thanks for your great work!
Eli

Hey Eli,
Sorry for my late reply. reviewing it is greatly appreciated. I attach
rebased patch.
Please use git repo and it will work on current HEAD

regards
Surafel

Attachments:

system-versioned-temporal-table_v4.patchtext/x-patch; charset=US-ASCII; name=system-versioned-temporal-table_v4.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index b2eb7097a9..2644067b00 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table, using default columns
+      names of system versioning which are StartTime and EndtTime. If the table is
+      not empty StartTime and EndtTime columns will be filled with current transaction
+      time and infinity respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +192,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty history records also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index dc688c415f..2a7da37426 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -874,6 +878,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -966,6 +992,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1220,6 +1256,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index b93e4ca208..c5d5fd3662 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,64 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN SYMMETRIC<replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        the least and greatest of <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 30c30cf3a2..f73e1c45ac 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 44da71c4cb..8f114e60f5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3174,6 +3174,11 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
 					ExecComputeStoredGenerated(estate, myslot, CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 27b596cb59..cc4b1a7ad9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -59,6 +59,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -76,6 +77,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -169,6 +171,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -421,11 +426,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1560,6 +1566,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1568,6 +1575,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3708,8 +3723,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3738,6 +3755,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4286,6 +4304,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4342,6 +4361,36 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to specified otherwise its
+			 * useless
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4438,16 +4487,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4659,6 +4708,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 			ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4715,7 +4765,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5078,6 +5128,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5115,6 +5166,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5182,6 +5234,14 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
+		estate->es_result_relation_info = resultRelInfo;
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5270,6 +5330,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5340,6 +5407,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5375,6 +5446,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6191,6 +6263,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6510,6 +6590,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -6889,6 +6976,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -6989,6 +7083,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7380,6 +7481,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7585,11 +7693,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7632,6 +7741,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7692,11 +7811,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7717,9 +7840,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11177,6 +11300,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -11921,10 +12050,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12258,6 +12390,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15733,7 +15872,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103feb..c3a88b9db8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c474cc..152d5900e6 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -362,6 +362,128 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -461,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
 		/*
 		 * Check any RLS WITH CHECK policies.
 		 *
@@ -787,6 +916,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1159,6 +1312,37 @@ ExecUpdate(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..ef98de0421 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3400,6 +3400,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4812,6 +4813,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5706,6 +5731,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e3f33c40be..e34db6f746 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1251,6 +1251,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2936,6 +2937,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3758,6 +3780,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..9dc114467c 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,130 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	Node	   *lexp = lexpr;
+
+	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
+	while (IsA(lexp, A_Expr) &&
+		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
+		lexp = ((A_Expr *) lexp)->lexpr;
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexp, BoolExpr))
+	{
+		BoolExpr   *blexpr = (BoolExpr *) lexp;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..9e63a118b1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2626,6 +2626,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..7b8e2adba4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index b02fcb9bfe..d4d36f8999 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -850,6 +851,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to filter out historical data .
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -896,6 +906,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and filter out historical data.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 25545029d7..dd1a268df4 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2345,3 +2350,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2957..e3468e8ee7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -45,6 +45,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -445,6 +446,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1220,6 +1226,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1317,6 +1332,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2280,6 +2300,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbb47d4982..c510ffd55e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -135,6 +135,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -153,7 +167,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -178,7 +191,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -251,6 +263,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -384,12 +399,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
+				distinct_clause opt_all_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -442,7 +459,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 <list>	for_clause for_locking_clause opt_for_locking_clause for_locking_items
 %type <list>	locked_rels_list
 %type <boolean>	all_or_distinct
 
@@ -508,7 +525,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -549,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -680,7 +697,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -695,7 +712,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -706,7 +723,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -727,7 +744,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 
 /* Precedence: lowest to highest */
@@ -742,6 +759,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
 %left		POSTFIXOP		/* dummy for postfix Op rules */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		FOR
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, we must give IDENT an explicit priority
  * between POSTFIXOP and Op.  We can safely assign the same priority to
@@ -783,6 +804,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2102,6 +2128,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2129,7 +2163,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2268,7 +2310,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2277,8 +2331,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2287,6 +2353,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3186,12 +3261,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3205,12 +3281,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3224,13 +3301,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3244,13 +3322,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3264,13 +3343,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3284,13 +3364,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3367,6 +3448,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3463,6 +3545,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3545,12 +3637,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3568,6 +3660,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3590,6 +3683,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3965,9 +4082,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4103,7 +4245,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11160,7 +11302,7 @@ select_no_parens:
 										yyscanner);
 					$$ = $1;
 				}
-			| select_clause opt_sort_clause for_locking_clause opt_select_limit
+			| select_clause opt_sort_clause for_clause opt_select_limit
 				{
 					insertSelectOptions((SelectStmt *) $1, $2, $3,
 										$4,
@@ -11192,7 +11334,7 @@ select_no_parens:
 										yyscanner);
 					$$ = $2;
 				}
-			| with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit
+			| with_clause select_clause opt_sort_clause for_clause opt_select_limit
 				{
 					insertSelectOptions((SelectStmt *) $2, $3, $4,
 										$5,
@@ -11724,11 +11866,15 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
+		;
+
+for_clause:
+			FOR for_locking_clause						{ $$ = $2; }
 			| FOR READ ONLY							{ $$ = NIL; }
 		;
 
 opt_for_locking_clause:
-			for_locking_clause						{ $$ = $1; }
+			for_clause						{ $$ = $1; }
 			| /* EMPTY */							{ $$ = NIL; }
 		;
 
@@ -11749,10 +11895,10 @@ for_locking_item:
 		;
 
 for_locking_strength:
-			FOR UPDATE 							{ $$ = LCS_FORUPDATE; }
-			| FOR NO KEY UPDATE 				{ $$ = LCS_FORNOKEYUPDATE; }
-			| FOR SHARE 						{ $$ = LCS_FORSHARE; }
-			| FOR KEY SHARE 					{ $$ = LCS_FORKEYSHARE; }
+			 UPDATE 							{ $$ = LCS_FORUPDATE; }
+			| NO KEY UPDATE 				{ $$ = LCS_FORNOKEYUPDATE; }
+			| SHARE 						{ $$ = LCS_FORSHARE; }
+			| KEY SHARE 					{ $$ = LCS_FORKEYSHARE; }
 		;
 
 locked_rels_list:
@@ -11791,7 +11937,7 @@ values_clause:
  *****************************************************************************/
 
 from_clause:
-			FROM from_list							{ $$ = $2; }
+			FROM from_list							{ $$ = $2 ; }
 			| /*EMPTY*/								{ $$ = NIL; }
 		;
 
@@ -11803,12 +11949,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11816,6 +11966,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11914,7 +12077,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
 
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
+
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15202,6 +15412,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15278,6 +15489,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15308,6 +15520,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15601,16 +15814,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16015,29 +16218,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	Node	   *lexp = lexpr;
-
-	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
-	while (IsA(lexp, A_Expr) &&
-		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
-		lexp = ((A_Expr *) lexp)->lexpr;
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexp, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexp;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 6fff13479e..d2bb40c04b 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1139,6 +1141,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		changeTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3699,3 +3730,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 25abc544fc..03ab9e1ba3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -249,6 +259,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -284,7 +298,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -292,6 +308,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -303,6 +353,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -726,6 +796,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row start time must be timestamptz ")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1300,6 +1426,35 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	table_close(relation, NoLock);
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time parameter must be the same as  the name of row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time  parameter must be the same as the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3059,7 +3214,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3086,7 +3241,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3123,6 +3278,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3157,6 +3316,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3166,6 +3333,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3333,6 +3517,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3364,7 +3563,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index be86eb37fe..3177e52851 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -118,6 +118,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -169,6 +172,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9b0c376c8c..558c27c172 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -58,7 +59,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1240,6 +1243,12 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
+
+
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1250,6 +1259,85 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already system versioned")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning for table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statment
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table add column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not system versioned")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1260,6 +1348,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a2453cf1f4..b1801025c9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -515,6 +515,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -569,6 +570,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 857c7c2278..2989fadc35 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15686,6 +15686,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3b870c3b17..7c615e2ca8 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2058,11 +2058,15 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated by default as identity";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 				default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col));
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else
 				/* (note: above we cut off the 'default' string at 128) */
 				default_str = PQgetvalue(res, i, attrdef_col);
 
-			printTableAddCell(&cont, default_str, false, generated[0] ? true : false);
+			printTableAddCell(&cont, default_str, false, generated[0] == ATTRIBUTE_GENERATED_STORED ? true : false);
 		}
 
 		/* Info for index columns */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index a4cc80adad..cf8e5cecff 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -199,6 +199,9 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 4ec4ebdabc..b27f1dcd0e 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -20,5 +20,7 @@ extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, Cmd
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..ac7264bb0d 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -104,5 +104,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..de08e8ecbb 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -481,6 +481,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 151bcdb7ef..105e6017ea 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1004,6 +1004,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1768,7 +1769,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1847,7 +1848,11 @@ typedef enum AlterTableType
 	AT_DetachPartition,			/* DETACH PARTITION */
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
-	AT_DropIdentity				/* DROP IDENTITY */
+	AT_DropIdentity,			/* DROP IDENTITY */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
+
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2082,6 +2087,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when it is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2131,7 +2137,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3570,4 +3578,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..d5125db2a7 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 08f22ce211..9f3a77bd67 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..fdb4da1b70 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1a5e0b83a7..3080edfb90 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 9594856c88..3cfa98e7f6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..9cff30b4a3
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,429 @@
+/*
+ * CREATE TABLE
+ */
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  the data type of row start time must be timestamptz 
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time  parameter must be the same as the name of row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time can not be specified multiple time
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time can not be specified multiple time
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                    Table "public.stest2"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                    Table "public.stest3"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+INSERT INTO stest0 VALUES (5);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..14dde559ea 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..0d51a057fd 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -125,6 +125,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..4f7439eb46
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,195 @@
+/*
+ * CREATE TABLE
+ */
+
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+
+
+--truncation
+truncate table stest0;
+
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+INSERT INTO stest0 VALUES (5);
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
#28Rémi Lapeyre
remi.lapeyre@lenstra.fr
In reply to: Surafel Temesgen (#27)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

Hi, thanks for working on this. I had planned to work on it and I’m looking forward to this natively in Postgres.

The patch builds with the following warnings:

plancat.c:2368:18: warning: variable 'name' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized]
for (int i = 0; i < natts; i++)
^~~~~~~~~
plancat.c:2379:9: note: uninitialized use occurs here
return name;
^~~~
plancat.c:2368:18: note: remove the condition if it is always true
for (int i = 0; i < natts; i++)
^~~~~~~~~
plancat.c:2363:15: note: initialize the variable 'name' to silence this warning
char *name;
^
= NULL
plancat.c:2396:18: warning: variable 'name' is used uninitialized whenever 'for' loop exits because its condition is false [-Wsometimes-uninitialized]
for (int i = 0; i < natts; i++)
^~~~~~~~~
plancat.c:2407:9: note: uninitialized use occurs here
return name;
^~~~
plancat.c:2396:18: note: remove the condition if it is always true
for (int i = 0; i < natts; i++)
^~~~~~~~~
plancat.c:2391:15: note: initialize the variable 'name' to silence this warning
char *name;
^
= NULL
2 warnings generated.

make check pass without issues, but make check-world fails for postgres_fdw, the diff is attached.

Before going further in the review, I’m a bit surprised by the quantity of code needed here. In https://github.com/xocolatl/periods there is far less code and I would have expected the same here. For example, are the changes to copy necessary or would it be possible to have a first patch the only implement the minimal changes required for this feature?

Thanks a lot!

Rémi

Attachments:

regression.diffsapplication/octet-stream; name=regression.diffs; x-unix-mode=0644Download
diff -U3 /Users/remi/src/postgresql/contrib/postgres_fdw/expected/postgres_fdw.out /Users/remi/src/postgresql/contrib/postgres_fdw/results/postgres_fdw.out
--- /Users/remi/src/postgresql/contrib/postgres_fdw/expected/postgres_fdw.out	2020-07-18 17:27:52.000000000 +0200
+++ /Users/remi/src/postgresql/contrib/postgres_fdw/results/postgres_fdw.out	2020-07-18 17:57:11.000000000 +0200
@@ -1720,29 +1720,11 @@
 
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
-                                                                                                                                                                                                                                    QUERY PLAN                                                                                                                                                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
-   Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 FOR UPDATE OF r2
-(4 rows)
-
+ERROR:  syntax error at or near "FOR"
+CONTEXT:  remote SQL command: EXPLAIN SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FOR UPDATE OF r1 FOR UPDATE OF r2
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
- c1  | c1  
------+-----
- 101 | 101
- 102 | 102
- 103 | 103
- 104 | 104
- 105 | 105
- 106 | 106
- 107 | 107
- 108 | 108
- 109 | 109
- 110 | 110
-(10 rows)
-
+ERROR:  syntax error at or near "FOR"
+CONTEXT:  remote SQL command: EXPLAIN SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FOR UPDATE OF r1 FOR UPDATE OF r2
 -- join two tables with FOR SHARE clause
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1;
@@ -1771,29 +1753,11 @@
 
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
-                                                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                                                   
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
-   Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 FOR SHARE OF r2
-(4 rows)
-
+ERROR:  syntax error at or near "FOR"
+CONTEXT:  remote SQL command: EXPLAIN SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FOR SHARE OF r1 FOR SHARE OF r2
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
- c1  | c1  
------+-----
- 101 | 101
- 102 | 102
- 103 | 103
- 104 | 104
- 105 | 105
- 106 | 106
- 107 | 107
- 108 | 108
- 109 | 109
- 110 | 110
-(10 rows)
-
+ERROR:  syntax error at or near "FOR"
+CONTEXT:  remote SQL command: EXPLAIN SELECT r1."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) FOR SHARE OF r1 FOR SHARE OF r2
 -- join in CTE
 EXPLAIN (VERBOSE, COSTS OFF)
 WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1)) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1 OFFSET 100 LIMIT 10;
@@ -2239,73 +2203,12 @@
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
-                                                                                                                                                                                                                                                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                                                                                                                                                                                                                                               
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- LockRows
-   Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
-   ->  Merge Join
-         Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, local_tbl.c1, local_tbl.c2, local_tbl.c3, ft1.*, ft2.*, ft4.*, ft5.*, local_tbl.ctid
-         Inner Unique: true
-         Merge Cond: (ft1.c2 = local_tbl.c1)
-         ->  Foreign Scan
-               Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
-               Relations: (((public.ft1) INNER JOIN (public.ft2)) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
-               Remote SQL: SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r3.c1, r3.c2, r3.c3, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r4.c1, r4.c2, r4.c3, CASE WHEN (r4.*)::text IS NOT NULL THEN ROW(r4.c1, r4.c2, r4.c3) END FROM ((("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) INNER JOIN "S 1"."T 3" r3 ON (((r1.c2 = r3.c1)))) INNER JOIN "S 1"."T 4" r4 ON (((r1.c2 = r4.c1)))) ORDER BY r1.c2 ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2 FOR UPDATE OF r3 FOR UPDATE OF r4
-               ->  Merge Join
-                     Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
-                     Merge Cond: (ft1.c2 = ft5.c1)
-                     ->  Merge Join
-                           Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*
-                           Merge Cond: (ft1.c2 = ft4.c1)
-                           ->  Sort
-                                 Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                 Sort Key: ft1.c2
-                                 ->  Merge Join
-                                       Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                       Merge Cond: (ft1.c1 = ft2.c1)
-                                       ->  Sort
-                                             Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                             Sort Key: ft1.c1
-                                             ->  Foreign Scan on public.ft1
-                                                   Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
-                                       ->  Materialize
-                                             Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                             ->  Foreign Scan on public.ft2
-                                                   Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
-                           ->  Sort
-                                 Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
-                                 Sort Key: ft4.c1
-                                 ->  Foreign Scan on public.ft4
-                                       Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
-                                       Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE
-                     ->  Sort
-                           Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
-                           Sort Key: ft5.c1
-                           ->  Foreign Scan on public.ft5
-                                 Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
-                                 Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE
-         ->  Index Scan using local_tbl_pkey on public.local_tbl
-               Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
-(47 rows)
-
+ERROR:  syntax error at or near "FOR"
+CONTEXT:  remote SQL command: EXPLAIN SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) FOR UPDATE OF r1 FOR UPDATE OF r2
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
- c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  | c1 | c2 |  c3   |              c4              |            c5            | c6 |     c7     | c8  | c1 | c2 |   c3   | c1 | c2 |   c3   | c1 | c2 |  c3  
-----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+-------+------------------------------+--------------------------+----+------------+-----+----+----+--------+----+----+--------+----+----+------
-  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo |  6 |  6 | 00006 | Wed Jan 07 00:00:00 1970 PST | Wed Jan 07 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo | 16 |  6 | 00016 | Sat Jan 17 00:00:00 1970 PST | Sat Jan 17 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 26 |  6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo | 26 |  6 | 00026 | Tue Jan 27 00:00:00 1970 PST | Tue Jan 27 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 36 |  6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo | 36 |  6 | 00036 | Fri Feb 06 00:00:00 1970 PST | Fri Feb 06 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 46 |  6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo | 46 |  6 | 00046 | Mon Feb 16 00:00:00 1970 PST | Mon Feb 16 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 56 |  6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo | 56 |  6 | 00056 | Thu Feb 26 00:00:00 1970 PST | Thu Feb 26 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 66 |  6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo | 66 |  6 | 00066 | Sun Mar 08 00:00:00 1970 PST | Sun Mar 08 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 76 |  6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo | 76 |  6 | 00076 | Wed Mar 18 00:00:00 1970 PST | Wed Mar 18 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 86 |  6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo | 86 |  6 | 00086 | Sat Mar 28 00:00:00 1970 PST | Sat Mar 28 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
- 96 |  6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo | 96 |  6 | 00096 | Tue Apr 07 00:00:00 1970 PST | Tue Apr 07 00:00:00 1970 | 6  | 6          | foo |  6 |  7 | AAA006 |  6 |  7 | AAA006 |  6 |  6 | 0006
-(10 rows)
-
+ERROR:  syntax error at or near "FOR"
+CONTEXT:  remote SQL command: EXPLAIN SELECT r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")) AND ((r2."C 1" < 100)) AND ((r1."C 1" < 100)))) FOR UPDATE OF r1 FOR UPDATE OF r2
 RESET enable_nestloop;
 RESET enable_hashjoin;
 DROP TABLE local_tbl;
#29Surafel Temesgen
surafel3000@gmail.com
In reply to: Rémi Lapeyre (#28)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

Hey Rémi,
Thank you for looking at it

On Sat, Jul 18, 2020 at 7:05 PM Rémi Lapeyre <remi.lapeyre@lenstra.fr>
wrote:

Hi, thanks for working on this. I had planned to work on it and I’m
looking forward to this natively in Postgres.

The patch builds with the following warnings:

plancat.c:2368:18: warning: variable 'name' is used uninitialized whenever
'for' loop exits because its condition is false [-Wsometimes-uninitialized]
for (int i = 0; i < natts; i++)
^~~~~~~~~
plancat.c:2379:9: note: uninitialized use occurs here
return name;
^~~~
plancat.c:2368:18: note: remove the condition if it is always true
for (int i = 0; i < natts; i++)
^~~~~~~~~
plancat.c:2363:15: note: initialize the variable 'name' to silence this
warning
char *name;
^
= NULL
plancat.c:2396:18: warning: variable 'name' is used uninitialized whenever
'for' loop exits because its condition is false [-Wsometimes-uninitialized]
for (int i = 0; i < natts; i++)
^~~~~~~~~
plancat.c:2407:9: note: uninitialized use occurs here
return name;
^~~~
plancat.c:2396:18: note: remove the condition if it is always true
for (int i = 0; i < natts; i++)
^~~~~~~~~
plancat.c:2391:15: note: initialize the variable 'name' to silence this
warning
char *name;
^
= NULL
2 warnings generated.

I wonder why my compiler didn't show me this

make check pass without issues, but make check-world fails for

postgres_fdw, the diff is attached.

Okay thanks the attached patch contains a fix for both issue

Before going further in the review, I’m a bit surprised by the quantity of
code needed here. In https://github.com/xocolatl/periods there is far
less code and I would have expected the same here. For example, are the
changes to copy necessary or would it be possible to have a first patch the
only implement the minimal changes required for this feature?

Yes there not many c code in there because most of the logice is written
in SQL

regards
Surafel

Attachments:

system-versioned-temporal-table_v5.patchtext/x-patch; charset=US-ASCII; name=system-versioned-temporal-table_v5.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index b2eb7097a9..2644067b00 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table, using default columns
+      names of system versioning which are StartTime and EndtTime. If the table is
+      not empty StartTime and EndtTime columns will be filled with current transaction
+      time and infinity respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +192,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty history records also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index dc688c415f..2a7da37426 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -874,6 +878,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -966,6 +992,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1220,6 +1256,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index b93e4ca208..c5d5fd3662 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,64 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN SYMMETRIC<replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        the least and greatest of <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 30c30cf3a2..f73e1c45ac 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 44da71c4cb..8f114e60f5 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3174,6 +3174,11 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
 					ExecComputeStoredGenerated(estate, myslot, CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 27b596cb59..cc4b1a7ad9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -59,6 +59,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -76,6 +77,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -169,6 +171,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -421,11 +426,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1560,6 +1566,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1568,6 +1575,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3708,8 +3723,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3738,6 +3755,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4286,6 +4304,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4342,6 +4361,36 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to specified otherwise its
+			 * useless
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4438,16 +4487,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4659,6 +4708,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 			ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4715,7 +4765,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5078,6 +5128,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5115,6 +5166,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5182,6 +5234,14 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
+		estate->es_result_relation_info = resultRelInfo;
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5270,6 +5330,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5340,6 +5407,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5375,6 +5446,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6191,6 +6263,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6510,6 +6590,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -6889,6 +6976,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -6989,6 +7083,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7380,6 +7481,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7585,11 +7693,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7632,6 +7741,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7692,11 +7811,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7717,9 +7840,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11177,6 +11300,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -11921,10 +12050,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12258,6 +12390,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15733,7 +15872,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103feb..c3a88b9db8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 20a4c474cc..152d5900e6 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -362,6 +362,128 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -461,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
 		/*
 		 * Check any RLS WITH CHECK policies.
 		 *
@@ -787,6 +916,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1159,6 +1312,37 @@ ExecUpdate(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 89c409de66..ef98de0421 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3400,6 +3400,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4812,6 +4813,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5706,6 +5731,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e3f33c40be..e34db6f746 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1251,6 +1251,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2936,6 +2937,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3758,6 +3780,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..9dc114467c 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,130 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	Node	   *lexp = lexpr;
+
+	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
+	while (IsA(lexp, A_Expr) &&
+		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
+		lexp = ((A_Expr *) lexp)->lexpr;
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexp, BoolExpr))
+	{
+		BoolExpr   *blexpr = (BoolExpr *) lexp;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e2f177515d..9e63a118b1 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2626,6 +2626,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b406d41e91..7b8e2adba4 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index b02fcb9bfe..d4d36f8999 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -850,6 +851,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to filter out historical data .
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -896,6 +906,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and filter out historical data.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 25545029d7..eea6265e17 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2345,3 +2350,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2957..e3468e8ee7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -45,6 +45,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -445,6 +446,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1220,6 +1226,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1317,6 +1332,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2280,6 +2300,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index dbb47d4982..055f52c2cc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -135,6 +135,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -153,7 +167,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -178,7 +191,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -251,6 +263,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -384,12 +399,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
+				distinct_clause opt_all_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -508,7 +525,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -549,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> unreserved_keyword type_func_name_keyword
 %type <keyword> col_name_keyword reserved_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -680,7 +697,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -695,7 +712,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -706,7 +723,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -727,7 +744,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 
 /* Precedence: lowest to highest */
@@ -742,6 +759,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
 %left		POSTFIXOP		/* dummy for postfix Op rules */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, we must give IDENT an explicit priority
  * between POSTFIXOP and Op.  We can safely assign the same priority to
@@ -783,6 +803,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2102,6 +2127,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2129,7 +2162,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2268,7 +2309,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2277,8 +2330,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2287,6 +2352,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3186,12 +3260,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3205,12 +3280,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3224,13 +3300,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3244,13 +3321,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3264,13 +3342,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3284,13 +3363,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3367,6 +3447,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3463,6 +3544,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3545,12 +3636,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3568,6 +3659,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3590,6 +3682,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3965,9 +4081,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4103,7 +4244,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11724,7 +11865,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11803,12 +11944,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11816,6 +11961,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11914,7 +12072,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
 
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
+
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15202,6 +15407,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15278,6 +15484,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15308,6 +15515,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15601,16 +15809,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16015,29 +16213,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	Node	   *lexp = lexpr;
-
-	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
-	while (IsA(lexp, A_Expr) &&
-		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
-		lexp = ((A_Expr *) lexp)->lexpr;
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexp, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexp;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 6fff13479e..d2bb40c04b 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1139,6 +1141,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		changeTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3699,3 +3730,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 25abc544fc..03ab9e1ba3 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -249,6 +259,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -284,7 +298,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -292,6 +308,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -303,6 +353,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -726,6 +796,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row start time must be timestamptz ")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1300,6 +1426,35 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 	table_close(relation, NoLock);
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time parameter must be the same as  the name of row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time  parameter must be the same as the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3059,7 +3214,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3086,7 +3241,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3123,6 +3278,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3157,6 +3316,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3166,6 +3333,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3333,6 +3517,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3364,7 +3563,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index be86eb37fe..3177e52851 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -118,6 +118,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -169,6 +172,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9b0c376c8c..558c27c172 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -58,7 +59,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1240,6 +1243,12 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
+
+
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1250,6 +1259,85 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already system versioned")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning for table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statment
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table add column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not system versioned")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1260,6 +1348,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index a2453cf1f4..b1801025c9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -515,6 +515,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -569,6 +570,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 857c7c2278..2989fadc35 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15686,6 +15686,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 3b870c3b17..7c615e2ca8 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2058,11 +2058,15 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated by default as identity";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 				default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col));
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else
 				/* (note: above we cut off the 'default' string at 128) */
 				default_str = PQgetvalue(res, i, attrdef_col);
 
-			printTableAddCell(&cont, default_str, false, generated[0] ? true : false);
+			printTableAddCell(&cont, default_str, false, generated[0] == ATTRIBUTE_GENERATED_STORED ? true : false);
 		}
 
 		/* Info for index columns */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index a4cc80adad..cf8e5cecff 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -199,6 +199,9 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 4ec4ebdabc..b27f1dcd0e 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -20,5 +20,7 @@ extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, Cmd
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..ac7264bb0d 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -104,5 +104,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..de08e8ecbb 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -481,6 +481,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 151bcdb7ef..105e6017ea 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1004,6 +1004,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1768,7 +1769,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1847,7 +1848,11 @@ typedef enum AlterTableType
 	AT_DetachPartition,			/* DETACH PARTITION */
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
-	AT_DropIdentity				/* DROP IDENTITY */
+	AT_DropIdentity,			/* DROP IDENTITY */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
+
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2082,6 +2087,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when it is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2131,7 +2137,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3570,4 +3578,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..d5125db2a7 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 08f22ce211..9f3a77bd67 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..fdb4da1b70 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1a5e0b83a7..3080edfb90 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 9594856c88..3cfa98e7f6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..9cff30b4a3
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,429 @@
+/*
+ * CREATE TABLE
+ */
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  the data type of row start time must be timestamptz 
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time  parameter must be the same as the name of row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time can not be specified multiple time
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time can not be specified multiple time
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                    Table "public.stest2"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                    Table "public.stest3"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+INSERT INTO stest0 VALUES (5);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..14dde559ea 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..0d51a057fd 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -125,6 +125,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..4f7439eb46
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,195 @@
+/*
+ * CREATE TABLE
+ */
+
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+
+
+--truncation
+truncate table stest0;
+
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+INSERT INTO stest0 VALUES (5);
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
#30Michael Paquier
michael@paquier.xyz
In reply to: Surafel Temesgen (#29)
Re: WIP: System Versioned Temporal Table

On Tue, Jul 21, 2020 at 05:32:44PM +0300, Surafel Temesgen wrote:

Thank you for looking at it

The patch is failing to apply. Could you send a rebase please?
--
Michael

#31Surafel Temesgen
surafel3000@gmail.com
In reply to: Michael Paquier (#30)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

Hi Michael,

On Tue, Sep 29, 2020 at 8:44 AM Michael Paquier <michael@paquier.xyz> wrote:

The patch is failing to apply. Could you send a rebase please?

Attached is a rebased one.

regards
Surafel

Attachments:

system-versioned-temporal-table_v6.patchtext/x-patch; charset=US-ASCII; name=system-versioned-temporal-table_v6.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index f034e75ec0..ec8d30a24a 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table, using default columns
+      names of system versioning which are StartTime and EndtTime. If the table is
+      not empty StartTime and EndtTime columns will be filled with current transaction
+      time and infinity respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +192,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty history records also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 087cad184c..33efc8dcf4 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -874,6 +878,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -966,6 +992,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1220,6 +1256,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index b93e4ca208..c5d5fd3662 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,64 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN SYMMETRIC<replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        the least and greatest of <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 30c30cf3a2..f73e1c45ac 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 2047557e52..8d8511094a 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3222,6 +3222,11 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_RelationDesc->rd_att->constr->has_generated_stored)
 					ExecComputeStoredGenerated(estate, myslot, CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e0ac4e05e5..b90ab8bcde 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -59,6 +59,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -76,6 +77,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -169,6 +171,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -423,11 +428,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1574,6 +1580,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1582,6 +1589,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3764,8 +3779,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3794,6 +3811,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4350,6 +4368,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4406,6 +4425,36 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to specified otherwise its
+			 * useless
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4505,16 +4554,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4733,6 +4782,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 			ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4789,7 +4839,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5165,6 +5215,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5202,6 +5253,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5269,6 +5321,14 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
+		estate->es_result_relation_info = resultRelInfo;
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5357,6 +5417,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5427,6 +5494,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5462,6 +5533,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6278,6 +6350,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6597,6 +6677,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7040,6 +7127,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7140,6 +7234,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7531,6 +7632,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7736,11 +7844,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7783,6 +7892,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7843,11 +7962,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7868,9 +7991,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11328,6 +11451,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12072,10 +12201,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12409,6 +12541,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15884,7 +16023,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103feb..c3a88b9db8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 9812089161..7c735381ff 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -362,6 +362,128 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot)
+{
+	ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -461,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot, CMD_INSERT);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
 		/*
 		 * Check any RLS WITH CHECK policies.
 		 *
@@ -787,6 +916,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1159,6 +1312,37 @@ ExecUpdate(ModifyTableState *mtstate,
 			resultRelationDesc->rd_att->constr->has_generated_stored)
 			ExecComputeStoredGenerated(estate, slot, CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 0409a40b82..23038300b5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3400,6 +3400,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4811,6 +4812,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5705,6 +5730,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..fa950f7c9c 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1251,6 +1251,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2935,6 +2936,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3757,6 +3779,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 49de285f01..9dc114467c 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -814,3 +814,130 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	Node	   *lexp = lexpr;
+
+	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
+	while (IsA(lexp, A_Expr) &&
+		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
+		lexp = ((A_Expr *) lexp)->lexpr;
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexp, BoolExpr))
+	{
+		BoolExpr   *blexpr = (BoolExpr *) lexp;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index f0386480ab..83690534ae 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2627,6 +2627,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index f331f82a6c..4a49362cff 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index fcce81926b..f337f67d98 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to filter out historical data .
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and filter out historical data.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index f9d0d67aa7..31dc168a5d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2342,3 +2347,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2957..e3468e8ee7 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -45,6 +45,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -445,6 +446,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1220,6 +1226,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1317,6 +1332,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2280,6 +2300,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 17653ef3a7..b773facf69 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -135,6 +135,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -153,7 +167,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -178,7 +191,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -251,6 +263,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -384,12 +399,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_all_clause opt_definition func_args func_args_list
+				distinct_clause opt_all_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -508,7 +525,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -551,7 +568,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -682,7 +699,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -697,7 +714,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -708,7 +725,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -729,7 +746,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 
 /* Precedence: lowest to highest */
@@ -743,6 +760,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -782,6 +802,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2099,6 +2124,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2126,7 +2159,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2265,7 +2306,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2274,8 +2327,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2284,6 +2349,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3183,12 +3257,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3202,12 +3277,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3221,13 +3297,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3241,13 +3318,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3261,13 +3339,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3281,13 +3360,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3364,6 +3444,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3460,6 +3541,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3542,12 +3633,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3565,6 +3656,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3587,6 +3679,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3962,9 +4078,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4100,7 +4241,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11725,7 +11866,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11804,12 +11945,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11817,6 +11962,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11915,7 +12073,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
 
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
+
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15209,6 +15414,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15285,6 +15491,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15315,6 +15522,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15780,6 +15988,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -15870,6 +16079,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -15915,6 +16125,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16031,16 +16242,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16445,29 +16646,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	Node	   *lexp = lexpr;
-
-	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
-	while (IsA(lexp, A_Expr) &&
-		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
-		lexp = ((A_Expr *) lexp)->lexpr;
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexp, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexp;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index edcaf276c0..7654147b15 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1139,6 +1141,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		changeTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3699,3 +3730,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 0dc03dd984..a228a886e2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -95,6 +98,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -118,6 +126,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -250,6 +260,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -285,7 +299,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -293,6 +309,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -304,6 +354,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -727,6 +797,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row start time must be timestamptz ")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1406,6 +1532,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time parameter must be the same as  the name of row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time  parameter must be the same as the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3146,7 +3301,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3173,7 +3328,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3209,6 +3364,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3243,6 +3402,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3252,6 +3419,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3419,6 +3603,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3450,7 +3649,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index be86eb37fe..3177e52851 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -118,6 +118,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -169,6 +172,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 9a35147b26..f36d86e0dd 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -58,7 +59,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1275,6 +1278,12 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
+
+
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1285,6 +1294,85 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already system versioned")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning for table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statment
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table add column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not system versioned")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1295,6 +1383,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9061af81a3..bb212d19a5 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -515,6 +515,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -569,6 +570,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 76320468ba..27cdc4eb4f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15739,6 +15739,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 58de433fd3..fcc6ab6a23 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2062,11 +2062,15 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated by default as identity";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 				default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col));
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else
 				/* (note: above we cut off the 'default' string at 128) */
 				default_str = PQgetvalue(res, i, attrdef_col);
 
-			printTableAddCell(&cont, default_str, false, generated[0] ? true : false);
+			printTableAddCell(&cont, default_str, false, generated[0] == ATTRIBUTE_GENERATED_STORED ? true : false);
 		}
 
 		/* Info for index columns */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index a4cc80adad..cf8e5cecff 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -199,6 +199,9 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 4ec4ebdabc..b27f1dcd0e 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -20,5 +20,7 @@ extern void ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, Cmd
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 31d9aedeeb..ac7264bb0d 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -104,5 +104,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..0e6c29fb96 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -480,6 +480,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45466..bbaec08c9a 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1005,6 +1005,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1769,7 +1770,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1849,7 +1850,11 @@ typedef enum AlterTableType
 	AT_DetachPartition,			/* DETACH PARTITION */
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
-	AT_DropIdentity				/* DROP IDENTITY */
+	AT_DropIdentity,			/* DROP IDENTITY */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
+
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2084,6 +2089,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when it is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2133,7 +2139,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3573,4 +3581,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..d5125db2a7 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..6b665e0e2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..fdb4da1b70 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bc3d66ed88..71e46f4f9c 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 9594856c88..3cfa98e7f6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..9cff30b4a3
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,429 @@
+/*
+ * CREATE TABLE
+ */
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  the data type of row start time must be timestamptz 
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time  parameter must be the same as the name of row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time can not be specified multiple time
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time can not be specified multiple time
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                    Table "public.stest2"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                    Table "public.stest3"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+INSERT INTO stest0 VALUES (5);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 026ea880cd..14dde559ea 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 979d926119..0d51a057fd 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -125,6 +125,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..4f7439eb46
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,195 @@
+/*
+ * CREATE TABLE
+ */
+
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+
+
+--truncation
+truncate table stest0;
+
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+INSERT INTO stest0 VALUES (5);
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
#32Georgios Kokolatos
gkokolatos@protonmail.com
In reply to: Surafel Temesgen (#31)
Re: WIP: System Versioned Temporal Table

Hi,

just a quick comment that this patch fails on the cfbot.

Cheers,
//Georgios

#33Surafel Temesgen
surafel3000@gmail.com
In reply to: Surafel Temesgen (#31)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

Attached is a rebased one.
regards
Surafel

Attachments:

system-versioned-temporal-table_v7.patchtext/x-patch; charset=US-ASCII; name=system-versioned-temporal-table_v7.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c25ef5abd6..4a38ebbc69 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table, using default columns
+      names of system versioning which are StartTime and EndtTime. If the table is
+      not empty StartTime and EndtTime columns will be filled with current transaction
+      time and infinity respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +192,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty history records also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index bc59a2d77d..ca98f6bedc 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -967,6 +993,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1221,6 +1257,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 472b7cae81..b2126b1f26 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,64 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN SYMMETRIC<replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        the least and greatest of <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 30c30cf3a2..f73e1c45ac 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 115860a9d4..0bb940b096 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -3202,6 +3202,11 @@ CopyFrom(CopyState cstate)
 					resultRelInfo->ri_RelationDesc->rd_att->constr)
 					ExecConstraints(resultRelInfo, myslot, estate);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * Also check the tuple against the partition constraint, if
 				 * there is one; except that if we got here via tuple-routing,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 46f1637e77..5e4d6c402c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -58,6 +58,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -75,6 +76,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -169,6 +171,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -423,11 +428,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1575,6 +1581,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1583,6 +1590,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3768,8 +3783,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3798,6 +3815,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4366,6 +4384,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4422,6 +4441,36 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to specified otherwise its
+			 * useless
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4521,16 +4570,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4754,6 +4803,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4810,7 +4860,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5186,6 +5236,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5223,6 +5274,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5290,6 +5342,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5378,6 +5436,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5448,6 +5513,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5483,6 +5552,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6299,6 +6369,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6618,6 +6696,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7061,6 +7146,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7161,6 +7253,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7568,6 +7667,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7773,11 +7879,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7820,6 +7927,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7880,11 +7997,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7905,9 +8026,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11368,6 +11489,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12112,10 +12239,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12449,6 +12579,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15924,7 +16061,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103feb..c3a88b9db8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7228..ad4dfdb1f4 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -360,6 +360,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -463,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		WCOKind		wco_kind;
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Constraints might reference the tableoid column, so (re-)initialize
 		 * tts_tableOid before evaluating them.
@@ -799,6 +926,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1255,6 +1406,37 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * update in foreign table: let the FDW do it
 		 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5a591d0a75..bb270828db 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3390,6 +3390,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4802,6 +4803,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5693,6 +5718,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2895a8985..607e3d1eae 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1243,6 +1243,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2929,6 +2930,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3748,6 +3770,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index ee033ae779..5b885e469e 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,130 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	Node	   *lexp = lexpr;
+
+	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
+	while (IsA(lexp, A_Expr) &&
+		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
+		lexp = ((A_Expr *) lexp)->lexpr;
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexp, BoolExpr))
+	{
+		BoolExpr   *blexpr = (BoolExpr *) lexp;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 4504b1503b..fb6bf9e8ea 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2624,6 +2624,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 986d7a52e3..787f9153b7 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index fcce81926b..f337f67d98 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to filter out historical data .
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and filter out historical data.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 52c01eb86b..8128040706 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -35,12 +35,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -53,6 +55,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -80,6 +83,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2353,3 +2358,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f73d..94f5c6bf43 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -45,6 +45,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -445,6 +446,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1220,6 +1226,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1317,6 +1332,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2265,6 +2285,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2cb377d034..1cb00ba643 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -135,6 +135,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -153,7 +167,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -178,7 +191,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -251,6 +263,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -384,12 +399,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_definition func_args func_args_list
+				distinct_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -508,7 +525,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -551,7 +568,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -682,7 +699,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -697,7 +714,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -708,7 +725,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -729,7 +746,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 
 /* Precedence: lowest to highest */
@@ -743,6 +760,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -782,6 +802,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2098,6 +2123,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2125,7 +2158,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2264,7 +2305,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2273,8 +2326,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2283,6 +2348,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3190,12 +3264,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3209,12 +3284,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3228,13 +3304,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3248,13 +3325,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3268,13 +3346,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3288,13 +3367,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3371,6 +3451,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3467,6 +3548,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3549,12 +3640,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3572,6 +3663,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3594,6 +3686,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3969,9 +4085,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4107,7 +4248,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11754,7 +11895,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11833,12 +11974,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11846,6 +11991,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11944,7 +12102,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
 
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
+
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15334,6 +15539,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15410,6 +15616,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15440,6 +15647,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15905,6 +16113,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -15995,6 +16204,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -16040,6 +16250,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16156,16 +16367,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16571,29 +16772,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	Node	   *lexp = lexpr;
-
-	/* Look through AEXPR_PAREN nodes so they don't affect flattening */
-	while (IsA(lexp, A_Expr) &&
-		   ((A_Expr *) lexp)->kind == AEXPR_PAREN)
-		lexp = ((A_Expr *) lexp)->lexpr;
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexp, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexp;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ea4a1f5aeb..169d10638f 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1141,6 +1143,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		changeTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3690,3 +3721,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 254c0f65c2..d67420d652 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -95,6 +98,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -118,6 +126,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -250,6 +260,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -285,7 +299,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -293,6 +309,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -304,6 +354,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -730,6 +800,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row start time must be timestamptz ")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1409,6 +1535,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time parameter must be the same as  the name of row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time  parameter must be the same as the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3149,7 +3304,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3176,7 +3331,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3212,6 +3367,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3246,6 +3405,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3255,6 +3422,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3422,6 +3606,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3453,7 +3652,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index be86eb37fe..3177e52851 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -118,6 +118,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -169,6 +172,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f398027fa6..1dba4bf610 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -58,7 +59,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1275,6 +1278,12 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
+
+
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1285,6 +1294,85 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already system versioned")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning for table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statment
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table add column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not system versioned")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1295,6 +1383,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 66393becfb..1495c357ce 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c68db75b97..91d8e92272 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15803,6 +15803,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 07d640021c..f20cb7d779 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2062,11 +2062,15 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated by default as identity";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 				default_str = psprintf("generated always as (%s) stored", PQgetvalue(res, i, attrdef_col));
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else
 				/* (note: above we cut off the 'default' string at 128) */
 				default_str = PQgetvalue(res, i, attrdef_col);
 
-			printTableAddCell(&cont, default_str, false, generated[0] ? true : false);
+			printTableAddCell(&cont, default_str, false, generated[0] == ATTRIBUTE_GENERATED_STORED ? true : false);
 		}
 
 		/* Info for index columns */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index cdf75a2380..49bbb2c634 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -204,6 +204,9 @@ DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnum_index, 2659, on pg_attribute usin
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 46a2dc9511..2bb3f14eed 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 7ebd794713..71de983846 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..0e6c29fb96 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -480,6 +480,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index a2dcdef3fa..6ecbe8d57c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1010,6 +1010,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1774,7 +1775,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1855,7 +1856,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2080,6 +2084,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when it is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2129,7 +2134,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3570,4 +3577,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..d5125db2a7 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..6b665e0e2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..fdb4da1b70 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bc3d66ed88..71e46f4f9c 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 9594856c88..3cfa98e7f6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..972d42f211
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,416 @@
+/*
+ * CREATE TABLE
+ */
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  the data type of row start time must be timestamptz 
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time  parameter must be the same as the name of row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time can not be specified multiple time
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time can not be specified multiple time
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                    Table "public.stest2"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                    Table "public.stest3"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+INSERT INTO stest0 VALUES (5);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 3
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b..9fabd58bc2 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -78,7 +78,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f..725e76cd26 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -126,6 +126,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..4f7439eb46
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,195 @@
+/*
+ * CREATE TABLE
+ */
+
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+
+
+--truncation
+truncate table stest0;
+
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+INSERT INTO stest0 VALUES (5);
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
#34Ryan Lambert
ryan@rustprooflabs.com
In reply to: Surafel Temesgen (#33)
Re: WIP: System Versioned Temporal Table

On Thu, Nov 19, 2020 at 11:04 AM Surafel Temesgen <surafel3000@gmail.com>
wrote:

Attached is a rebased one.
regards
Surafel

Thank you for your work on this! The v7 patch fails on the current master
branch. Error from make:

gram.y:16695:1: error: static declaration of ‘makeAndExpr’ follows
non-static declaration
makeAndExpr(Node *lexpr, Node *rexpr, int location)
^~~~~~~~~~~
In file included from gram.y:58:0:
../../../src/include/nodes/makefuncs.h:108:14: note: previous declaration
of ‘makeAndExpr’ was here
extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
^~~~~~~~~~~
gram.y:16695:1: warning: ‘makeAndExpr’ defined but not used
[-Wunused-function]
makeAndExpr(Node *lexpr, Node *rexpr, int location)
^~~~~~~~~~~

The docs have two instances of "EndtTime" that should be "EndTime".

Ryan Lambert

#35Surafel Temesgen
surafel3000@gmail.com
In reply to: Ryan Lambert (#34)
Re: WIP: System Versioned Temporal Table

Hi Ryan,

On Fri, Dec 18, 2020 at 10:28 PM Ryan Lambert <ryan@rustprooflabs.com>
wrote:

On Thu, Nov 19, 2020 at 11:04 AM Surafel Temesgen <surafel3000@gmail.com>
wrote:

The docs have two instances of "EndtTime" that should be "EndTime".

Since my first language is not english i'm glad you find only this error
on doc. I will send rebased pach soon

regards
Surafel

#36Masahiko Sawada
sawada.mshk@gmail.com
In reply to: Surafel Temesgen (#35)
Re: WIP: System Versioned Temporal Table

Hi Surafel,

On Tue, Dec 22, 2020 at 3:01 AM Surafel Temesgen <surafel3000@gmail.com> wrote:

Hi Ryan,

On Fri, Dec 18, 2020 at 10:28 PM Ryan Lambert <ryan@rustprooflabs.com> wrote:

On Thu, Nov 19, 2020 at 11:04 AM Surafel Temesgen <surafel3000@gmail.com> wrote:

The docs have two instances of "EndtTime" that should be "EndTime".

Since my first language is not english i'm glad you find only this error
on doc. I will send rebased pach soon

The patch is not submitted yet. Are you planning to submit the updated
patch? Please also note the v7 patch cannot be applied to the current
HEAD. I'm switching the patch as Waiting on Author.

Regards,

--
Masahiko Sawada
EnterpriseDB: https://www.enterprisedb.com/

#37Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Masahiko Sawada (#36)
Re: WIP: System Versioned Temporal Table

On Mon, Jan 4, 2021 at 2:24 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:

Please also note the v7 patch cannot be applied to the current HEAD. I'm switching the patch as Waiting on Author.

Surafel, please say whether you are working on this or not. If you
need help, let us know.

On Tue, 7 Jan 2020 at 10:33, Kyotaro Horiguchi <horikyota.ntt@gmail.com> wrote:

I think that the start and end timestamps represent the period where
that version of the row was active. So UPDATE should modify the start
timestamp of the new version to the same value with the end timestamp
of the old version to the updated time. Thus, I don't think adding
start timestamp to PK doesn't work as expected. That hinders us from
rejecting rows with the same user-defined unique key because start
timestamps is different each time of inserts. I think what Surafel is
doing in the current patch is correct. Only end_timestamp = +infinity
rejects another non-historical (= live) version with the same
user-defined unique key.

The end_time needs to be updated when a row is updated, so it cannot
form part of the PK. If you try to force that to happen, then logical
replication will not work with system versioned tables, which would be
a bad thing. So *only* start_time should be added to the PK to make
this work. (A later comment also says the start_time needs to be
updated, which makes no sense!)

On Wed, 23 Oct 2019 at 21:03, Vik Fearing <vik.fearing@2ndquadrant.com> wrote:

I don't see any error handling for transaction anomalies. In READ
COMMITTED, you can easily end up with a case where the end time comes
before the start time. I don't even see anything constraining start
time to be strictly inferior to the end time. Such a constraint will be
necessary for application-time periods (which your patch doesn't address
at all but that's okay).

I don't see how it can have meaning to have an end_time earlier than a
start_time, so yes that should be checked. Having said that, if we use
a statement timestamp on row insertion then, yes, the end_time could
be earlier than start time, so that is just wrong. Ideally we would
use commit timestamp and fill the values in later. So the only thing
that makes sense for me is to use the dynamic time of insertion while
we hold the buffer lock, otherwise we will get anomalies.

The work looks interesting and I will be doing a longer review.

--
Simon Riggs http://www.EnterpriseDB.com/

#38Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Simon Riggs (#37)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Thu, Jan 7, 2021 at 5:59 PM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

On Mon, Jan 4, 2021 at 2:24 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:

Please also note the v7 patch cannot be applied to the current HEAD. I'm switching the patch as Waiting on Author.

Surafel, please say whether you are working on this or not. If you
need help, let us know.

I've minimally rebased the patch to current head so that it compiles
and passes current make check.

From here, I will add further docs and tests to enhance review and
discover issues.

--
Simon Riggs http://www.EnterpriseDB.com/

Attachments:

system-versioned-temporal-table_2021_v8.patchapplication/octet-stream; name=system-versioned-temporal-table_2021_v8.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c25ef5abd6..4a38ebbc69 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table, using default columns
+      names of system versioning which are StartTime and EndtTime. If the table is
+      not empty StartTime and EndtTime columns will be filled with current transaction
+      time and infinity respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +192,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty history records also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 569f4c9da7..917cddee90 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -981,6 +1007,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1235,6 +1271,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 6757033e09..7ba7ea8123 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,64 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN SYMMETRIC<replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        the least and greatest of <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 30c30cf3a2..f73e1c45ac 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a6eb..3d24c8f047 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1012,6 +1012,11 @@ CopyFrom(CopyFromState cstate)
 					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
 											   CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1fa9f19f08..fdd385eaaa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,6 +57,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -74,6 +75,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -168,6 +170,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -422,11 +427,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1574,6 +1580,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1582,6 +1589,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3767,8 +3782,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3797,6 +3814,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4365,6 +4383,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4421,6 +4440,36 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to specified otherwise its
+			 * useless
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4520,16 +4569,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4753,6 +4802,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4809,7 +4859,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5185,6 +5235,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5222,6 +5273,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5289,6 +5341,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5377,6 +5435,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5447,6 +5512,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5482,6 +5551,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6298,6 +6368,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6617,6 +6695,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7060,6 +7145,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7160,6 +7252,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7567,6 +7666,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7772,11 +7878,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7819,6 +7926,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7879,11 +7996,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7904,9 +8025,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11367,6 +11488,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12111,10 +12238,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12448,6 +12578,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15923,7 +16060,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103feb..c3a88b9db8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ab3d655e60..6b647fba34 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -360,6 +360,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -463,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		WCOKind		wco_kind;
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Constraints might reference the tableoid column, so (re-)initialize
 		 * tts_tableOid before evaluating them.
@@ -796,6 +923,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1252,6 +1403,37 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * update in foreign table: let the FDW do it
 		 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70f8b718e0..118b6c53ec 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3393,6 +3393,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4806,6 +4807,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5697,6 +5722,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 541e0e6b48..ea01fa67f9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1246,6 +1246,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2933,6 +2934,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3752,6 +3774,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index ee033ae779..74b3ba0a7b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,124 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexpr, BoolExpr))
+	{
+		BoolExpr *blexpr = (BoolExpr *) lexpr;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d78b16ed1d..b7cb5159a6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2643,6 +2643,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1a94b58f8b..39526f9f05 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index fcce81926b..f337f67d98 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to filter out historical data .
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and filter out historical data.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index daf1759623..01f2c6d203 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2354,3 +2359,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f73d..94f5c6bf43 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -45,6 +45,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -445,6 +446,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1220,6 +1226,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1317,6 +1332,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2265,6 +2285,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8f341ac006..eb1b5eb463 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -134,6 +134,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -152,7 +166,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -177,7 +190,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -250,6 +262,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -383,12 +398,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_definition func_args func_args_list
+				distinct_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -505,7 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -543,7 +560,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -674,7 +691,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -689,7 +706,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -700,7 +717,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -721,7 +738,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 
 /* Precedence: lowest to highest */
@@ -735,6 +752,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -774,6 +794,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2090,6 +2115,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2117,7 +2150,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2256,7 +2297,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2265,8 +2318,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2275,6 +2340,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3182,12 +3256,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3201,12 +3276,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3220,13 +3296,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3240,13 +3317,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3260,13 +3338,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3280,13 +3359,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3363,6 +3443,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3459,6 +3540,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3541,12 +3632,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3564,6 +3655,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3586,6 +3678,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3962,9 +4078,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4100,7 +4241,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11711,7 +11852,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11790,12 +11931,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11803,6 +11948,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11901,7 +12059,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
+
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
 
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15253,6 +15458,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15329,6 +15535,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15359,6 +15566,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15824,6 +16032,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -15914,6 +16123,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -15959,6 +16169,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16075,16 +16286,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16490,23 +16691,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexpr, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ea4a1f5aeb..169d10638f 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1141,6 +1143,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		changeTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3690,3 +3721,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 89ee990599..6cfa145485 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -252,6 +262,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -287,7 +301,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -295,6 +311,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -306,6 +356,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -746,6 +816,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row start time must be timestamptz ")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1433,6 +1559,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time parameter must be the same as  the name of row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time  parameter must be the same as the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3173,7 +3328,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3200,7 +3355,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3237,6 +3392,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3271,6 +3430,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3280,6 +3447,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3447,6 +3631,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3478,7 +3677,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index be86eb37fe..3177e52851 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -118,6 +118,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -169,6 +172,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a42ead7d69..e8e1e333fc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -59,7 +60,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1274,6 +1277,12 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
+
+
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1284,6 +1293,85 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already system versioned")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning for table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statment
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table add column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not system versioned")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1294,6 +1382,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3bd5e18042..341d00def4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1ab98a2286..8514e2660e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15860,6 +15860,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 14150d05a9..01bdd955b7 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2061,6 +2061,10 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated always as identity";
 			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 				default_str = "generated by default as identity";
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 			{
 				default_str = psprintf("generated always as (%s) stored",
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index cdf75a2380..49bbb2c634 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -204,6 +204,9 @@ DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnum_index, 2659, on pg_attribute usin
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 46a2dc9511..2bb3f14eed 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 7ebd794713..71de983846 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 3684f87a88..4355201a43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 48a79a7657..ff9840c334 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1009,6 +1009,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1773,7 +1774,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1854,7 +1855,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2079,6 +2083,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when it is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2128,7 +2133,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3556,4 +3563,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..d5125db2a7 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..6b665e0e2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index beb56fec87..562b1757e9 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bc3d66ed88..71e46f4f9c 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 9594856c88..3cfa98e7f6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef71dd..c7cfa19a5a 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -80,7 +80,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 081fce32e7..767ab1ef3e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -127,6 +127,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
#39Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Simon Riggs (#38)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 8, 2021 at 7:13 AM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

I've minimally rebased the patch to current head so that it compiles
and passes current make check.

Full version attached

--
Simon Riggs http://www.EnterpriseDB.com/

Attachments:

system-versioned-temporal-table_2021_v9.patchapplication/octet-stream; name=system-versioned-temporal-table_2021_v9.patchDownload
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c25ef5abd6..4a38ebbc69 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table, using default columns
+      names of system versioning which are StartTime and EndtTime. If the table is
+      not empty StartTime and EndtTime columns will be filled with current transaction
+      time and infinity respectively.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +192,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty history records also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 569f4c9da7..917cddee90 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -981,6 +1007,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PRIMARY PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      It specifies a pair of column that hold the row start
+      time and row end time column name.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1235,6 +1271,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 6757033e09..7ba7ea8123 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,64 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN SYMMETRIC<replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        the least and greatest of <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 30c30cf3a2..f73e1c45ac 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a6eb..3d24c8f047 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1012,6 +1012,11 @@ CopyFrom(CopyFromState cstate)
 					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
 											   CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1fa9f19f08..fdd385eaaa 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,6 +57,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -74,6 +75,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -168,6 +170,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -422,11 +427,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1574,6 +1580,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1582,6 +1589,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3767,8 +3782,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3797,6 +3814,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4365,6 +4383,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4421,6 +4440,36 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to specified otherwise its
+			 * useless
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4520,16 +4569,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4753,6 +4802,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4809,7 +4859,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5185,6 +5235,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5222,6 +5273,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5289,6 +5341,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5377,6 +5435,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5447,6 +5512,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5482,6 +5551,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6298,6 +6368,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6617,6 +6695,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7060,6 +7145,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7160,6 +7252,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7567,6 +7666,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7772,11 +7878,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7819,6 +7926,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7879,11 +7996,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7904,9 +8025,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11367,6 +11488,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12111,10 +12238,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12448,6 +12578,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15923,7 +16060,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103feb..c3a88b9db8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ab3d655e60..6b647fba34 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -360,6 +360,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -463,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		WCOKind		wco_kind;
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Constraints might reference the tableoid column, so (re-)initialize
 		 * tts_tableOid before evaluating them.
@@ -796,6 +923,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1252,6 +1403,37 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * update in foreign table: let the FDW do it
 		 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70f8b718e0..118b6c53ec 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3393,6 +3393,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4806,6 +4807,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5697,6 +5722,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 541e0e6b48..ea01fa67f9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1246,6 +1246,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2933,6 +2934,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3752,6 +3774,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index ee033ae779..74b3ba0a7b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,124 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexpr, BoolExpr))
+	{
+		BoolExpr *blexpr = (BoolExpr *) lexpr;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d78b16ed1d..b7cb5159a6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2643,6 +2643,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1a94b58f8b..39526f9f05 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index fcce81926b..f337f67d98 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to filter out historical data .
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and filter out historical data.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index daf1759623..01f2c6d203 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2354,3 +2359,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if there are system versioned relation and where clause did not
+ * already contain filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+			/*
+			 * Create a condition that filter history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f73d..94f5c6bf43 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -45,6 +45,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -445,6 +446,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1220,6 +1226,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1317,6 +1332,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2265,6 +2285,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8f341ac006..eb1b5eb463 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -134,6 +134,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -152,7 +166,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -177,7 +190,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -250,6 +262,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -383,12 +398,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_definition func_args func_args_list
+				distinct_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -505,7 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -543,7 +560,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -674,7 +691,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -689,7 +706,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -700,7 +717,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -721,7 +738,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 
 /* Precedence: lowest to highest */
@@ -735,6 +752,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -774,6 +794,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2090,6 +2115,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2117,7 +2150,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2256,7 +2297,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2265,8 +2318,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2275,6 +2340,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3182,12 +3256,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3201,12 +3276,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3220,13 +3296,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3240,13 +3317,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3260,13 +3338,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3280,13 +3359,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3363,6 +3443,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3459,6 +3540,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3541,12 +3632,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3564,6 +3655,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3586,6 +3678,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3962,9 +4078,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4100,7 +4241,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11711,7 +11852,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11790,12 +11931,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11803,6 +11948,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11901,7 +12059,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
+
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
 
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15253,6 +15458,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15329,6 +15535,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15359,6 +15566,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15824,6 +16032,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -15914,6 +16123,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -15959,6 +16169,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16075,16 +16286,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16490,23 +16691,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexpr, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ea4a1f5aeb..169d10638f 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1141,6 +1143,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("Temporal clause can only be to system versioned table")));
+
+		changeTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3690,3 +3721,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 89ee990599..6cfa145485 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -252,6 +262,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -287,7 +301,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -295,6 +311,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time column and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -306,6 +356,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -746,6 +816,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row start time must be timestamptz ")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time can not be specified multiple time")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1433,6 +1559,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time parameter must be the same as  the name of row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time  parameter must be the same as the name of row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3173,7 +3328,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3200,7 +3355,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3237,6 +3392,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3271,6 +3430,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3280,6 +3447,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3447,6 +3631,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3478,7 +3677,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index be86eb37fe..3177e52851 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -118,6 +118,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -169,6 +172,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a42ead7d69..e8e1e333fc 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -59,7 +60,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1274,6 +1277,12 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
+
+
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1284,6 +1293,85 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already system versioned")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning for table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statment
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table add column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell becouse we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not system versioned")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1294,6 +1382,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3bd5e18042..341d00def4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1ab98a2286..8514e2660e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15860,6 +15860,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 14150d05a9..01bdd955b7 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2061,6 +2061,10 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated always as identity";
 			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 				default_str = "generated by default as identity";
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 			{
 				default_str = psprintf("generated always as (%s) stored",
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index cdf75a2380..49bbb2c634 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -204,6 +204,9 @@ DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnum_index, 2659, on pg_attribute usin
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 46a2dc9511..2bb3f14eed 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 7ebd794713..71de983846 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 3684f87a88..4355201a43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 48a79a7657..ff9840c334 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1009,6 +1009,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1773,7 +1774,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1854,7 +1855,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2079,6 +2083,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when it is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2128,7 +2133,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3556,4 +3563,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..d5125db2a7 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..6b665e0e2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index beb56fec87..562b1757e9 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bc3d66ed88..71e46f4f9c 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 9594856c88..3cfa98e7f6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..972d42f211
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,416 @@
+/*
+ * CREATE TABLE
+ */
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  the data type of row start time must be timestamptz 
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time  parameter must be the same as the name of row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time parameter must be the same as  the name of row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time can not be specified multiple time
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time can not be specified multiple time
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                    Table "public.stest2"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                    Table "public.stest3"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+INSERT INTO stest0 VALUES (5);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 3
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef71dd..c7cfa19a5a 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -80,7 +80,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 081fce32e7..767ab1ef3e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -127,6 +127,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..4f7439eb46
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,195 @@
+/*
+ * CREATE TABLE
+ */
+
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+
+
+--truncation
+truncate table stest0;
+
+-- test UPDATE/DELETE
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+INSERT INTO stest0 VALUES (5);
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
#40Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Simon Riggs (#39)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 8, 2021 at 7:34 AM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

On Fri, Jan 8, 2021 at 7:13 AM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

I've minimally rebased the patch to current head so that it compiles
and passes current make check.

Full version attached

New version attached with improved error messages, some additional
docs and a review of tests.

* UPDATE doesn't set EndTime correctly, so err... the patch doesn't
work on this aspect.
Everything else does actually work, AFAICS, so we "just" need a way to
update the END_TIME column in place...
So input from other Hackers/Committers needed on this point to see
what is acceptable.

* Anomalies around use of CURRENT_TIMESTAMP are not discussed or resolved

* No discussion, comments or tests around freezing and whether that
causes issues here

* What happens if you ask for a future time?
It will give an inconsistent result as it scans, so we should refuse a
query for time > current_timestamp.

* ALTER TABLE needs some work, it's a bit klugey at the moment and
needs extra tests.
Should refuse DROP COLUMN on a system time column

* Do StartTime and EndTime show in SELECT *? Currently, yes. Would
guess we wouldn't want them to, not sure what standard says.

* The syntax changes in gram.y probably need some coralling

Overall, it's a pretty good patch and worth working on more. I will
consider a recommendation on what to do with this.

--
Simon Riggs http://www.EnterpriseDB.com/

Attachments:

system-versioned-temporal-table_2021_v10.patchapplication/octet-stream; name=system-versioned-temporal-table_2021_v10.patchDownload
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index c12a32c8c7..c391fbe212 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -4917,6 +4917,60 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+ <sect1 id="ddl-temporal">
+  <title>Temporal Data</title>
+
+   <indexterm>
+    <primary>temporal data</primary>
+   </indexterm>
+   <indexterm>
+    <primary>system versioned</primary>
+   </indexterm>
+   <indexterm>
+    <primary>SYSTEM_TIME</primary>
+   </indexterm>
+
+   <para>
+    <productname>PostgreSQL</productname> implements system versioned
+    tables that allow you to store and access temporal data.
+   </para>
+
+   <para>
+    System versioning is optional and can be defined easily for a table:
+<programlisting>
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row verson.
+    The data type of these columns will be TIMESTAMP WITH TIME ZONE.
+    By default, the column names will be StartTime and EndTime, though
+    you can specify different names if you choose. The StartTime
+    column will be automatically added to the Primary Key of the
+    table.
+   </para>
+   <para>
+    StartTime will be generated always when a new row version is added
+    by INSERT or UPDATE, leaving EndTime set at Infinity. EndTime will
+    be set when a row version is UPDATEd or DELETEd. The user may never
+    set the system time column values explicitly.
+   </para>
+   <para>
+    The StartTime and EndTime values form a time range of validity for
+    the row version. Temporal queries can be specified that utilize
+    the system time columns to produce an <quote>as-was</quote> result.
+   </para>
+   <para>
+    The SQL Standard defines that the timestamps used should be the
+    CURRENT_TIMESTAMP value defined at statement start. This currently
+    leads to anomalies in some cases where the EndTime can be set to
+    a value earlier than the StartTime.
+   </para>
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c25ef5abd6..ba0512ea15 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,19 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table by adding two columns which
+      record the period start and period end for the validity of each row version.
+      The column names will be StartTime and EndTime respectively, though the data
+      types are TIMESTAMP WITH TIME ZONE. See
+      <xref linkend="ddl-temporal"/>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +193,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty then history records are also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 569f4c9da7..90002e4572 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a <firstterm>generated
+      column</firstterm>.  The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -981,6 +1007,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      Specifies the pair of columns that hold the ROW START
+      timestamp and ROW END timestamp column names.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1235,6 +1271,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      It specifies the table is system versioned temporal table.
+      If period columns is not specified the default column for
+      system versioned is created which are StartTime and EndTime.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 6757033e09..7ba7ea8123 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,64 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current as <replaceable class="parameter">
+        expression</replaceable> point in time.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> including
+        <replaceable class="parameter">start_time</replaceable> but excluding
+        <replaceable class="parameter">end_time</replaceable>.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN SYMMETRIC<replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        the least and greatest of <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable>
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        Is specifies to see the table as where current at any point between
+        <replaceable class="parameter">start_time</replaceable> and
+        <replaceable class="parameter">end_time</replaceable> inclusively.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 30c30cf3a2..f73e1c45ac 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a6eb..3d24c8f047 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1012,6 +1012,11 @@ CopyFrom(CopyFromState cstate)
 					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
 											   CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1fa9f19f08..8f877768f4 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,6 +57,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -74,6 +75,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -168,6 +170,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -422,11 +427,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1574,6 +1580,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1582,6 +1589,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate system versioned table")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3767,8 +3782,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3797,6 +3814,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4365,6 +4383,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4421,6 +4440,35 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to be specified
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4520,16 +4568,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4753,6 +4801,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4809,7 +4858,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5185,6 +5234,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5222,6 +5272,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5289,6 +5340,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5377,6 +5434,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5447,6 +5511,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5482,6 +5550,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6298,6 +6367,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6617,6 +6694,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7060,6 +7144,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7160,6 +7251,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7567,6 +7665,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7772,11 +7877,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7819,6 +7925,32 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+#ifdef NOTUSED
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+#endif
+
+	/*
+	 * Reviewers note: We should be disallowing DROP COLUMN on a
+	 * system time column, but DROP SYSTEM VERSIONING is currently
+	 * kluged to generate multiple DropColumn subcommands, which
+	 * means we need to allow this for now, even though an explicit
+	 * DROP COLUMN will crash the server. Needs work.
+	 */
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7879,11 +8011,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7904,9 +8040,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11367,6 +11503,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12111,10 +12253,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12448,6 +12593,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15923,7 +16075,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103feb..c3a88b9db8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ab3d655e60..31622a62d8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -360,6 +360,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -463,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		WCOKind		wco_kind;
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Constraints might reference the tableoid column, so (re-)initialize
 		 * tts_tableOid before evaluating them.
@@ -796,6 +923,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1252,6 +1403,37 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * update in foreign table: let the FDW do it
 		 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 70f8b718e0..118b6c53ec 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3393,6 +3393,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4806,6 +4807,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5697,6 +5722,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 541e0e6b48..ea01fa67f9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1246,6 +1246,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2933,6 +2934,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3752,6 +3774,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index ee033ae779..74b3ba0a7b 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,124 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexpr, BoolExpr))
+	{
+		BoolExpr *blexpr = (BoolExpr *) lexpr;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index d78b16ed1d..b7cb5159a6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2643,6 +2643,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1a94b58f8b..39526f9f05 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index fcce81926b..848f091762 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index daf1759623..2db59af44b 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2354,3 +2359,193 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if this is a system versioned relation and the where clause did not
+ * already contain a filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filters history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f73d..94f5c6bf43 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -45,6 +45,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -445,6 +446,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1220,6 +1226,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1317,6 +1332,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2265,6 +2285,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8f341ac006..eb1b5eb463 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -134,6 +134,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -152,7 +166,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -177,7 +190,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -250,6 +262,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -383,12 +398,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	stmtblock stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_definition func_args func_args_list
+				distinct_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -505,7 +522,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -543,7 +560,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -674,7 +691,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -689,7 +706,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -700,7 +717,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -721,7 +738,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 
 /* Precedence: lowest to highest */
@@ -735,6 +752,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -774,6 +794,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2090,6 +2115,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2117,7 +2150,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2256,7 +2297,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2265,8 +2318,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2275,6 +2340,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3182,12 +3256,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3201,12 +3276,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3220,13 +3296,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3240,13 +3317,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3260,13 +3338,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3280,13 +3359,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3363,6 +3443,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3459,6 +3540,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3541,12 +3632,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3564,6 +3655,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3586,6 +3678,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -3962,9 +4078,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4100,7 +4241,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11711,7 +11852,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11790,12 +11931,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11803,6 +11948,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11901,7 +12059,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
+
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
 
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15253,6 +15458,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15329,6 +15535,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15359,6 +15566,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15824,6 +16032,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -15914,6 +16123,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -15959,6 +16169,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16075,16 +16286,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16490,23 +16691,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexpr, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ea4a1f5aeb..afb2ab0a48 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1141,6 +1143,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("temporal clause can only specified for a system versioned table")));
+
+		changeTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3690,3 +3721,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 89ee990599..d3e7662eda 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -252,6 +262,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -287,7 +301,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -295,6 +311,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time columns and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -306,6 +356,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -746,6 +816,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row start time must be timestamptz")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1433,6 +1559,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time must reference the row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time must reference the row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3173,7 +3328,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3200,7 +3355,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3237,6 +3392,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3271,6 +3429,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3280,6 +3446,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3447,6 +3630,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3478,7 +3676,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index be86eb37fe..3177e52851 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -118,6 +118,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -169,6 +172,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a42ead7d69..ea51941bd1 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -59,7 +60,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1274,6 +1277,10 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1284,6 +1291,83 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already system versioned")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning to table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statement
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table add column cmd and append to the end
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not system versioned")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1294,6 +1378,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3bd5e18042..341d00def4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1ab98a2286..8514e2660e 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15860,6 +15860,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 14150d05a9..01bdd955b7 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2061,6 +2061,10 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated always as identity";
 			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 				default_str = "generated by default as identity";
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 			{
 				default_str = psprintf("generated always as (%s) stored",
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index d17af13ee3..d3f74ddba7 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index cdf75a2380..49bbb2c634 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -204,6 +204,9 @@ DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnum_index, 2659, on pg_attribute usin
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 46a2dc9511..2bb3f14eed 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 7ebd794713..71de983846 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 3684f87a88..4355201a43 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -479,6 +479,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 48a79a7657..ff9840c334 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1009,6 +1009,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1773,7 +1774,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1854,7 +1855,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2079,6 +2083,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when it is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2128,7 +2133,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3556,4 +3563,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index c29a7091ec..d5125db2a7 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..6b665e0e2d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index beb56fec87..562b1757e9 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bc3d66ed88..71e46f4f9c 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 9594856c88..3cfa98e7f6 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioned_table.out b/src/test/regress/expected/system_versioned_table.out
new file mode 100644
index 0000000000..05d46a36d3
--- /dev/null
+++ b/src/test/regress/expected/system_versioned_table.out
@@ -0,0 +1,425 @@
+-- CREATE TABLE
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  data type of row start time must be timestamptz
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time must reference the row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time must reference the row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time must reference the row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time specified more than once
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time specified more than once
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                    Table "public.stest2"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                    Table "public.stest3"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+DROP TABLE stest2, stest3;
+\d stest0
+                                       Table "public.stest0"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Indexes:
+    "stest0_pkey" PRIMARY KEY, btree (a, end_timestamp)
+
+-- ALTER TABLE
+-- Don't test DROP COLUMN at present because of klugey way we ADD SYSTEM VERSIONING
+--ALTER TABLE stest0 DROP COLUMN start_timestamp;
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation not allowed
+truncate table stest0;
+ERROR:  cannot truncate system versioned table
+-- test DML
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+INSERT INTO stest0 VALUES (5);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 3
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef71dd..c7cfa19a5a 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -80,7 +80,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioned_table
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 081fce32e7..767ab1ef3e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -127,6 +127,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioned_table
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioned_table.sql b/src/test/regress/sql/system_versioned_table.sql
new file mode 100644
index 0000000000..31c3524b39
--- /dev/null
+++ b/src/test/regress/sql/system_versioned_table.sql
@@ -0,0 +1,191 @@
+-- CREATE TABLE
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+DROP TABLE stest2, stest3;
+
+\d stest0
+-- ALTER TABLE
+-- Don't test DROP COLUMN at present because of klugey way we ADD SYSTEM VERSIONING
+--ALTER TABLE stest0 DROP COLUMN start_timestamp;
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+--truncation not allowed
+truncate table stest0;
+
+-- test DML
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+INSERT INTO stest0 VALUES (5);
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
#41Andrew Dunstan
andrew@dunslane.net
In reply to: Simon Riggs (#40)
Re: WIP: System Versioned Temporal Table

On 1/8/21 7:33 AM, Simon Riggs wrote:

* What happens if you ask for a future time?
It will give an inconsistent result as it scans, so we should refuse a
query for time > current_timestamp.

That seems like a significant limitation. Can we fix it instead of
refusing the query?

cheers

andrew

--
Andrew Dunstan
EDB: https://www.enterprisedb.com

#42Ryan Lambert
ryan@rustprooflabs.com
In reply to: Simon Riggs (#40)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 8, 2021 at 5:34 AM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

On Fri, Jan 8, 2021 at 7:34 AM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

On Fri, Jan 8, 2021 at 7:13 AM Simon Riggs <simon.riggs@enterprisedb.com>

wrote:

I've minimally rebased the patch to current head so that it compiles
and passes current make check.

Full version attached

New version attached with improved error messages, some additional
docs and a review of tests.

The v10 patch fails to make on the current master branch (15b824da). Error:

make[2]: Entering directory
'/var/lib/postgresql/git/postgresql/src/backend/parser'
'/usr/bin/perl' ./check_keywords.pl gram.y
../../../src/include/parser/kwlist.h
/usr/bin/bison -Wno-deprecated -d -o gram.c gram.y
gram.y:3685.55-56: error: $4 of ‘ColConstraintElem’ has no declared type
n->contype = ($4)->contype;
^^
gram.y:3687.56-57: error: $4 of ‘ColConstraintElem’ has no declared type
n->raw_expr = ($4)->raw_expr;
^^
gram.y:3734.41-42: error: $$ of ‘generated_type’ has no declared type
$$ = n;
^^
gram.y:3741.41-42: error: $$ of ‘generated_type’ has no declared type
$$ = n;
^^
gram.y:3748.41-42: error: $$ of ‘generated_type’ has no declared type
$$ = n;
^^
../../../src/Makefile.global:750: recipe for target 'gram.c' failed
make[2]: *** [gram.c] Error 1
make[2]: Leaving directory
'/var/lib/postgresql/git/postgresql/src/backend/parser'
Makefile:137: recipe for target 'parser/gram.h' failed
make[1]https://bornsql.ca/blog/temporal-tables-hidden-columns/: *** [parser/gram.h] Error 2
make[1]https://bornsql.ca/blog/temporal-tables-hidden-columns/: Leaving directory '/var/lib/postgresql/git/postgresql/src/backend'
src/Makefile.global:389: recipe for target 'submake-generated-headers'
failed
make: *** [submake-generated-headers] Error 2

* UPDATE doesn't set EndTime correctly, so err... the patch doesn't

work on this aspect.
Everything else does actually work, AFAICS, so we "just" need a way to
update the END_TIME column in place...
So input from other Hackers/Committers needed on this point to see
what is acceptable.

* Anomalies around use of CURRENT_TIMESTAMP are not discussed or resolved

* No discussion, comments or tests around freezing and whether that
causes issues here

* What happens if you ask for a future time?
It will give an inconsistent result as it scans, so we should refuse a
query for time > current_timestamp.

* ALTER TABLE needs some work, it's a bit klugey at the moment and

needs extra tests.
Should refuse DROP COLUMN on a system time column

* Do StartTime and EndTime show in SELECT *? Currently, yes. Would
guess we wouldn't want them to, not sure what standard says.

I prefer to have them hidden by default. This was mentioned up-thread with
no decision, it seems the standard is ambiguous. MS SQL appears to
have flip-flopped on this decision [1]https://bornsql.ca/blog/temporal-tables-hidden-columns/.

SELECT * shows the timestamp columns, don't we need to hide the period

timestamp columns from this query?

The sql standard didn't dictate hiding the column but i agree hiding it by
default is good thing because this columns are used by the system
to classified the data and not needed in user side frequently. I can
change to that if we have consensus

* The syntax changes in gram.y probably need some coralling

Overall, it's a pretty good patch and worth working on more. I will
consider a recommendation on what to do with this.

--
Simon Riggs http://www.EnterpriseDB.com/

I am increasingly interested in this feature and have heard others asking
for this type of functionality. I'll do my best to continue reviewing and
testing.

Thanks,

Ryan Lambert

[1]: https://bornsql.ca/blog/temporal-tables-hidden-columns/

#43Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Ryan Lambert (#42)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 8, 2021 at 4:50 PM Ryan Lambert <ryan@rustprooflabs.com> wrote:

On Fri, Jan 8, 2021 at 5:34 AM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

On Fri, Jan 8, 2021 at 7:34 AM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

On Fri, Jan 8, 2021 at 7:13 AM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

I've minimally rebased the patch to current head so that it compiles
and passes current make check.

Full version attached

New version attached with improved error messages, some additional
docs and a review of tests.

The v10 patch fails to make on the current master branch (15b824da). Error:

Updated v11 with additional docs and some rewording of messages/tests
to use "system versioning" correctly.

No changes on the points previously raised.

--
Simon Riggs http://www.EnterpriseDB.com/

Attachments:

system-versioning-temporal-table_2020_v11.patchapplication/octet-stream; name=system-versioning-temporal-table_2020_v11.patchDownload
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index c12a32c8c7..dcad6ca3b4 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -4917,6 +4917,61 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+ <sect1 id="ddl-temporal">
+  <title>Temporal Data</title>
+
+   <indexterm>
+    <primary>temporal data</primary>
+   </indexterm>
+   <indexterm>
+    <primary>system versioning</primary>
+   </indexterm>
+   <indexterm>
+    <primary>SYSTEM_TIME</primary>
+   </indexterm>
+
+   <para>
+    <productname>PostgreSQL</productname> implements system versioning
+    tables that allow you to access data using temporal queries.
+   </para>
+
+   <para>
+    System versioning is optional and can be defined easily for a table:
+<programlisting>
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row verson.
+    The data type of these columns will be TIMESTAMP WITH TIME ZONE.
+    By default, the column names will be StartTime and EndTime, though
+    you can specify different names if you choose. The StartTime
+    column will be automatically added to the Primary Key of the
+    table.
+   </para>
+   <para>
+    StartTime will be generated always when a new row version is added
+    by INSERT or UPDATE, leaving EndTime set at Infinity. EndTime will
+    be set when a row version is UPDATEd or DELETEd. The user may never
+    set the system time column values explicitly.
+   </para>
+   <para>
+    The StartTime and EndTime values form a time range of validity for
+    the row version. Temporal queries can be specified that utilize
+    the system time columns to produce an <quote>as-was</quote> result,
+    or the full history of changes may be inspected.
+   </para>
+   <para>
+    The SQL Standard defines that the timestamps used should be the
+    CURRENT_TIMESTAMP value defined at statement start. This currently
+    leads to anomalies in some cases where the EndTime can be set to
+    a value earlier than the StartTime.
+   </para>
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c25ef5abd6..ba0512ea15 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,19 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table by adding two columns which
+      record the period start and period end for the validity of each row version.
+      The column names will be StartTime and EndTime respectively, though the data
+      types are TIMESTAMP WITH TIME ZONE. See
+      <xref linkend="ddl-temporal"/>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +193,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty then history records are also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 569f4c9da7..598f019be2 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -981,6 +1007,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      Specifies the pair of columns that hold the row start
+      timestamp and row end timestamp column names.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1235,6 +1271,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This clause specifies that the table contains multiple historical
+      row versions that may be viewed using temporal queries.
+      If period columns are not specified explicitly the default columns
+      StartTime and EndTime will be added automatically.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 6757033e09..3038476f62 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,42 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Allows temporal queries for tables defined with system versioning.
+        This specifies a single timestamp that is used in place of the
+        normal transaction snapshot to determine which row version of a
+        row is visible for this query. At most one row version will be
+        returned for any row.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        For tables with system versioning this specifies that all
+        row versions visible at any point over the time range
+        will be visible to this query.
+        Note that this form of query allows multiple row versions to be
+        returned for one row rather than just a single row. It allows
+        the query to inspect the history of UPDATEs and DELETEs
+        that have occured to row after the initial INSERT, over
+        the time range specified. To see all changes over time
+        specify a start_time of '-Infinity' and an end_time of 'Infinity'.
+       </para>
+       <para>
+        Optionally, <replaceable class="parameter">SYMMETRIC</replaceable>
+        or <replaceable class="parameter">ASYMMETRIC</replaceable> may
+        be specified for the time range.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 902f59440c..2eba3a1eb2 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->is_system_versioned = constr->is_system_versioned;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->is_system_versioned != constr2->is_system_versioned)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->is_system_versioned = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 08b6f782c7..562e31a842 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1014,6 +1014,11 @@ CopyFrom(CopyFromState cstate)
 					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
 											   CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->is_system_versioned)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 993da56d43..0d75e29825 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,6 +57,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -74,6 +75,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -168,6 +170,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -422,11 +427,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1574,6 +1580,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1582,6 +1589,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->is_system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate table with system versioning")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3767,8 +3782,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PerodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3797,6 +3814,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4365,6 +4383,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4421,6 +4440,35 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to be specified
+			 */
+			if (context)
+			{
+				if (context->isSystemVersioned)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4520,16 +4568,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4753,6 +4801,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4809,7 +4858,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5185,6 +5234,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5222,6 +5272,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5289,6 +5340,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5377,6 +5434,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5447,6 +5511,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5482,6 +5550,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6298,6 +6367,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6617,6 +6694,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7060,6 +7144,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7160,6 +7251,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7567,6 +7665,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7772,11 +7877,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7819,6 +7925,32 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+#ifdef NOTUSED
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+#endif
+
+	/*
+	 * Reviewers note: We should be disallowing DROP COLUMN on a
+	 * system time column, but DROP SYSTEM VERSIONING is currently
+	 * kluged to generate multiple DropColumn subcommands, which
+	 * means we need to allow this for now, even though an explicit
+	 * DROP COLUMN will crash the server. Needs work.
+	 */
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7879,11 +8011,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7904,9 +8040,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11367,6 +11503,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12111,10 +12253,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12448,6 +12593,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15923,7 +16075,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..93c8c2dec2 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d7b8f65591..d7cc2e9cfd 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -360,6 +360,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->is_system_versioned);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -463,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		WCOKind		wco_kind;
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Constraints might reference the tableoid column, so (re-)initialize
 		 * tts_tableOid before evaluating them.
@@ -796,6 +923,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1252,6 +1403,37 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->is_system_versioned)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * update in foreign table: let the FDW do it
 		 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ba3ccc712c..54ad9f2da5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3407,6 +3407,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioned);
 }
 
 static CreateStmt *
@@ -4820,6 +4821,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5714,6 +5739,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a2ef853dc2..513c5d2ae6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1258,6 +1258,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioned);
 
 	return true;
 }
@@ -2945,6 +2946,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3767,6 +3789,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..18a4e15fc9 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,124 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexpr, BoolExpr))
+	{
+		BoolExpr *blexpr = (BoolExpr *) lexpr;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, "StartTime") == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = "StartTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = "EndTime";
+		n->constraints = list_make1((Node *) c);
+	}
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8392be6d44..226d4044f6 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2643,6 +2643,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioned);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4e6497ff32..8f53e89a36 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 6d4cc1bcce..b90848f0a7 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index da322b453e..409fbfb132 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioned_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioned_table(RangeTblEntry *rte);
 
 
 /*
@@ -2354,3 +2359,193 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if this is a system versioned relation and the where clause did not
+ * already contain a filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioned_table(rte) ||
+			check_system_versioned_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filters history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioned_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioned_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioned_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioned_column_walker(node, rte);
+}
+
+static bool
+check_system_versioned_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->is_system_versioned;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 28e192f51c..253c36dbde 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -455,6 +456,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1230,6 +1236,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1327,6 +1342,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2275,6 +2295,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..b49c554a2e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -134,6 +134,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioned;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -152,7 +166,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -177,7 +190,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -250,6 +262,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -384,12 +399,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	parse_toplevel stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_definition func_args func_args_list
+				distinct_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -506,7 +523,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -544,7 +561,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -675,7 +692,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -690,7 +707,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -701,7 +718,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -722,7 +739,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 /*
  * The grammar likewise thinks these tokens are keywords, but they are never
@@ -749,6 +766,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -788,6 +808,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2139,6 +2164,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PerodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2166,7 +2199,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2305,7 +2346,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2314,8 +2367,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2324,6 +2389,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3231,12 +3305,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioned = ($11)->systemVersioned;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3250,12 +3325,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioned = ($14)->systemVersioned;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3269,13 +3345,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioned = ($10)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3289,13 +3366,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioned = ($13)->systemVersioned;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3309,13 +3387,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioned = ($12)->systemVersioned;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3329,13 +3408,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioned = ($15)->systemVersioned;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3412,6 +3492,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3508,6 +3589,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3590,12 +3681,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3613,6 +3704,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3635,6 +3727,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -4011,9 +4127,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioned = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4149,7 +4290,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11760,7 +11901,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11839,12 +11980,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11852,6 +11997,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11950,7 +12108,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
+
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
 
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15368,6 +15573,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15444,6 +15650,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15474,6 +15681,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15939,6 +16147,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -16029,6 +16238,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -16074,6 +16284,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16190,16 +16401,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16605,23 +16806,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexpr, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 672245ded7..212de3f9cb 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1141,6 +1143,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->system_versioned = (tupdesc->constr && tupdesc->constr->is_system_versioned);
+		table_close(rel, NoLock);
+
+		if (!rte->system_versioned)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("temporal clause can only specified for a table with system versioning")));
+
+		changeTempToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3690,3 +3721,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTempToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b31f3afa03..1443c4fc44 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -252,6 +262,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -287,7 +301,9 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 			case T_TableLikeClause:
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
-
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -295,6 +311,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time columns and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is added to the table
+	 * definition.
+	 */
+	if (!cxt.isSystemVersioned && stmt->systemVersioned)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef("StartTime");
+		endCol = makeTemporalColumnDef("EndTime");
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(stmt->tableElts, list_make2(startCol, endCol));
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
+	if (cxt.isSystemVersioned)
+	{
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -306,6 +356,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	/*
+	 * End time column is added to primary and unique key constraint
+	 * implicitly to make history and current data co-exist.
+	 */
+	if (cxt.isSystemVersioned)
+	{
+		ListCell   *lc;
+
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -746,6 +816,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row start time must be timestamptz")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("the data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->isSystemVersioned = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1433,6 +1559,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time must reference the row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time must reference the row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3173,7 +3328,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3200,7 +3355,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3237,6 +3392,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.isSystemVersioned = false;
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3271,6 +3429,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PerodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3280,6 +3446,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->is_system_versioned) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3447,6 +3630,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.isSystemVersioned)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->isSystemVersioned = cxt.isSystemVersioned;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3478,7 +3676,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 875de7ba28..e000a4040a 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -137,6 +137,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -188,6 +191,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			break;
 		case NOT:
 			/* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
 			switch (next_token)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 53a511f1da..3b59a719aa 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -59,7 +60,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1274,6 +1277,10 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		isSystemVersioned = false;
+					TupleDesc	tupdesc;
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1284,6 +1291,83 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already defined with system versioning")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning to table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statement
+							 */
+							startTimeCol = makeTemporalColumnDef("StartTime");
+							endTimeCol = makeTemporalColumnDef("EndTime");
+
+							/*
+							 * create alter table add column cmd and append to the end
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							isSystemVersioned = tupdesc->constr && tupdesc->constr->is_system_versioned;
+							if (!isSystemVersioned)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not defined with system versioning")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1294,6 +1378,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.isSystemVersioned = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ef510cd01..b229e48f45 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->is_system_versioned = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->is_system_versioned = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1f70653c02..10afe5c248 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15860,6 +15860,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index caf97563f4..3687ebec18 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2061,6 +2061,10 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated always as identity";
 			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 				default_str = "generated by default as identity";
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 			{
 				default_str = psprintf("generated always as (%s) stored",
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f45d47aab7..a7299bafdc 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		is_system_versioned;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 059dec3647..d57521009e 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -204,6 +204,9 @@ DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnum_index, 2659, on pg_attribute usin
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 83e2965531..be9790a6be 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..b9d36d3751 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,11 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index caed683ba9..941fcec266 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -480,6 +480,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc2bb40926..09114fd310 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1009,6 +1009,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		system_versioned;	/* is from relation system versioned? */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1792,7 +1793,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1873,7 +1874,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PerodColumn				/* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2098,6 +2102,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioned;	/* true when it is system versioned table */
 } CreateStmt;
 
 /* ----------
@@ -2147,7 +2152,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3575,4 +3582,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 8d1d6c1b42..dd2d6bce2e 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8c554e1f69..c10366ed26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index dfc214b06f..41a708b952 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bfa4a6b0f2..3a6251f800 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 841062b4b3..ed137a7ba9 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool		isSystemVersioned;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period  start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioning.out b/src/test/regress/expected/system_versioning.out
new file mode 100644
index 0000000000..9310c5b4c1
--- /dev/null
+++ b/src/test/regress/expected/system_versioning.out
@@ -0,0 +1,425 @@
+-- CREATE TABLE
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  data type of row start time must be timestamptz
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time must reference the row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time must reference the row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time must reference the row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time specified more than once
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time specified more than once
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                    Table "public.stest2"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                    Table "public.stest3"
+  Column   |           Type           | Collation | Nullable |            Default            
+-----------+--------------------------+-----------+----------+-------------------------------
+ a         | integer                  |           |          | 
+ StartTime | timestamp with time zone |           | not null | generated always as row start
+ EndTime   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+DROP TABLE stest2, stest3;
+\d stest0
+                                       Table "public.stest0"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Indexes:
+    "stest0_pkey" PRIMARY KEY, btree (a, end_timestamp)
+
+-- ALTER TABLE
+-- Don't test DROP COLUMN at present because of klugey way we ADD SYSTEM VERSIONING
+--ALTER TABLE stest0 DROP COLUMN start_timestamp;
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation not allowed
+truncate table stest0;
+ERROR:  cannot truncate table with system versioning
+-- test DML
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+(2 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+(3 rows)
+
+INSERT INTO stest0 VALUES (5);
+SELECT a FROM stest0 ORDER BY a;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 3
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+ a 
+---
+ 4
+ 2
+ 3
+(3 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 2
+ 3
+ 4
+ 5
+(4 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef71dd..991d84b955 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -80,7 +80,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioning
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 081fce32e7..6222a84fa3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -127,6 +127,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioning
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioning.sql b/src/test/regress/sql/system_versioning.sql
new file mode 100644
index 0000000000..31c3524b39
--- /dev/null
+++ b/src/test/regress/sql/system_versioning.sql
@@ -0,0 +1,191 @@
+-- CREATE TABLE
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+DROP TABLE stest2, stest3;
+
+\d stest0
+-- ALTER TABLE
+-- Don't test DROP COLUMN at present because of klugey way we ADD SYSTEM VERSIONING
+--ALTER TABLE stest0 DROP COLUMN start_timestamp;
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+--truncation not allowed
+truncate table stest0;
+
+-- test DML
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+SELECT now() AS ts1 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+UPDATE stest0 SET a = 4 WHERE a = 1;
+SELECT now() AS ts2 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+DELETE FROM stest0 WHERE a = 2;
+SELECT now() AS ts3 \gset
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+INSERT INTO stest0 VALUES (5);
+
+SELECT a FROM stest0 ORDER BY a;
+SELECT a FROM stest0 FOR system_time FROM '-infinity' TO 'infinity' ORDER BY a;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT a FROM stest0 FOR system_time AS OF :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time AS OF :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN ASYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts2' AND :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN ASYMMETRIC :'ts1' AND :'ts3' ORDER BY start_timestamp, a;
+
+-- BETWEEN SYMMETRIC ... AND ...
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts2' AND :'ts1' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time BETWEEN SYMMETRIC :'ts3' AND :'ts1' ORDER BY start_timestamp, a;
+
+-- FROM ... TO ...
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts2' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts2' TO :'ts3' ORDER BY start_timestamp, a;
+SELECT a FROM stest0 FOR system_time FROM :'ts1' TO :'ts3' ORDER BY start_timestamp, a;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
#44Ryan Lambert
ryan@rustprooflabs.com
In reply to: Simon Riggs (#43)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 8, 2021 at 11:38 AM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

On Fri, Jan 8, 2021 at 4:50 PM Ryan Lambert <ryan@rustprooflabs.com>
wrote:

On Fri, Jan 8, 2021 at 5:34 AM Simon Riggs <simon.riggs@enterprisedb.com>

wrote:

On Fri, Jan 8, 2021 at 7:34 AM Simon Riggs <

simon.riggs@enterprisedb.com> wrote:

On Fri, Jan 8, 2021 at 7:13 AM Simon Riggs <

simon.riggs@enterprisedb.com> wrote:

I've minimally rebased the patch to current head so that it compiles
and passes current make check.

Full version attached

New version attached with improved error messages, some additional
docs and a review of tests.

The v10 patch fails to make on the current master branch (15b824da).

Error:

Updated v11 with additional docs and some rewording of messages/tests
to use "system versioning" correctly.

No changes on the points previously raised.

--
Simon Riggs http://www.EnterpriseDB.com/

Thank you! The v11 applies and installs. I tried a simple test,
unfortunately it appears the versioning is not working. The initial value
is not preserved through an update and a new row does not appear to be
created.

CREATE TABLE t
(
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
v BIGINT NOT NULL
)
WITH SYSTEM VERSIONING
;

Verify start/end time columns created.

t=# \d t
Table "public.t"
Column | Type | Collation | Nullable |
Default
-----------+--------------------------+-----------+----------+----------------------------------
id | bigint | | not null | generated by
default as identity
v | bigint | | not null |
StartTime | timestamp with time zone | | not null | generated
always as row start
EndTime | timestamp with time zone | | not null | generated
always as row end
Indexes:
"t_pkey" PRIMARY KEY, btree (id, "EndTime")

Add a row and check the timestamps set as expected.

INSERT INTO t (v) VALUES (1);

SELECT * FROM t;
id | v | StartTime | EndTime
----+---+-------------------------------+----------
1 | 1 | 2021-01-08 20:56:20.848097+00 | infinity

Update the row.

UPDATE t SET v = -1;

The value for v updated but StartTime is the same.

SELECT * FROM t;
id | v | StartTime | EndTime
----+----+-------------------------------+----------
1 | -1 | 2021-01-08 20:56:20.848097+00 | infinity

Querying the table for all versions only returns the single updated row (v
= -1) with the original row StartTime. The original value has disappeared
entirely it seems.

SELECT * FROM t
FOR SYSTEM_TIME FROM '-infinity' TO 'infinity';

I also created a non-versioned table and later added the columns using
ALTER TABLE and encountered the same behavior.

Ryan Lambert

#45Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Ryan Lambert (#44)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 8, 2021 at 9:19 PM Ryan Lambert <ryan@rustprooflabs.com> wrote:

Updated v11 with additional docs and some rewording of messages/tests
to use "system versioning" correctly.

No changes on the points previously raised.

Thank you! The v11 applies and installs. I tried a simple test, unfortunately it appears the versioning is not working. The initial value is not preserved through an update and a new row does not appear to be created.

Agreed. I already noted this in my earlier review comments.

I will send in a new version with additional tests so we can see more
clearly that the tests fail on the present patch.

I will post more on this next week.

--
Simon Riggs http://www.EnterpriseDB.com/

#46Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Simon Riggs (#45)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Sat, Jan 9, 2021 at 10:39 AM Simon Riggs
<simon.riggs@enterprisedb.com> wrote:

On Fri, Jan 8, 2021 at 9:19 PM Ryan Lambert <ryan@rustprooflabs.com> wrote:

Updated v11 with additional docs and some rewording of messages/tests
to use "system versioning" correctly.

No changes on the points previously raised.

Thank you! The v11 applies and installs. I tried a simple test, unfortunately it appears the versioning is not working. The initial value is not preserved through an update and a new row does not appear to be created.

Agreed. I already noted this in my earlier review comments.

I'm pleased to note that UPDATE-not-working was a glitch, possibly in
an earlier patch merge. That now works as advertised.

I've added fairly clear SGML docs to explain how the current patch
works, which should assist wider review.

Also moved test SQL around a bit, renamed some things in code for
readability, but not done any structural changes.

This is looking much better now... with the TODO/issues list now
looking like this...

* Anomalies around use of CURRENT_TIMESTAMP are not discussed or resolved.
Probably need to add a test that end_timestamp > start_timestamp or ERROR,
which effectively enforces serializability.

* No discussion, comments or tests around freezing and whether that
causes issues here

* What happens if you ask for a future time?
It will give an inconsistent result as it scans, so we should refuse a
query for time > current_timestamp.

* ALTER TABLE needs some work, it's a bit klugey at the moment and
needs extra tests.
Should refuse DROP COLUMN on a system time column, but currently doesn't

* UPDATE foo SET start_timestamp = DEFAULT should fail but currently doesn't

* Do StartTime and EndTime show in SELECT *? Currently, yes. Would
guess we wouldn't want them to, not sure what standard says.

From here, the plan would be to set this to "Ready For Committer" in
about a week. That is not the same thing as me saying it is
ready-for-commit, but we need some more eyes on this patch to decide
if it is something we want and, if so, are the code changes cool.

--
Simon Riggs http://www.EnterpriseDB.com/

Attachments:

system-versioning-temporal-table_2020_v12.patchapplication/octet-stream; name=system-versioning-temporal-table_2020_v12.patchDownload
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index c12a32c8c7..7da692f724 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -4917,6 +4917,104 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+ <sect1 id="ddl-temporal">
+  <title>Temporal Data</title>
+
+   <indexterm>
+    <primary>temporal data</primary>
+   </indexterm>
+   <indexterm>
+    <primary>system versioning</primary>
+   </indexterm>
+   <indexterm>
+    <primary>SYSTEM_TIME</primary>
+   </indexterm>
+
+   <para>
+    <productname>PostgreSQL</productname> implements a table option
+    for system versioning that records each change permanently,
+    allowing access to the full history of data changes.
+   </para>
+
+   <para>
+    System versioning is optional and can be defined easily for a table:
+<programlisting>
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row verson.
+    The data type of these columns will be TIMESTAMP WITH TIME ZONE.
+    By default, the column names will be StartTime and EndTime, though
+    you can specify different names if you choose. The StartTime
+    column will be automatically added to the Primary Key of the
+    table.
+   </para>
+   <para>
+    StartTime will be generated always when a new row version is added
+    by INSERT or UPDATE, leaving EndTime set at Infinity. EndTime will
+    be generated always when a row version is UPDATEd or DELETEd.
+    This sequence of commands produces the full history shown in
+    the resulting query:
+<programlisting>
+INSERT INTO products VALUES (100, 'Washing Machine', 300.0);
+INSERT INTO products VALUES (200, 'Extended Warranty', 50.0);
+INSERT INTO products VALUES (300, 'Laptop', 250.0);
+UPDATE products SET price = 75.0 WHERE product_no = 200;
+UPDATE products SET price = 350.0 WHERE product_no = 300;
+UPDATE products SET product_no = 400 WHERE product_no = 100;
+DELETE FROM products WHERE product_no = 300;
+INSERT INTO products VALUES (500, 'Spare Parts', 25.0);
+
+SELECT * FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no, start_timestamp;
+ product_no |       name        | price |           start_timestamp           |            end_timestamp            
+------------+-------------------+-------+-------------------------------------+-------------------------------------
+        100 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.020361 2021 PST | Mon Jan 11 03:47:36.027029 2021 PST
+        200 | Extended Warranty |  50.0 | Mon Jan 11 03:47:36.022847 2021 PST | Mon Jan 11 03:47:36.02641 2021 PST
+        200 | Extended Warranty |  75.0 | Mon Jan 11 03:47:36.02641 2021 PST  | infinity
+        300 | Laptop            | 250.0 | Mon Jan 11 03:47:36.023316 2021 PST | Mon Jan 11 03:47:36.026779 2021 PST
+        300 | Laptop            | 350.0 | Mon Jan 11 03:47:36.026779 2021 PST | Mon Jan 11 03:47:36.029086 2021 PST
+        400 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.027029 2021 PST | infinity
+        500 | Spare Parts       |  25.0 | Mon Jan 11 03:47:36.032077 2021 PST | infinity
+(7 rows)
+</programlisting>
+    Note that this is
+    implemented by having an UPDATE become an INSERT of the data from
+    the old row version with EndTime set, plus the actual UPDATE,
+    increasing the number of actual visible rows in the table.
+    DELETE becomes an UPDATE of the old row version with EndTime set.
+    This means that UPDATEs and DELETEs still cause bloat that needs
+    to be removed by VACUUM.
+   </para>
+   <para>
+    Temporal queries are discussed in <xref linkend="temporal-queries"/>.
+   </para>
+   <para>
+    The system versioning period end column will be added to the
+    Primary Key of the table as a way of ensuring that concurrent
+    INSERTs conflict correctly. This alters the way that Foreign
+    Keys work for tables with system versioning.
+   </para>
+   <para>
+    The user may never set the system time column values explicitly
+    on INSERT for generated columns. Currently, UPDATE does allow
+    setting the period start and end dates to DEFAULT values.
+   </para>
+   <para>
+    SELECT * currently returns the period start and end columns, even
+    when added implicitly. 
+   </para>
+   <para>
+    The SQL Standard defines that the timestamps used should be the
+    CURRENT_TIMESTAMP value defined at statement start. This currently
+    leads to anomalies in some cases where the EndTime can be set to
+    a value earlier than the StartTime.
+   </para>
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index ca51204875..b752960d42 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -2588,4 +2588,92 @@ SELECT * FROM t;
 
  </sect1>
 
+ <sect1 id="temporal-queries">
+  <title>Temporal Queries</title>
+
+   <para>
+    <productname>PostgreSQL</productname> implements an option to
+    to add system versioning onto a user table. Such tables can
+    be accessed normally to see the current contents, but also
+    allow you to access data using both temporal and
+    historical queries.  Queries must specify a period name,
+    which for system versioning must be <quote>SYSTEM_TIME</quote>.
+   </para>
+
+   <para>
+    Historical queries show the changes to rows over time, such as the
+    following query that shows changes to a company's products and pricing:
+<programlisting>
+SELECT * FROM products FOR system_time BETWEEN '-infinity' AND 'infinity' ORDER BY product_no, start_timestamp;
+ product_no |       name        | price |           start_timestamp           |            end_timestamp            
+------------+-------------------+-------+-------------------------------------+-------------------------------------
+        100 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.020361 2021 PST | Mon Jan 11 08:47:36.027029 2021 PST
+        200 | Extended Warranty |  50.0 | Mon Jan 11 03:47:36.022847 2021 PST | Mon Jan 11 05:47:36.02641 2021 PST
+        200 | Extended Warranty |  75.0 | Mon Jan 11 05:47:36.02641 2021 PST  | infinity
+        300 | Laptop            | 250.0 | Mon Jan 11 03:47:36.023316 2021 PST | Mon Jan 11 06:47:36.026779 2021 PST
+        300 | Laptop            | 350.0 | Mon Jan 11 06:47:36.026779 2021 PST | Mon Jan 11 07:47:36.029086 2021 PST
+        400 | Washing Machine   | 300.0 | Mon Jan 11 08:47:36.027029 2021 PST | infinity
+        500 | Spare Parts       |  25.0 | Mon Jan 11 09:47:36.032077 2021 PST | infinity
+(7 rows)
+</programlisting>
+    This query shows the full history over all time since it includes
+    all changes between a timestamp of '-infinity' to 'infinity'.
+    The user can specify tighter time ranges to filter away unwanted
+    parts of the change history.
+   </para>
+
+   <para>
+    Normal non-temporal queries show the current contents of the
+    system versioned table, just like normal tables:
+<programlisting>
+SELECT product_no, name, price FROM products ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        200 | Extended Warranty |  75.0 
+        400 | Washing Machine   | 300.0 
+        500 | Spare Parts       |  25.0 
+(3 rows)
+</programlisting>
+    which is achieved by generating adding WHERE clauses to exclude
+    the historical row versions from the query. This can be seen
+    more clearly using <command>EXPLAIN</command>:
+<programlisting>
+EXPLAIN (COSTS OFF) SELECT * FROM products;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Seq Scan on products
+   Filter: (end_timestamp = 'infinity'::timestamp with time zone)
+(2 rows)
+</programlisting>
+    which shows that the presence of historical row versions will
+    affect the performance of all types of query on tables with
+    system versioning.
+   </para>
+
+   <para>
+    Temporal queries show the table rows at a specific timestamp, e.g.
+<programlisting>
+SELECT product_no, name, price FROM products FOR system_time AS OF 'Mon Jan 11 07:30:00 2021 PST' ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        100 | Washing Machine   | 300.0 
+        200 | Extended Warranty |  75.0 
+        300 | Laptop            | 350.0 
+(3 rows)
+</programlisting>
+
+    or
+
+<programlisting>
+SELECT product_no, name, price FROM products FOR system_time AS OF 'Mon Jan 11 09:30:00 2021 PST' ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        200 | Extended Warranty |  75.0 
+        400 | Washing Machine   | 300.0 
+(2 rows)
+</programlisting>
+   </para>
+
+ </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c25ef5abd6..ba0512ea15 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,19 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table by adding two columns which
+      record the period start and period end for the validity of each row version.
+      The column names will be StartTime and EndTime respectively, though the data
+      types are TIMESTAMP WITH TIME ZONE. See
+      <xref linkend="ddl-temporal"/>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +193,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty then history records are also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 569f4c9da7..598f019be2 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -981,6 +1007,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      Specifies the pair of columns that hold the row start
+      timestamp and row end timestamp column names.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1235,6 +1271,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This clause specifies that the table contains multiple historical
+      row versions that may be viewed using temporal queries.
+      If period columns are not specified explicitly the default columns
+      StartTime and EndTime will be added automatically.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 6757033e09..3038476f62 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,42 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Allows temporal queries for tables defined with system versioning.
+        This specifies a single timestamp that is used in place of the
+        normal transaction snapshot to determine which row version of a
+        row is visible for this query. At most one row version will be
+        returned for any row.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        For tables with system versioning this specifies that all
+        row versions visible at any point over the time range
+        will be visible to this query.
+        Note that this form of query allows multiple row versions to be
+        returned for one row rather than just a single row. It allows
+        the query to inspect the history of UPDATEs and DELETEs
+        that have occured to row after the initial INSERT, over
+        the time range specified. To see all changes over time
+        specify a start_time of '-Infinity' and an end_time of 'Infinity'.
+       </para>
+       <para>
+        Optionally, <replaceable class="parameter">SYMMETRIC</replaceable>
+        or <replaceable class="parameter">ASYMMETRIC</replaceable> may
+        be specified for the time range.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 902f59440c..bb222b4f6c 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->has_system_versioning = constr->has_system_versioning;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->has_system_versioning != constr2->has_system_versioning)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->has_system_versioning = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 08b6f782c7..f63c26808b 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1014,6 +1014,11 @@ CopyFrom(CopyFromState cstate)
 					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
 											   CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->has_system_versioning)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 993da56d43..ccb39c5a77 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,6 +57,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -74,6 +75,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -168,6 +170,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -422,11 +427,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1574,6 +1580,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1582,6 +1589,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->has_system_versioning)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate table with system versioning")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3767,8 +3782,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PeriodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3797,6 +3814,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4365,6 +4383,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4421,6 +4440,35 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to be specified
+			 */
+			if (context)
+			{
+				if (context->hasSystemVersioning)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4520,16 +4568,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4753,6 +4801,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4809,7 +4858,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5185,6 +5234,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5222,6 +5272,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5289,6 +5340,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5377,6 +5434,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5447,6 +5511,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5482,6 +5550,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6298,6 +6367,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6617,6 +6694,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7060,6 +7144,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7160,6 +7251,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7567,6 +7665,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7772,11 +7877,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7819,6 +7925,32 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+#ifdef NOTUSED
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+#endif
+
+	/*
+	 * Reviewers note: We should be disallowing DROP COLUMN on a
+	 * system time column, but DROP SYSTEM VERSIONING is currently
+	 * kluged to generate multiple DropColumn subcommands, which
+	 * means we need to allow this for now, even though an explicit
+	 * DROP COLUMN will crash the server. Needs work.
+	 */
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7879,11 +8011,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7904,9 +8040,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11367,6 +11503,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12111,10 +12253,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12448,6 +12593,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15923,7 +16075,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..93c8c2dec2 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d7b8f65591..c092265ace 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -360,6 +360,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -463,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		WCOKind		wco_kind;
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Constraints might reference the tableoid column, so (re-)initialize
 		 * tts_tableOid before evaluating them.
@@ -796,6 +923,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple while attempting delete with system versioning");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1290,6 +1441,40 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			/*
+			 * Insert a new row to represent the old row with EndTime set
+			 * Note that this creates a new row version rather than updating in place
+			 * the existing row version.
+			 */
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple while attempting update with system versioning");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+
+			/*
+			 * Set the StartTime for the soon to be newly updated row
+			 */
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ba3ccc712c..8d907859f4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3407,6 +3407,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioning);
 }
 
 static CreateStmt *
@@ -4820,6 +4821,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5714,6 +5739,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a2ef853dc2..f3b8dd51c9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1258,6 +1258,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioning);
 
 	return true;
 }
@@ -2945,6 +2946,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3767,6 +3789,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..cb9e7eef01 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,126 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexpr, BoolExpr))
+	{
+		BoolExpr *blexpr = (BoolExpr *) lexpr;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, SYSTEM_VERSIONING_DEFAULT_START_NAME) == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = SYSTEM_VERSIONING_DEFAULT_START_NAME;
+		n->constraints = list_make1((Node *) c);
+	}
+	else if (strcmp(name, SYSTEM_VERSIONING_DEFAULT_END_NAME) == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = SYSTEM_VERSIONING_DEFAULT_END_NAME;
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+		elog(ERROR, "unexpected temporal column name");
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8392be6d44..f79740b361 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2643,6 +2643,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioning);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4e6497ff32..8f53e89a36 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 6d4cc1bcce..b90848f0a7 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index da322b453e..800fa9afae 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioning_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioning_table(RangeTblEntry *rte);
 
 
 /*
@@ -2354,3 +2359,193 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if this is a system versioned relation and the where clause did not
+ * already contain a filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioning_table(rte) ||
+			check_system_versioning_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filters history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioning_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioning_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioning_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioning_column_walker(node, rte);
+}
+
+static bool
+check_system_versioning_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->has_system_versioning;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 28e192f51c..253c36dbde 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -455,6 +456,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1230,6 +1236,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1327,6 +1342,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2275,6 +2295,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 31c95443a5..6c9da3539e 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -134,6 +134,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioning;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -152,7 +166,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -177,7 +190,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -250,6 +262,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -384,12 +399,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	parse_toplevel stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith distinct_clause opt_definition func_args func_args_list
+				distinct_clause opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -506,7 +523,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -544,7 +561,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -675,7 +692,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -690,7 +707,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -701,7 +718,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -722,7 +739,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 /*
  * The grammar likewise thinks these tokens are keywords, but they are never
@@ -749,6 +766,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -788,6 +808,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2139,6 +2164,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PeriodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2166,7 +2199,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2305,7 +2346,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2314,8 +2367,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2324,6 +2389,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3231,12 +3305,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioning = ($11)->systemVersioning;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3250,12 +3325,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioning = ($14)->systemVersioning;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3269,13 +3345,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioning = ($10)->systemVersioning;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3289,13 +3366,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioning = ($13)->systemVersioning;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3309,13 +3387,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioning = ($12)->systemVersioning;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3329,13 +3408,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioning = ($15)->systemVersioning;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3412,6 +3492,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3508,6 +3589,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3590,12 +3681,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3613,6 +3704,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3635,6 +3727,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -4011,9 +4127,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioning = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4149,7 +4290,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11760,7 +11901,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11839,12 +11980,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11852,6 +11997,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11950,7 +12108,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
+
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
 
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15368,6 +15573,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15444,6 +15650,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15474,6 +15681,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15939,6 +16147,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -16029,6 +16238,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -16074,6 +16284,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16190,16 +16401,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16605,23 +16806,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexpr, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 672245ded7..dabcab94ed 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1141,6 +1143,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->has_system_versioning = (tupdesc->constr && tupdesc->constr->has_system_versioning);
+		table_close(rel, NoLock);
+
+		if (!rte->has_system_versioning)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("temporal clause can only specified for a table with system versioning")));
+
+		changeTemporalToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3690,3 +3721,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTemporalToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b31f3afa03..7967fa7a8a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		hasSystemVersioning;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -252,6 +262,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.hasSystemVersioning = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -288,6 +302,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
 
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -295,6 +313,27 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time columns and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is prepended to the table
+	 * definition. This is an extension to the SQL Standard.
+	 */
+	if (!cxt.hasSystemVersioning && stmt->systemVersioning)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_START_NAME);
+		endCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_END_NAME);
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(list_make2(startCol, endCol), stmt->tableElts);
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -306,6 +345,36 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	if (cxt.hasSystemVersioning)
+	{
+		ListCell   *lc;
+
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+
+		/*
+		 * End time column is added to primary and unique key constraint
+		 * implicitly to make history and current data co-exist.
+		 */
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -746,6 +815,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row start time must be timestamptz")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->hasSystemVersioning = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->hasSystemVersioning = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1433,6 +1558,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time must reference the row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time must reference the row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3173,7 +3327,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3200,7 +3354,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3237,6 +3391,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.hasSystemVersioning = false;
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3271,6 +3428,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PeriodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3280,6 +3445,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->has_system_versioning) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3447,6 +3629,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.hasSystemVersioning)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->hasSystemVersioning = cxt.hasSystemVersioning;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->hasSystemVersioning = cxt.hasSystemVersioning;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3478,7 +3675,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 875de7ba28..e000a4040a 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -137,6 +137,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -188,6 +191,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			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 0c7508a0d8..7806c2257b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -931,14 +931,24 @@ rewriteTargetListIU(List *targetList,
 								NameStr(att_tup->attname)),
 						 errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.",
 								   NameStr(att_tup->attname))));
-
-			if (att_tup->attgenerated && new_tle && !apply_default)
+#ifdef NOTUSED
+			if (att_tup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+				att_tup->attgenerated == ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
-						(errcode(ERRCODE_GENERATED_ALWAYS),
-						 errmsg("column \"%s\" can only be updated to DEFAULT",
-								NameStr(att_tup->attname)),
-						 errdetail("Column \"%s\" is a generated column.",
-								   NameStr(att_tup->attname))));
+					(errcode(ERRCODE_GENERATED_ALWAYS),
+					 errmsg("column \"%s\" cannot be updated",
+							NameStr(att_tup->attname)),
+					 errdetail("Column \"%s\" is a system versioning column.",
+							   NameStr(att_tup->attname))));
+			else if (att_tup->attgenerated && new_tle && !apply_default)
+#endif
+			if (att_tup->attgenerated && new_tle && !apply_default)
+				 ereport(ERROR,
+					(errcode(ERRCODE_GENERATED_ALWAYS),
+					 errmsg("column \"%s\" can only be updated to DEFAULT",
+							NameStr(att_tup->attname)),
+					 errdetail("Column \"%s\" is a generated column.",
+							   NameStr(att_tup->attname))));
 		}
 
 		if (att_tup->attgenerated)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 53a511f1da..c554c9b3ff 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -59,7 +60,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1274,6 +1277,10 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		hasSystemVersioning = false;
+					TupleDesc	tupdesc;
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1284,6 +1291,83 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							hasSystemVersioning = tupdesc->constr && tupdesc->constr->has_system_versioning;
+							if (hasSystemVersioning)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already defined with system versioning")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning to table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statement
+							 */
+							startTimeCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_START_NAME);
+							endTimeCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_END_NAME);
+
+							/*
+							 * create alter table add column cmd and append to the end
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							hasSystemVersioning = tupdesc->constr && tupdesc->constr->has_system_versioning;
+							if (!hasSystemVersioning)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not defined with system versioning")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1294,6 +1378,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.hasSystemVersioning = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ef510cd01..8312a36261 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->has_system_versioning = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->has_system_versioning = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 1f70653c02..10afe5c248 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15860,6 +15860,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index caf97563f4..3687ebec18 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2061,6 +2061,10 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated always as identity";
 			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 				default_str = "generated by default as identity";
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 			{
 				default_str = psprintf("generated always as (%s) stored",
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f45d47aab7..3a7ffd4464 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		has_system_versioning;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 059dec3647..d57521009e 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -204,6 +204,9 @@ DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnum_index, 2659, on pg_attribute usin
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 83e2965531..be9790a6be 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..9af47ba697 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,16 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+
+#define SYSTEM_VERSIONING_DEFAULT_START_NAME "start_timestamp"
+#define SYSTEM_VERSIONING_DEFAULT_END_NAME "end_timestamp"
+
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
+
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index caed683ba9..941fcec266 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -480,6 +480,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc2bb40926..a533a897fa 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1009,6 +1009,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		has_system_versioning;	/* relation has system versioning */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1792,7 +1793,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1873,7 +1874,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PeriodColumn				/* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2098,6 +2102,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioning;	/* true with system versioning */
 } CreateStmt;
 
 /* ----------
@@ -2147,7 +2152,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3575,4 +3582,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 8d1d6c1b42..dd2d6bce2e 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8c554e1f69..c10366ed26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index dfc214b06f..41a708b952 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bfa4a6b0f2..3a6251f800 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 841062b4b3..4184ea14f4 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool hasSystemVersioning;	/* true if table has system versioning */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/regress/expected/system_versioning.out b/src/test/regress/expected/system_versioning.out
new file mode 100644
index 0000000000..e1b44d1c92
--- /dev/null
+++ b/src/test/regress/expected/system_versioning.out
@@ -0,0 +1,405 @@
+-- CREATE TABLE
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  data type of row start time must be timestamptz
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time must reference the row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time must reference the row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time must reference the row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time specified more than once
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time specified more than once
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                       Table "public.stest2"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           |          | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                       Table "public.stest3"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           |          | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+-- Don't test DROP COLUMN at present because of klugey way we ADD SYSTEM VERSIONING
+--ALTER TABLE stest0 DROP COLUMN start_timestamp;
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation not allowed
+truncate table stest0;
+ERROR:  cannot truncate table with system versioning
+DROP TABLE stest2, stest3;
+-- test DML
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+UPDATE stest0 SET a = 4 WHERE a = 1;
+DELETE FROM stest0 WHERE a = 2;
+INSERT INTO stest0 VALUES (5);
+-- working example
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+\d products
+                                      Table "public.products"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ product_no      | integer                  |           |          | 
+ name            | text                     |           |          | 
+ price           | numeric                  |           |          | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+
+-- test DML
+INSERT INTO products VALUES (100, 'Washing Machine', 300.0);
+INSERT INTO products VALUES (200, 'Extended Warranty', 50.0);
+INSERT INTO products VALUES (300, 'Laptop', 250.0);
+SELECT now() AS ts1 \gset
+SELECT product_no, price FROM products ORDER BY product_no;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        300 | 250.0
+(3 rows)
+
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+ product_no 
+------------
+        100
+        200
+        300
+(3 rows)
+
+UPDATE products SET price = 75.0 WHERE product_no = 200;
+UPDATE products SET price = 350.0 WHERE product_no = 300;
+UPDATE products SET product_no = 400 WHERE product_no = 100;
+SELECT now() AS ts2 \gset
+SELECT product_no, price FROM products ORDER BY product_no;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        300 | 350.0
+        400 | 300.0
+(3 rows)
+
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+ product_no 
+------------
+        100
+        200
+        200
+        300
+        300
+        400
+(6 rows)
+
+DELETE FROM products WHERE product_no = 300;
+SELECT now() AS ts3 \gset
+SELECT product_no, price FROM products ORDER BY product_no;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        400 | 300.0
+(2 rows)
+
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+ product_no 
+------------
+        100
+        200
+        200
+        300
+        300
+        400
+(6 rows)
+
+INSERT INTO products VALUES (500, 'Spare Parts', 25.0);
+SELECT product_no, price FROM products ORDER BY product_no;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        400 | 300.0
+        500 |  25.0
+(3 rows)
+
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+ product_no 
+------------
+        100
+        200
+        200
+        300
+        300
+        400
+        500
+(7 rows)
+
+-- cannot update system versioning timestamps
+UPDATE products SET start_timestamp = now();
+ERROR:  column "start_timestamp" can only be updated to DEFAULT
+DETAIL:  Column "start_timestamp" is a generated column.
+-- these should fail... but currently succeed
+--UPDATE products SET start_timestamp = default;
+--UPDATE products SET end_timestamp = default;
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts1'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        300 | 250.0
+(3 rows)
+
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts2'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        300 | 350.0
+        400 | 300.0
+(3 rows)
+
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts3'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        400 | 300.0
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts2'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        200 |  75.0
+        300 | 250.0
+        300 | 350.0
+        400 | 300.0
+(6 rows)
+
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts3'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        200 |  75.0
+        300 | 250.0
+        300 | 350.0
+        400 | 300.0
+(6 rows)
+
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts3'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        200 |  75.0
+        300 | 250.0
+        300 | 350.0
+        400 | 300.0
+(6 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
+DROP TABLE stest0, stest1;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef71dd..991d84b955 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -80,7 +80,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioning
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 081fce32e7..6222a84fa3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -127,6 +127,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioning
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioning.sql b/src/test/regress/sql/system_versioning.sql
new file mode 100644
index 0000000000..7cadc0b1b5
--- /dev/null
+++ b/src/test/regress/sql/system_versioning.sql
@@ -0,0 +1,218 @@
+-- CREATE TABLE
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+-- Don't test DROP COLUMN at present because of klugey way we ADD SYSTEM VERSIONING
+--ALTER TABLE stest0 DROP COLUMN start_timestamp;
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+--truncation not allowed
+truncate table stest0;
+
+DROP TABLE stest2, stest3;
+
+-- test DML
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+UPDATE stest0 SET a = 4 WHERE a = 1;
+DELETE FROM stest0 WHERE a = 2;
+INSERT INTO stest0 VALUES (5);
+
+-- working example
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+\d products
+
+-- test DML
+INSERT INTO products VALUES (100, 'Washing Machine', 300.0);
+INSERT INTO products VALUES (200, 'Extended Warranty', 50.0);
+INSERT INTO products VALUES (300, 'Laptop', 250.0);
+SELECT now() AS ts1 \gset
+
+SELECT product_no, price FROM products ORDER BY product_no;
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+
+UPDATE products SET price = 75.0 WHERE product_no = 200;
+UPDATE products SET price = 350.0 WHERE product_no = 300;
+UPDATE products SET product_no = 400 WHERE product_no = 100;
+SELECT now() AS ts2 \gset
+
+SELECT product_no, price FROM products ORDER BY product_no;
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+
+DELETE FROM products WHERE product_no = 300;
+SELECT now() AS ts3 \gset
+
+SELECT product_no, price FROM products ORDER BY product_no;
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+
+INSERT INTO products VALUES (500, 'Spare Parts', 25.0);
+
+SELECT product_no, price FROM products ORDER BY product_no;
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+
+-- cannot update system versioning timestamps
+UPDATE products SET start_timestamp = now();
+-- these should fail... but currently succeed
+--UPDATE products SET start_timestamp = default;
+--UPDATE products SET end_timestamp = default;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts1'
+ORDER BY product_no, start_timestamp;
+
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts2'
+ORDER BY product_no, start_timestamp;
+
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts3'
+ORDER BY product_no, start_timestamp;
+
+-- BETWEEN ... AND ...
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts2'
+ORDER BY product_no, start_timestamp;
+
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts3'
+ORDER BY product_no, start_timestamp;
+
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts3'
+ORDER BY product_no, start_timestamp;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+
+DROP TABLE stest0, stest1;
#47Ryan Lambert
ryan@rustprooflabs.com
In reply to: Simon Riggs (#46)
Re: WIP: System Versioned Temporal Table

On Mon, Jan 11, 2021 at 7:02 AM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

On Sat, Jan 9, 2021 at 10:39 AM Simon Riggs
<simon.riggs@enterprisedb.com> wrote:

On Fri, Jan 8, 2021 at 9:19 PM Ryan Lambert <ryan@rustprooflabs.com>

wrote:

Updated v11 with additional docs and some rewording of messages/tests
to use "system versioning" correctly.

No changes on the points previously raised.

Thank you! The v11 applies and installs. I tried a simple test,

unfortunately it appears the versioning is not working. The initial value
is not preserved through an update and a new row does not appear to be
created.

Agreed. I already noted this in my earlier review comments.

I'm pleased to note that UPDATE-not-working was a glitch, possibly in
an earlier patch merge. That now works as advertised.

It is working as expected now, Thank you!

I've added fairly clear SGML docs to explain how the current patch
works, which should assist wider review.

The default column names changed to start_timestamp and end_timestamp. A
number of places in the docs still refer to StartTime and EndTime. I
prefer the new names without MixedCase.

Also moved test SQL around a bit, renamed some things in code for
readability, but not done any structural changes.

This is looking much better now... with the TODO/issues list now
looking like this...

* Anomalies around use of CURRENT_TIMESTAMP are not discussed or resolved.
Probably need to add a test that end_timestamp > start_timestamp or ERROR,
which effectively enforces serializability.

* No discussion, comments or tests around freezing and whether that
causes issues here

* What happens if you ask for a future time?
It will give an inconsistent result as it scans, so we should refuse a
query for time > current_timestamp.

* ALTER TABLE needs some work, it's a bit klugey at the moment and

needs extra tests.
Should refuse DROP COLUMN on a system time column, but currently doesn't

* UPDATE foo SET start_timestamp = DEFAULT should fail but currently
doesn't

* Do StartTime and EndTime show in SELECT *? Currently, yes. Would
guess we wouldn't want them to, not sure what standard says.

From here, the plan would be to set this to "Ready For Committer" in
about a week. That is not the same thing as me saying it is
ready-for-commit, but we need some more eyes on this patch to decide
if it is something we want and, if so, are the code changes cool.

Should I invest time now into further testing with more production-like
scenarios on this patch? Or would it be better to wait on putting effort
into that until it has had more review? I don't have much to offer for
help on your current todo list.

Thanks,

Ryan Lambert

#48Surafel Temesgen
surafel3000@gmail.com
In reply to: Andrew Dunstan (#41)
Re: WIP: System Versioned Temporal Table

Hi Andrew,
On Fri, Jan 8, 2021 at 4:38 PM Andrew Dunstan <andrew@dunslane.net> wrote:

On 1/8/21 7:33 AM, Simon Riggs wrote:

* What happens if you ask for a future time?
It will give an inconsistent result as it scans, so we should refuse a
query for time > current_timestamp.

That seems like a significant limitation. Can we fix it instead of
refusing the query?

Querying a table without system versioning with a value of non existent
data returns no record rather than error out or have other behavior. i
don't
understand the needs for special treatment here

regards
Surafel

#49Surafel Temesgen
surafel3000@gmail.com
In reply to: Simon Riggs (#46)
Re: WIP: System Versioned Temporal Table

Hi Simon,
Thank you for all the work you does

On Mon, Jan 11, 2021 at 5:02 PM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

* Anomalies around use of CURRENT_TIMESTAMP are not discussed or resolved.
Probably need to add a test that end_timestamp > start_timestamp or ERROR,
which effectively enforces serializability.

This scenario doesn't happen. There are no possibility of a record being
deleted or updated before inserting

* No discussion, comments or tests around freezing and whether that
causes issues here

This feature introduced no new issue regarding freezing. Adding
the doc about the table size growth because of a retention of old record
seems
enough for me

* ALTER TABLE needs some work, it's a bit klugey at the moment and
needs extra tests.
Should refuse DROP COLUMN on a system time column, but currently doesn't

* UPDATE foo SET start_timestamp = DEFAULT should fail but currently
doesn't

okay i will fix it

regards
Surafel

#50Surafel Temesgen
surafel3000@gmail.com
In reply to: Ryan Lambert (#42)
Re: WIP: System Versioned Temporal Table

Hi Ryan

On Fri, Jan 8, 2021 at 7:50 PM Ryan Lambert <ryan@rustprooflabs.com> wrote:

I prefer to have them hidden by default. This was mentioned up-thread
with no decision, it seems the standard is ambiguous. MS SQL appears to
have flip-flopped on this decision [1].

I will change it to hidden by default if there are no objection

regards
Surafel

#51Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Surafel Temesgen (#50)
Re: WIP: System Versioned Temporal Table

On Thu, Jan 14, 2021 at 5:46 PM Surafel Temesgen <surafel3000@gmail.com> wrote:

On Fri, Jan 8, 2021 at 7:50 PM Ryan Lambert <ryan@rustprooflabs.com> wrote:

I prefer to have them hidden by default. This was mentioned up-thread with no decision, it seems the standard is ambiguous. MS SQL appears to have flip-flopped on this decision [1].

I think the default should be like this:

SELECT * FROM foo FOR SYSTEM_TIME AS OF ...
should NOT include the Start and End timestamp columns
because this acts like a normal query just with a different snapshot timestamp

SELECT * FROM foo FOR SYSTEM_TIME BETWEEN x AND y
SHOULD include the Start and End timestamp columns
since this form of query can include multiple row versions for the
same row, so it makes sense to see the validity times

--
Simon Riggs http://www.EnterpriseDB.com/

#52Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Surafel Temesgen (#49)
Re: WIP: System Versioned Temporal Table

On Thu, Jan 14, 2021 at 5:42 PM Surafel Temesgen <surafel3000@gmail.com> wrote:

Hi Simon,
Thank you for all the work you does

No problem.

On Mon, Jan 11, 2021 at 5:02 PM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

* Anomalies around use of CURRENT_TIMESTAMP are not discussed or resolved.
Probably need to add a test that end_timestamp > start_timestamp or ERROR,
which effectively enforces serializability.

This scenario doesn't happen.

Yes, I think it can. The current situation is that the Start or End is
set to the Transaction Start Timestamp.
So if t2 starts before t1, then if t1 creates a row and t2 deletes it
then we will have start=t1 end=t2, but t2<t1
Your tests don't show that because it must happen concurrently.
We need to add an isolation test to show this, or to prove it doesn't happen.

There are no possibility of a record being deleted or updated before inserting

Agreed, but that was not the point.

--
Simon Riggs http://www.EnterpriseDB.com/

#53Ryan Lambert
ryan@rustprooflabs.com
In reply to: Simon Riggs (#51)
Re: WIP: System Versioned Temporal Table

On Thu, Jan 14, 2021 at 2:22 PM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

On Thu, Jan 14, 2021 at 5:46 PM Surafel Temesgen <surafel3000@gmail.com>
wrote:

On Fri, Jan 8, 2021 at 7:50 PM Ryan Lambert <ryan@rustprooflabs.com>

wrote:

I prefer to have them hidden by default. This was mentioned up-thread

with no decision, it seems the standard is ambiguous. MS SQL appears to
have flip-flopped on this decision [1].

I think the default should be like this:

SELECT * FROM foo FOR SYSTEM_TIME AS OF ...
should NOT include the Start and End timestamp columns
because this acts like a normal query just with a different snapshot
timestamp

SELECT * FROM foo FOR SYSTEM_TIME BETWEEN x AND y
SHOULD include the Start and End timestamp columns
since this form of query can include multiple row versions for the
same row, so it makes sense to see the validity times

+1

Ryan Lambert

#54Surafel Temesgen
surafel3000@gmail.com
In reply to: Simon Riggs (#52)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 15, 2021 at 12:27 AM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

Yes, I think it can. The current situation is that the Start or End is
set to the Transaction Start Timestamp.
So if t2 starts before t1, then if t1 creates a row and t2 deletes it
then we will have start=t1 end=t2, but t2<t1
Your tests don't show that because it must happen concurrently.
We need to add an isolation test to show this, or to prove it doesn't
happen.

Does MVCC allow that? i am not expert on MVCC but i don't
think t2 can see the row create by translation started before
itself

regards
Surafel

#55Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Surafel Temesgen (#54)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 15, 2021 at 4:46 PM Surafel Temesgen <surafel3000@gmail.com> wrote:

On Fri, Jan 15, 2021 at 12:27 AM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

Yes, I think it can. The current situation is that the Start or End is
set to the Transaction Start Timestamp.
So if t2 starts before t1, then if t1 creates a row and t2 deletes it
then we will have start=t1 end=t2, but t2<t1
Your tests don't show that because it must happen concurrently.
We need to add an isolation test to show this, or to prove it doesn't happen.

Does MVCC allow that? i am not expert on MVCC but i don't
think t2 can see the row create by translation started before
itself

Yeh. Read Committed mode can see anything committed prior to the start
of the current statement, but UPDATEs always update the latest version
even if they can't see it.

Anyway, isolationtester spec file needed to test this. See
src/test/isolation and examples in specs/ directory

--
Simon Riggs http://www.EnterpriseDB.com/

#56Surafel Temesgen
surafel3000@gmail.com
In reply to: Simon Riggs (#51)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 15, 2021 at 12:22 AM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

SELECT * FROM foo FOR SYSTEM_TIME AS OF ...
should NOT include the Start and End timestamp columns
because this acts like a normal query just with a different snapshot
timestamp

SELECT * FROM foo FOR SYSTEM_TIME BETWEEN x AND y
SHOULD include the Start and End timestamp columns
since this form of query can include multiple row versions for the
same row, so it makes sense to see the validity times

One disadvantage of returning system time columns is it
breaks upward compatibility. if an existing application wants to
switch to system versioning it will break.

regards
Surafel

#57Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Surafel Temesgen (#56)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 15, 2021 at 4:56 PM Surafel Temesgen <surafel3000@gmail.com> wrote:

On Fri, Jan 15, 2021 at 12:22 AM Simon Riggs <simon.riggs@enterprisedb.com> wrote:

SELECT * FROM foo FOR SYSTEM_TIME AS OF ...
should NOT include the Start and End timestamp columns
because this acts like a normal query just with a different snapshot timestamp

SELECT * FROM foo FOR SYSTEM_TIME BETWEEN x AND y
SHOULD include the Start and End timestamp columns
since this form of query can include multiple row versions for the
same row, so it makes sense to see the validity times

One disadvantage of returning system time columns is it
breaks upward compatibility. if an existing application wants to
switch to system versioning it will break.

There are no existing applications, so for PostgreSQL, it wouldn't be an issue.

If you mean compatibility with other databases, that might be an
argument to do what others have done. What have other databases done
for SELECT * ?

--
Simon Riggs http://www.EnterpriseDB.com/

#58legrand legrand
legrand_legrand@hotmail.com
In reply to: Simon Riggs (#57)
Re: WIP: System Versioned Temporal Table

Hello,

it seems that Oracle (11R2) doesn't add the Start and End timestamp columns
and permit statement like

select * from tt
union
select * from tt
AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL '6' SECOND)
minus
select * from tt
VERSIONS BETWEEN TIMESTAMP (SYSTIMESTAMP - INTERVAL '6' second) and
SYSTIMESTAMP;

Regards
PAscal

--
Sent from: https://www.postgresql-archive.org/PostgreSQL-hackers-f1928748.html

#59Vik Fearing
vik@postgresfriends.org
In reply to: Simon Riggs (#51)
Re: WIP: System Versioned Temporal Table

On 1/14/21 10:22 PM, Simon Riggs wrote:

On Thu, Jan 14, 2021 at 5:46 PM Surafel Temesgen <surafel3000@gmail.com> wrote:

On Fri, Jan 8, 2021 at 7:50 PM Ryan Lambert <ryan@rustprooflabs.com> wrote:

I prefer to have them hidden by default. This was mentioned up-thread with no decision, it seems the standard is ambiguous. MS SQL appears to have flip-flopped on this decision [1].

I think the default should be like this:

SELECT * FROM foo FOR SYSTEM_TIME AS OF ...
should NOT include the Start and End timestamp columns
because this acts like a normal query just with a different snapshot timestamp

SELECT * FROM foo FOR SYSTEM_TIME BETWEEN x AND y
SHOULD include the Start and End timestamp columns
since this form of query can include multiple row versions for the
same row, so it makes sense to see the validity times

I don't read the standard as being ambiguous about this at all. The
columns should be shown just like any other column of the table.

I am not opposed to being able to set an attribute on columns allowing
them to be excluded from "*" but that is irrelevant to this patch.
--
Vik Fearing

#60Vik Fearing
vik@postgresfriends.org
In reply to: Surafel Temesgen (#49)
Re: WIP: System Versioned Temporal Table

On 1/14/21 6:42 PM, Surafel Temesgen wrote:

Hi Simon,
Thank you for all the work you does

On Mon, Jan 11, 2021 at 5:02 PM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

* Anomalies around use of CURRENT_TIMESTAMP are not discussed or resolved.
Probably need to add a test that end_timestamp > start_timestamp or ERROR,
which effectively enforces serializability.

This scenario doesn't happen.

It *does* happen and the standard even provides a specific error code
for it (2201H).

Please look at my extension for this feature which implements all the
requirements of the standard (except syntax grammar, of course).
https://github.com/xocolatl/periods/
--
Vik Fearing

#61Surafel Temesgen
surafel3000@gmail.com
In reply to: Simon Riggs (#57)
Re: WIP: System Versioned Temporal Table

On Fri, Jan 15, 2021 at 8:02 PM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

There are no existing applications, so for PostgreSQL, it wouldn't be an
issue.

Yes we don't have but the main function of ALTER TABLE foo ADD SYSTEM
VERSIONING
is to add system versioning functionality to existing application

regards
Surafel

#62Vik Fearing
vik@postgresfriends.org
In reply to: Surafel Temesgen (#61)
Re: WIP: System Versioned Temporal Table

On 1/16/21 7:39 PM, Surafel Temesgen wrote:

On Fri, Jan 15, 2021 at 8:02 PM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

There are no existing applications, so for PostgreSQL, it wouldn't be an
issue.

Yes we don't have but the main function of ALTER TABLE foo ADD SYSTEM
VERSIONING
is to add system versioning functionality to existing application

I haven't looked at this patch in a while, but I hope that ALTER TABLE t
ADD SYSTEM VERSIONING is not adding any columns. That is a bug if it does.
--
Vik Fearing

#63Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#62)
Re: WIP: System Versioned Temporal Table

On Sat, Jan 16, 2021 at 10:12 PM Vik Fearing <vik@postgresfriends.org>
wrote:

I haven't looked at this patch in a while, but I hope that ALTER TABLE t
ADD SYSTEM VERSIONING is not adding any columns. That is a bug if it does.

Yes, that is how I implement it. I don't understand how it became a bug?

regards
Surafel

#64Vik Fearing
vik@postgresfriends.org
In reply to: Surafel Temesgen (#63)
Re: WIP: System Versioned Temporal Table

On 1/17/21 5:46 PM, Surafel Temesgen wrote:

On Sat, Jan 16, 2021 at 10:12 PM Vik Fearing <vik@postgresfriends.org>
wrote:

I haven't looked at this patch in a while, but I hope that ALTER TABLE t
ADD SYSTEM VERSIONING is not adding any columns. That is a bug if it does.

Yes, that is how I implement it. I don't understand how it became a bug?

This is not good, and I see that DROP SYSTEM VERSIONING also removes
these columns which is even worse. Please read the standard that you
are trying to implement!

I will do a more thorough review of the functionalities in this patch
(not necessarily the code) this week.
--
Vik Fearing

#65Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#64)
Re: WIP: System Versioned Temporal Table

On Mon, Jan 18, 2021 at 1:43 AM Vik Fearing <vik@postgresfriends.org> wrote:

This is not good, and I see that DROP SYSTEM VERSIONING also removes
these columns which is even worse. Please read the standard that you
are trying to implement!

The standard states the function of ALTER TABLE ADD SYSTEM VERSIONING
as "Alter a regular persistent base table to a system-versioned table" and
system versioned table is described in the standard by two generated
stored constraint columns and implemented as such.

I will do a more thorough review of the functionalities in this patch
(not necessarily the code) this week.

Please do

regards
Surafel

#66Vik Fearing
vik@postgresfriends.org
In reply to: Simon Riggs (#46)
Re: WIP: System Versioned Temporal Table

On 1/11/21 3:02 PM, Simon Riggs wrote:

* UPDATE foo SET start_timestamp = DEFAULT should fail but currently doesn't

I'm still in the weeds of reviewing this patch, but why should this
fail? It should not fail.
--
Vik Fearing

#67Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Vik Fearing (#66)
Re: WIP: System Versioned Temporal Table

On Tue, Jan 26, 2021 at 11:33 AM Vik Fearing <vik@postgresfriends.org> wrote:

On 1/11/21 3:02 PM, Simon Riggs wrote:

* UPDATE foo SET start_timestamp = DEFAULT should fail but currently doesn't

I'm still in the weeds of reviewing this patch, but why should this
fail? It should not fail.

It should not be possible for the user to change the start or end
timestamp of a system_time time range, by definition.

--
Simon Riggs http://www.EnterpriseDB.com/

#68Vik Fearing
vik@postgresfriends.org
In reply to: Simon Riggs (#67)
Re: WIP: System Versioned Temporal Table

On 1/26/21 1:16 PM, Simon Riggs wrote:

On Tue, Jan 26, 2021 at 11:33 AM Vik Fearing <vik@postgresfriends.org> wrote:

On 1/11/21 3:02 PM, Simon Riggs wrote:

* UPDATE foo SET start_timestamp = DEFAULT should fail but currently doesn't

I'm still in the weeds of reviewing this patch, but why should this
fail? It should not fail.

It should not be possible for the user to change the start or end
timestamp of a system_time time range, by definition.

Correct, but setting it to DEFAULT is not changing it.

See also SQL:2016 11.5 <default clause> General Rule 3.a.
--
Vik Fearing

#69Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Vik Fearing (#68)
Re: WIP: System Versioned Temporal Table

On Tue, Jan 26, 2021 at 12:51 PM Vik Fearing <vik@postgresfriends.org> wrote:

On 1/26/21 1:16 PM, Simon Riggs wrote:

On Tue, Jan 26, 2021 at 11:33 AM Vik Fearing <vik@postgresfriends.org> wrote:

On 1/11/21 3:02 PM, Simon Riggs wrote:

* UPDATE foo SET start_timestamp = DEFAULT should fail but currently doesn't

I'm still in the weeds of reviewing this patch, but why should this
fail? It should not fail.

It should not be possible for the user to change the start or end
timestamp of a system_time time range, by definition.

Correct, but setting it to DEFAULT is not changing it.

See also SQL:2016 11.5 <default clause> General Rule 3.a.

Thanks for pointing this out. Identity columns don't currently work that way.

--
Simon Riggs http://www.EnterpriseDB.com/

#70Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#66)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Tue, Jan 26, 2021 at 2:33 PM Vik Fearing <vik@postgresfriends.org> wrote:

I'm still in the weeds of reviewing this patch, but why should this
fail? It should not fail.

Attached is rebased patch that include isolation test

regards
Surafel

Attachments:

system-versioning-temporal-table_2021_v13.patchtext/x-patch; charset=US-ASCII; name=system-versioning-temporal-table_2021_v13.patchDownload
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index c12a32c8c7..7da692f724 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -4917,6 +4917,104 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+ <sect1 id="ddl-temporal">
+  <title>Temporal Data</title>
+
+   <indexterm>
+    <primary>temporal data</primary>
+   </indexterm>
+   <indexterm>
+    <primary>system versioning</primary>
+   </indexterm>
+   <indexterm>
+    <primary>SYSTEM_TIME</primary>
+   </indexterm>
+
+   <para>
+    <productname>PostgreSQL</productname> implements a table option
+    for system versioning that records each change permanently,
+    allowing access to the full history of data changes.
+   </para>
+
+   <para>
+    System versioning is optional and can be defined easily for a table:
+<programlisting>
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row verson.
+    The data type of these columns will be TIMESTAMP WITH TIME ZONE.
+    By default, the column names will be StartTime and EndTime, though
+    you can specify different names if you choose. The StartTime
+    column will be automatically added to the Primary Key of the
+    table.
+   </para>
+   <para>
+    StartTime will be generated always when a new row version is added
+    by INSERT or UPDATE, leaving EndTime set at Infinity. EndTime will
+    be generated always when a row version is UPDATEd or DELETEd.
+    This sequence of commands produces the full history shown in
+    the resulting query:
+<programlisting>
+INSERT INTO products VALUES (100, 'Washing Machine', 300.0);
+INSERT INTO products VALUES (200, 'Extended Warranty', 50.0);
+INSERT INTO products VALUES (300, 'Laptop', 250.0);
+UPDATE products SET price = 75.0 WHERE product_no = 200;
+UPDATE products SET price = 350.0 WHERE product_no = 300;
+UPDATE products SET product_no = 400 WHERE product_no = 100;
+DELETE FROM products WHERE product_no = 300;
+INSERT INTO products VALUES (500, 'Spare Parts', 25.0);
+
+SELECT * FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no, start_timestamp;
+ product_no |       name        | price |           start_timestamp           |            end_timestamp            
+------------+-------------------+-------+-------------------------------------+-------------------------------------
+        100 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.020361 2021 PST | Mon Jan 11 03:47:36.027029 2021 PST
+        200 | Extended Warranty |  50.0 | Mon Jan 11 03:47:36.022847 2021 PST | Mon Jan 11 03:47:36.02641 2021 PST
+        200 | Extended Warranty |  75.0 | Mon Jan 11 03:47:36.02641 2021 PST  | infinity
+        300 | Laptop            | 250.0 | Mon Jan 11 03:47:36.023316 2021 PST | Mon Jan 11 03:47:36.026779 2021 PST
+        300 | Laptop            | 350.0 | Mon Jan 11 03:47:36.026779 2021 PST | Mon Jan 11 03:47:36.029086 2021 PST
+        400 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.027029 2021 PST | infinity
+        500 | Spare Parts       |  25.0 | Mon Jan 11 03:47:36.032077 2021 PST | infinity
+(7 rows)
+</programlisting>
+    Note that this is
+    implemented by having an UPDATE become an INSERT of the data from
+    the old row version with EndTime set, plus the actual UPDATE,
+    increasing the number of actual visible rows in the table.
+    DELETE becomes an UPDATE of the old row version with EndTime set.
+    This means that UPDATEs and DELETEs still cause bloat that needs
+    to be removed by VACUUM.
+   </para>
+   <para>
+    Temporal queries are discussed in <xref linkend="temporal-queries"/>.
+   </para>
+   <para>
+    The system versioning period end column will be added to the
+    Primary Key of the table as a way of ensuring that concurrent
+    INSERTs conflict correctly. This alters the way that Foreign
+    Keys work for tables with system versioning.
+   </para>
+   <para>
+    The user may never set the system time column values explicitly
+    on INSERT for generated columns. Currently, UPDATE does allow
+    setting the period start and end dates to DEFAULT values.
+   </para>
+   <para>
+    SELECT * currently returns the period start and end columns, even
+    when added implicitly. 
+   </para>
+   <para>
+    The SQL Standard defines that the timestamps used should be the
+    CURRENT_TIMESTAMP value defined at statement start. This currently
+    leads to anomalies in some cases where the EndTime can be set to
+    a value earlier than the StartTime.
+   </para>
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index ca51204875..b752960d42 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -2588,4 +2588,92 @@ SELECT * FROM t;
 
  </sect1>
 
+ <sect1 id="temporal-queries">
+  <title>Temporal Queries</title>
+
+   <para>
+    <productname>PostgreSQL</productname> implements an option to
+    to add system versioning onto a user table. Such tables can
+    be accessed normally to see the current contents, but also
+    allow you to access data using both temporal and
+    historical queries.  Queries must specify a period name,
+    which for system versioning must be <quote>SYSTEM_TIME</quote>.
+   </para>
+
+   <para>
+    Historical queries show the changes to rows over time, such as the
+    following query that shows changes to a company's products and pricing:
+<programlisting>
+SELECT * FROM products FOR system_time BETWEEN '-infinity' AND 'infinity' ORDER BY product_no, start_timestamp;
+ product_no |       name        | price |           start_timestamp           |            end_timestamp            
+------------+-------------------+-------+-------------------------------------+-------------------------------------
+        100 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.020361 2021 PST | Mon Jan 11 08:47:36.027029 2021 PST
+        200 | Extended Warranty |  50.0 | Mon Jan 11 03:47:36.022847 2021 PST | Mon Jan 11 05:47:36.02641 2021 PST
+        200 | Extended Warranty |  75.0 | Mon Jan 11 05:47:36.02641 2021 PST  | infinity
+        300 | Laptop            | 250.0 | Mon Jan 11 03:47:36.023316 2021 PST | Mon Jan 11 06:47:36.026779 2021 PST
+        300 | Laptop            | 350.0 | Mon Jan 11 06:47:36.026779 2021 PST | Mon Jan 11 07:47:36.029086 2021 PST
+        400 | Washing Machine   | 300.0 | Mon Jan 11 08:47:36.027029 2021 PST | infinity
+        500 | Spare Parts       |  25.0 | Mon Jan 11 09:47:36.032077 2021 PST | infinity
+(7 rows)
+</programlisting>
+    This query shows the full history over all time since it includes
+    all changes between a timestamp of '-infinity' to 'infinity'.
+    The user can specify tighter time ranges to filter away unwanted
+    parts of the change history.
+   </para>
+
+   <para>
+    Normal non-temporal queries show the current contents of the
+    system versioned table, just like normal tables:
+<programlisting>
+SELECT product_no, name, price FROM products ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        200 | Extended Warranty |  75.0 
+        400 | Washing Machine   | 300.0 
+        500 | Spare Parts       |  25.0 
+(3 rows)
+</programlisting>
+    which is achieved by generating adding WHERE clauses to exclude
+    the historical row versions from the query. This can be seen
+    more clearly using <command>EXPLAIN</command>:
+<programlisting>
+EXPLAIN (COSTS OFF) SELECT * FROM products;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Seq Scan on products
+   Filter: (end_timestamp = 'infinity'::timestamp with time zone)
+(2 rows)
+</programlisting>
+    which shows that the presence of historical row versions will
+    affect the performance of all types of query on tables with
+    system versioning.
+   </para>
+
+   <para>
+    Temporal queries show the table rows at a specific timestamp, e.g.
+<programlisting>
+SELECT product_no, name, price FROM products FOR system_time AS OF 'Mon Jan 11 07:30:00 2021 PST' ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        100 | Washing Machine   | 300.0 
+        200 | Extended Warranty |  75.0 
+        300 | Laptop            | 350.0 
+(3 rows)
+</programlisting>
+
+    or
+
+<programlisting>
+SELECT product_no, name, price FROM products FOR system_time AS OF 'Mon Jan 11 09:30:00 2021 PST' ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        200 | Extended Warranty |  75.0 
+        400 | Washing Machine   | 300.0 
+(2 rows)
+</programlisting>
+   </para>
+
+ </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c25ef5abd6..ba0512ea15 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,19 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table by adding two columns which
+      record the period start and period end for the validity of each row version.
+      The column names will be StartTime and EndTime respectively, though the data
+      types are TIMESTAMP WITH TIME ZONE. See
+      <xref linkend="ddl-temporal"/>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +193,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty then history records are also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 569f4c9da7..598f019be2 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -981,6 +1007,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      Specifies the pair of columns that hold the row start
+      timestamp and row end timestamp column names.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1235,6 +1271,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This clause specifies that the table contains multiple historical
+      row versions that may be viewed using temporal queries.
+      If period columns are not specified explicitly the default columns
+      StartTime and EndTime will be added automatically.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 6757033e09..3038476f62 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -529,6 +534,42 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Allows temporal queries for tables defined with system versioning.
+        This specifies a single timestamp that is used in place of the
+        normal transaction snapshot to determine which row version of a
+        row is visible for this query. At most one row version will be
+        returned for any row.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        For tables with system versioning this specifies that all
+        row versions visible at any point over the time range
+        will be visible to this query.
+        Note that this form of query allows multiple row versions to be
+        returned for one row rather than just a single row. It allows
+        the query to inspect the history of UPDATEs and DELETEs
+        that have occured to row after the initial INSERT, over
+        the time range specified. To see all changes over time
+        specify a start_time of '-Infinity' and an end_time of 'Infinity'.
+       </para>
+       <para>
+        Optionally, <replaceable class="parameter">SYMMETRIC</replaceable>
+        or <replaceable class="parameter">ASYMMETRIC</replaceable> may
+        be specified for the time range.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 902f59440c..bb222b4f6c 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->has_system_versioning = constr->has_system_versioning;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->has_system_versioning != constr2->has_system_versioning)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->has_system_versioning = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index c39cc736ed..24ffaf268a 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1014,6 +1014,11 @@ CopyFrom(CopyFromState cstate)
 					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
 											   CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->has_system_versioning)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8687e9a97c..ed9ae63d7a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,6 +57,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -74,6 +75,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -168,6 +170,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -422,11 +427,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1574,6 +1580,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1582,6 +1589,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->has_system_versioning)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate table with system versioning")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3769,8 +3784,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PeriodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3799,6 +3816,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4367,6 +4385,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4423,6 +4442,35 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to be specified
+			 */
+			if (context)
+			{
+				if (context->hasSystemVersioning)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4522,16 +4570,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4755,6 +4803,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4811,7 +4860,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5187,6 +5236,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5224,6 +5274,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5291,6 +5342,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg("rewriting table \"%s\"",
@@ -5379,6 +5436,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5449,6 +5513,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5484,6 +5552,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6300,6 +6369,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6619,6 +6696,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7062,6 +7146,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7162,6 +7253,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7569,6 +7667,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7774,11 +7879,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7821,6 +7927,32 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+#ifdef NOTUSED
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+#endif
+
+	/*
+	 * Reviewers note: We should be disallowing DROP COLUMN on a
+	 * system time column, but DROP SYSTEM VERSIONING is currently
+	 * kluged to generate multiple DropColumn subcommands, which
+	 * means we need to allow this for now, even though an explicit
+	 * DROP COLUMN will crash the server. Needs work.
+	 */
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7881,11 +8013,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -7906,9 +8042,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11369,6 +11505,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12113,10 +12255,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12450,6 +12595,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15929,7 +16081,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..93c8c2dec2 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 5d90337498..946221ae83 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -367,6 +367,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -520,6 +640,13 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		WCOKind		wco_kind;
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Constraints might reference the tableoid column, so (re-)initialize
 		 * tts_tableOid before evaluating them.
@@ -917,6 +1044,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple while attempting delete with system versioning");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1411,6 +1562,40 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			/*
+			 * Insert a new row to represent the old row with EndTime set
+			 * Note that this creates a new row version rather than updating in place
+			 * the existing row version.
+			 */
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple while attempting update with system versioning");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+
+			/*
+			 * Set the StartTime for the soon to be newly updated row
+			 */
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ba3ccc712c..8d907859f4 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3407,6 +3407,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioning);
 }
 
 static CreateStmt *
@@ -4820,6 +4821,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5714,6 +5739,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a2ef853dc2..f3b8dd51c9 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1258,6 +1258,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioning);
 
 	return true;
 }
@@ -2945,6 +2946,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3767,6 +3789,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..cb9e7eef01 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,126 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexpr, BoolExpr))
+	{
+		BoolExpr *blexpr = (BoolExpr *) lexpr;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, SYSTEM_VERSIONING_DEFAULT_START_NAME) == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = SYSTEM_VERSIONING_DEFAULT_START_NAME;
+		n->constraints = list_make1((Node *) c);
+	}
+	else if (strcmp(name, SYSTEM_VERSIONING_DEFAULT_END_NAME) == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = SYSTEM_VERSIONING_DEFAULT_END_NAME;
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+		elog(ERROR, "unexpected temporal column name");
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8392be6d44..f79740b361 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2643,6 +2643,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioning);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4e6497ff32..8f53e89a36 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 54ef61bfb3..18cdd27b3f 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index da322b453e..800fa9afae 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioning_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioning_table(RangeTblEntry *rte);
 
 
 /*
@@ -2354,3 +2359,193 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if this is a system versioned relation and the where clause did not
+ * already contain a filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioning_table(rte) ||
+			check_system_versioning_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filters history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioning_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioning_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioning_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioning_column_walker(node, rte);
+}
+
+static bool
+check_system_versioning_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->has_system_versioning;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6548389225..c9f2b7acaf 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -455,6 +456,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1230,6 +1236,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1327,6 +1342,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2275,6 +2295,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7574d545e0..81b3ff8a60 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -134,6 +134,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioning;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -152,7 +166,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -177,7 +190,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -250,6 +262,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -384,12 +399,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	parse_toplevel stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith opt_definition func_args func_args_list
+				opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -507,7 +524,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -545,7 +562,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -676,7 +693,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -691,7 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -702,7 +719,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -723,7 +740,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 /*
  * The grammar likewise thinks these tokens are keywords, but they are never
@@ -750,6 +767,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -789,6 +809,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2140,6 +2165,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PeriodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2167,7 +2200,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2306,7 +2347,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2315,8 +2368,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2325,6 +2390,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3232,12 +3306,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioning = ($11)->systemVersioning;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3251,12 +3326,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioning = ($14)->systemVersioning;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3270,13 +3346,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioning = ($10)->systemVersioning;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3290,13 +3367,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioning = ($13)->systemVersioning;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3310,13 +3388,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioning = ($12)->systemVersioning;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3330,13 +3409,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioning = ($15)->systemVersioning;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3413,6 +3493,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3509,6 +3590,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3591,12 +3682,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3614,6 +3705,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3636,6 +3728,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -4012,9 +4128,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioning = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4150,7 +4291,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11771,7 +11912,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11850,12 +11991,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11863,6 +12008,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -11961,7 +12119,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
 
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
+
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15380,6 +15585,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15456,6 +15662,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15486,6 +15693,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -15951,6 +16159,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -16041,6 +16250,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -16086,6 +16296,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16202,16 +16413,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16617,23 +16818,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexpr, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 672245ded7..dabcab94ed 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1141,6 +1143,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->has_system_versioning = (tupdesc->constr && tupdesc->constr->has_system_versioning);
+		table_close(rel, NoLock);
+
+		if (!rte->has_system_versioning)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("temporal clause can only specified for a table with system versioning")));
+
+		changeTemporalToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3690,3 +3721,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTemporalToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index b31f3afa03..7967fa7a8a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		hasSystemVersioning;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -252,6 +262,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.hasSystemVersioning = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -288,6 +302,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
 
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -295,6 +313,27 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time columns and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is prepended to the table
+	 * definition. This is an extension to the SQL Standard.
+	 */
+	if (!cxt.hasSystemVersioning && stmt->systemVersioning)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_START_NAME);
+		endCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_END_NAME);
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(list_make2(startCol, endCol), stmt->tableElts);
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -306,6 +345,36 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	if (cxt.hasSystemVersioning)
+	{
+		ListCell   *lc;
+
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+
+		/*
+		 * End time column is added to primary and unique key constraint
+		 * implicitly to make history and current data co-exist.
+		 */
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -746,6 +815,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row start time must be timestamptz")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->hasSystemVersioning = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->hasSystemVersioning = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1433,6 +1558,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time must reference the row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time must reference the row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3173,7 +3327,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3200,7 +3354,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3237,6 +3391,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.hasSystemVersioning = false;
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3271,6 +3428,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PeriodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3280,6 +3445,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->has_system_versioning) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3447,6 +3629,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.hasSystemVersioning)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->hasSystemVersioning = cxt.hasSystemVersioning;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->hasSystemVersioning = cxt.hasSystemVersioning;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3478,7 +3675,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 875de7ba28..e000a4040a 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -137,6 +137,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -188,6 +191,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			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 0c7508a0d8..7806c2257b 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -931,14 +931,24 @@ rewriteTargetListIU(List *targetList,
 								NameStr(att_tup->attname)),
 						 errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.",
 								   NameStr(att_tup->attname))));
-
+#ifdef NOTUSED
+			if (att_tup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+				att_tup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+				ereport(ERROR,
+					(errcode(ERRCODE_GENERATED_ALWAYS),
+					 errmsg("column \"%s\" cannot be updated",
+							NameStr(att_tup->attname)),
+					 errdetail("Column \"%s\" is a system versioning column.",
+							   NameStr(att_tup->attname))));
+			else if (att_tup->attgenerated && new_tle && !apply_default)
+#endif
 			if (att_tup->attgenerated && new_tle && !apply_default)
-				ereport(ERROR,
-						(errcode(ERRCODE_GENERATED_ALWAYS),
-						 errmsg("column \"%s\" can only be updated to DEFAULT",
-								NameStr(att_tup->attname)),
-						 errdetail("Column \"%s\" is a generated column.",
-								   NameStr(att_tup->attname))));
+				 ereport(ERROR,
+					(errcode(ERRCODE_GENERATED_ALWAYS),
+					 errmsg("column \"%s\" can only be updated to DEFAULT",
+							NameStr(att_tup->attname)),
+					 errdetail("Column \"%s\" is a generated column.",
+							   NameStr(att_tup->attname))));
 		}
 
 		if (att_tup->attgenerated)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 1d81071c35..0b28a34aa7 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -59,7 +60,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1236,6 +1239,10 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		hasSystemVersioning = false;
+					TupleDesc	tupdesc;
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1246,6 +1253,83 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							hasSystemVersioning = tupdesc->constr && tupdesc->constr->has_system_versioning;
+							if (hasSystemVersioning)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already defined with system versioning")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning to table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statement
+							 */
+							startTimeCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_START_NAME);
+							endTimeCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_END_NAME);
+
+							/*
+							 * create alter table add column cmd and append to the end
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							hasSystemVersioning = tupdesc->constr && tupdesc->constr->has_system_versioning;
+							if (!hasSystemVersioning)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not defined with system versioning")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1256,6 +1340,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.hasSystemVersioning = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ef510cd01..8312a36261 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->has_system_versioning = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->has_system_versioning = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 798d14580e..3a734fb5b9 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15869,6 +15869,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a92b4..c89cf2f4bc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2061,6 +2061,10 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated always as identity";
 			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 				default_str = "generated by default as identity";
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 			{
 				default_str = psprintf("generated always as (%s) stored",
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f45d47aab7..3a7ffd4464 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		has_system_versioning;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 059dec3647..d57521009e 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -204,6 +204,9 @@ DECLARE_UNIQUE_INDEX(pg_attribute_relid_attnum_index, 2659, on pg_attribute usin
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 83e2965531..be9790a6be 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..9af47ba697 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,16 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+
+#define SYSTEM_VERSIONING_DEFAULT_START_NAME "start_timestamp"
+#define SYSTEM_VERSIONING_DEFAULT_END_NAME "end_timestamp"
+
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
+
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index caed683ba9..941fcec266 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -480,6 +480,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index dc2bb40926..a533a897fa 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1009,6 +1009,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		has_system_versioning;	/* relation has system versioning */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1792,7 +1793,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1873,7 +1874,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PeriodColumn				/* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2098,6 +2102,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioning;	/* true with system versioning */
 } CreateStmt;
 
 /* ----------
@@ -2147,7 +2152,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3575,4 +3582,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 8d1d6c1b42..dd2d6bce2e 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 8c554e1f69..c10366ed26 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index dfc214b06f..41a708b952 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -199,7 +199,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bfa4a6b0f2..3a6251f800 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 841062b4b3..4184ea14f4 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool hasSystemVersioning;	/* true if table has system versioning */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/isolation/expected/lock-update-delete-system-versioned.out b/src/test/isolation/expected/lock-update-delete-system-versioned.out
new file mode 100644
index 0000000000..a48375df3b
--- /dev/null
+++ b/src/test/isolation/expected/lock-update-delete-system-versioned.out
@@ -0,0 +1,213 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s2b s1l s2u s2_blocker1 s2_unlock s2c
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s2c: COMMIT;
+step s1l: <... completed>
+key            value          
+
+
+starting permutation: s2b s1l s2u s2_blocker2 s2_unlock s2c
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s2c: COMMIT;
+step s1l: <... completed>
+key            value          
+
+
+starting permutation: s2b s1l s2u s2_blocker3 s2_unlock s2c
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1l: <... completed>
+key            value          
+
+1              1              
+step s2c: COMMIT;
+
+starting permutation: s2b s1l s2u s2_blocker1 s2_unlock s2r
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s2r: ROLLBACK;
+step s1l: <... completed>
+key            value          
+
+1              1              
+
+starting permutation: s2b s1l s2u s2_blocker2 s2_unlock s2r
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s2r: ROLLBACK;
+step s1l: <... completed>
+key            value          
+
+1              1              
+
+starting permutation: s2b s1l s2u s2_blocker3 s2_unlock s2r
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1l: <... completed>
+key            value          
+
+1              1              
+step s2r: ROLLBACK;
+
+starting permutation: s2b s1l s2u s2_blocker1 s2c s2_unlock
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1l: <... completed>
+key            value          
+
+
+starting permutation: s2b s1l s2u s2_blocker2 s2c s2_unlock
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1l: <... completed>
+key            value          
+
+
+starting permutation: s2b s1l s2u s2_blocker3 s2c s2_unlock
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2c: COMMIT;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1l: <... completed>
+key            value          
+
+1              1              
+
+starting permutation: s2b s1l s2u s2_blocker1 s2r s2_unlock
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker1: DELETE FROM foo;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1l: <... completed>
+key            value          
+
+1              1              
+
+starting permutation: s2b s1l s2u s2_blocker2 s2r s2_unlock
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker2: UPDATE foo SET key = 2 WHERE key = 1;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1l: <... completed>
+key            value          
+
+1              1              
+
+starting permutation: s2b s1l s2u s2_blocker3 s2r s2_unlock
+pg_advisory_lock
+
+               
+step s2b: BEGIN;
+step s1l: SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; <waiting ...>
+step s2u: UPDATE foo SET value = 2 WHERE key = 1;
+step s2_blocker3: UPDATE foo SET value = 2 WHERE key = 1;
+step s2r: ROLLBACK;
+step s2_unlock: SELECT pg_advisory_unlock(0);
+pg_advisory_unlock
+
+t              
+step s1l: <... completed>
+key            value          
+
+1              1              
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f2e752c445..be55b670b4 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -32,6 +32,7 @@ test: fk-partitioned-2
 test: eval-plan-qual
 test: eval-plan-qual-trigger
 test: lock-update-delete
+test: lock-update-delete-system-versioned
 test: lock-update-traversal
 test: inherit-temp
 test: insert-conflict-do-nothing
diff --git a/src/test/isolation/specs/lock-update-delete-system-versioned.spec b/src/test/isolation/specs/lock-update-delete-system-versioned.spec
new file mode 100644
index 0000000000..42fb18901f
--- /dev/null
+++ b/src/test/isolation/specs/lock-update-delete-system-versioned.spec
@@ -0,0 +1,62 @@
+# This test verifies behavior when traversing an update chain during
+# locking an old version of the tuple for system versioned table.
+# There are three tests here:
+# 1. update the tuple, then delete it; a second transaction locks the
+# first version.  This should raise an error if the DELETE succeeds,
+# but be allowed to continue if it aborts.
+# 2. Same as (1), except that instead of deleting the tuple, we merely
+# update its key.  The behavior should be the same as for (1).
+# 3. Same as (2), except that we update the tuple without modifying its
+# key. In this case, no error should be raised.
+# When run in REPEATABLE READ or SERIALIZABLE transaction isolation levels, all
+# permutations that commit s2 cause a serializability error; all permutations
+# that rollback s2 can get through.
+#
+# We use an advisory lock (which is locked during s1's setup) to let s2 obtain
+# its snapshot early and only allow it to actually traverse the update chain
+# when s1 is done creating it.
+
+setup
+{
+  DROP TABLE IF EXISTS foo;
+  CREATE TABLE foo (
+	key		int PRIMARY KEY,
+	value	int
+  ) WITH SYSTEM VERSIONING;
+
+  INSERT INTO foo VALUES (1, 1);
+}
+
+teardown
+{
+  DROP TABLE foo;
+}
+
+session "s1"
+# obtain lock on the tuple, traversing its update chain
+step "s1l"	{ SELECT key, value  FROM foo WHERE pg_advisory_xact_lock(0) IS NOT NULL AND key = 1 FOR KEY SHARE; }
+
+session "s2"
+setup		{ SELECT pg_advisory_lock(0); }
+step "s2b"	{ BEGIN; }
+step "s2u"	{ UPDATE foo SET value = 2 WHERE key = 1; }
+step "s2_blocker1"	{ DELETE FROM foo; }
+step "s2_blocker2"	{ UPDATE foo SET key = 2 WHERE key = 1; }
+step "s2_blocker3"	{ UPDATE foo SET value = 2 WHERE key = 1; }
+step "s2_unlock" { SELECT pg_advisory_unlock(0); }
+step "s2c"	{ COMMIT; }
+step "s2r"	{ ROLLBACK; }
+
+permutation "s2b" "s1l" "s2u" "s2_blocker1" "s2_unlock" "s2c"
+permutation "s2b" "s1l" "s2u" "s2_blocker2" "s2_unlock" "s2c"
+permutation "s2b" "s1l" "s2u" "s2_blocker3" "s2_unlock" "s2c"
+permutation "s2b" "s1l" "s2u" "s2_blocker1" "s2_unlock" "s2r"
+permutation "s2b" "s1l" "s2u" "s2_blocker2" "s2_unlock" "s2r"
+permutation "s2b" "s1l" "s2u" "s2_blocker3" "s2_unlock" "s2r"
+
+permutation "s2b" "s1l" "s2u" "s2_blocker1" "s2c" "s2_unlock"
+permutation "s2b" "s1l" "s2u" "s2_blocker2" "s2c" "s2_unlock"
+permutation "s2b" "s1l" "s2u" "s2_blocker3" "s2c" "s2_unlock"
+permutation "s2b" "s1l" "s2u" "s2_blocker1" "s2r" "s2_unlock"
+permutation "s2b" "s1l" "s2u" "s2_blocker2" "s2r" "s2_unlock"
+permutation "s2b" "s1l" "s2u" "s2_blocker3" "s2r" "s2_unlock"
diff --git a/src/test/regress/expected/system_versioning.out b/src/test/regress/expected/system_versioning.out
new file mode 100644
index 0000000000..e1b44d1c92
--- /dev/null
+++ b/src/test/regress/expected/system_versioning.out
@@ -0,0 +1,405 @@
+-- CREATE TABLE
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  data type of row start time must be timestamptz
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time must reference the row start time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+ERROR:  period end time must reference the row end time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  period start time must reference the row start time column
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row start time specified more than once
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+ERROR:  row end time specified more than once
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+\d stest2
+                                       Table "public.stest2"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           |          | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+\d stest3
+                                       Table "public.stest3"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           |          | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+\d stest3
+               Table "public.stest3"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+
+-- ALTER TABLE
+-- Don't test DROP COLUMN at present because of klugey way we ADD SYSTEM VERSIONING
+--ALTER TABLE stest0 DROP COLUMN start_timestamp;
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+ERROR:  column "start_timestamp" of relation "stest0" is system time column
+--truncation not allowed
+truncate table stest0;
+ERROR:  cannot truncate table with system versioning
+DROP TABLE stest2, stest3;
+-- test DML
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+UPDATE stest0 SET a = 4 WHERE a = 1;
+DELETE FROM stest0 WHERE a = 2;
+INSERT INTO stest0 VALUES (5);
+-- working example
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+\d products
+                                      Table "public.products"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ product_no      | integer                  |           |          | 
+ name            | text                     |           |          | 
+ price           | numeric                  |           |          | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+
+-- test DML
+INSERT INTO products VALUES (100, 'Washing Machine', 300.0);
+INSERT INTO products VALUES (200, 'Extended Warranty', 50.0);
+INSERT INTO products VALUES (300, 'Laptop', 250.0);
+SELECT now() AS ts1 \gset
+SELECT product_no, price FROM products ORDER BY product_no;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        300 | 250.0
+(3 rows)
+
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+ product_no 
+------------
+        100
+        200
+        300
+(3 rows)
+
+UPDATE products SET price = 75.0 WHERE product_no = 200;
+UPDATE products SET price = 350.0 WHERE product_no = 300;
+UPDATE products SET product_no = 400 WHERE product_no = 100;
+SELECT now() AS ts2 \gset
+SELECT product_no, price FROM products ORDER BY product_no;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        300 | 350.0
+        400 | 300.0
+(3 rows)
+
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+ product_no 
+------------
+        100
+        200
+        200
+        300
+        300
+        400
+(6 rows)
+
+DELETE FROM products WHERE product_no = 300;
+SELECT now() AS ts3 \gset
+SELECT product_no, price FROM products ORDER BY product_no;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        400 | 300.0
+(2 rows)
+
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+ product_no 
+------------
+        100
+        200
+        200
+        300
+        300
+        400
+(6 rows)
+
+INSERT INTO products VALUES (500, 'Spare Parts', 25.0);
+SELECT product_no, price FROM products ORDER BY product_no;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        400 | 300.0
+        500 |  25.0
+(3 rows)
+
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+ product_no 
+------------
+        100
+        200
+        200
+        300
+        300
+        400
+        500
+(7 rows)
+
+-- cannot update system versioning timestamps
+UPDATE products SET start_timestamp = now();
+ERROR:  column "start_timestamp" can only be updated to DEFAULT
+DETAIL:  Column "start_timestamp" is a generated column.
+-- these should fail... but currently succeed
+--UPDATE products SET start_timestamp = default;
+--UPDATE products SET end_timestamp = default;
+/*
+ * Temporal Queries
+ */
+-- AS OF ...
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts1'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        300 | 250.0
+(3 rows)
+
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts2'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        300 | 350.0
+        400 | 300.0
+(3 rows)
+
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts3'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        200 |  75.0
+        400 | 300.0
+(2 rows)
+
+-- BETWEEN ... AND ...
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts2'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        200 |  75.0
+        300 | 250.0
+        300 | 350.0
+        400 | 300.0
+(6 rows)
+
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts3'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        200 |  75.0
+        300 | 250.0
+        300 | 350.0
+        400 | 300.0
+(6 rows)
+
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts3'
+ORDER BY product_no, start_timestamp;
+ product_no | price 
+------------+-------
+        100 | 300.0
+        200 |  50.0
+        200 |  75.0
+        300 | 250.0
+        300 | 350.0
+        400 | 300.0
+(6 rows)
+
+/*
+ * JOINS
+ */
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+(1 row)
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(3 rows)
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+ a | x  | y 
+---+----+---
+   | 11 | 1
+   | 22 | 2
+ 3 | 33 | 3
+ 4 |    |  
+ 5 |    |  
+(5 rows)
+
+DROP TABLE stestx;
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+SELECT * FROM stest2v;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+ a 
+---
+ 3
+ 4
+ 5
+(3 rows)
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+ a | start_timestamp | end_timestamp 
+---+-----------------+---------------
+(0 rows)
+
+\d stest1
+                                       Table "public.stest1"
+     Column      |           Type           | Collation | Nullable |            Default            
+-----------------+--------------------------+-----------+----------+-------------------------------
+ a               | integer                  |           | not null | 
+ start_timestamp | timestamp with time zone |           | not null | generated always as row start
+ end_timestamp   | timestamp with time zone |           | not null | generated always as row end
+Inherits: stest0
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+ a 
+---
+ 4
+(1 row)
+
+DROP TABLE stest0, stest1;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef71dd..991d84b955 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -80,7 +80,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan collate.icu.utf8 incremental_sort system_versioning
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 081fce32e7..6222a84fa3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -127,6 +127,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioning
 test: join_hash
 test: create_table_like
 test: alter_generic
diff --git a/src/test/regress/sql/system_versioning.sql b/src/test/regress/sql/system_versioning.sql
new file mode 100644
index 0000000000..7cadc0b1b5
--- /dev/null
+++ b/src/test/regress/sql/system_versioning.sql
@@ -0,0 +1,218 @@
+-- CREATE TABLE
+-- invalid datatype
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp GENERATED ALWAYS AS ROW START,
+    end_timestamp integer GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- references to other column in period columns
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (a, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, a)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (end_timestamp, start_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- duplicate system time column
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    start_timestamp1 timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+CREATE TABLE stest1 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS row START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    end_timestamp1 timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- success
+CREATE TABLE stest0 (
+    a integer PRIMARY KEY,
+    start_timestamp timestamp with time zone GENERATED ALWAYS AS ROW START,
+    end_timestamp timestamp with time zone GENERATED ALWAYS AS ROW END,
+    PERIOD FOR SYSTEM_TIME (start_timestamp, end_timestamp)
+) WITH SYSTEM VERSIONING;
+
+-- default system time column usage
+CREATE TABLE stest2 (
+    a integer
+) WITH SYSTEM VERSIONING;
+
+\d stest2
+
+-- ALTER TABLE tbName ADD SYSTEM VERSIONING
+CREATE TABLE stest3 (
+    a integer
+);
+
+\d stest3
+
+ALTER TABLE stest3 ADD SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE tbName DROP SYSTEM VERSIONING
+ALTER TABLE stest3 DROP SYSTEM VERSIONING;
+
+\d stest3
+
+-- ALTER TABLE
+-- Don't test DROP COLUMN at present because of klugey way we ADD SYSTEM VERSIONING
+--ALTER TABLE stest0 DROP COLUMN start_timestamp;
+ALTER TABLE stest0 ALTER start_timestamp DROP NOT NULL;
+ALTER TABLE stest0 ALTER COLUMN start_timestamp SET DATA TYPE character;
+
+--truncation not allowed
+truncate table stest0;
+
+DROP TABLE stest2, stest3;
+
+-- test DML
+INSERT INTO stest0 VALUES (1);
+INSERT INTO stest0 VALUES (2);
+INSERT INTO stest0 VALUES (3);
+UPDATE stest0 SET a = 4 WHERE a = 1;
+DELETE FROM stest0 WHERE a = 2;
+INSERT INTO stest0 VALUES (5);
+
+-- working example
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+\d products
+
+-- test DML
+INSERT INTO products VALUES (100, 'Washing Machine', 300.0);
+INSERT INTO products VALUES (200, 'Extended Warranty', 50.0);
+INSERT INTO products VALUES (300, 'Laptop', 250.0);
+SELECT now() AS ts1 \gset
+
+SELECT product_no, price FROM products ORDER BY product_no;
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+
+UPDATE products SET price = 75.0 WHERE product_no = 200;
+UPDATE products SET price = 350.0 WHERE product_no = 300;
+UPDATE products SET product_no = 400 WHERE product_no = 100;
+SELECT now() AS ts2 \gset
+
+SELECT product_no, price FROM products ORDER BY product_no;
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+
+DELETE FROM products WHERE product_no = 300;
+SELECT now() AS ts3 \gset
+
+SELECT product_no, price FROM products ORDER BY product_no;
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+
+INSERT INTO products VALUES (500, 'Spare Parts', 25.0);
+
+SELECT product_no, price FROM products ORDER BY product_no;
+SELECT product_no FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no;
+
+-- cannot update system versioning timestamps
+UPDATE products SET start_timestamp = now();
+-- these should fail... but currently succeed
+--UPDATE products SET start_timestamp = default;
+--UPDATE products SET end_timestamp = default;
+
+/*
+ * Temporal Queries
+ */
+
+-- AS OF ...
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts1'
+ORDER BY product_no, start_timestamp;
+
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts2'
+ORDER BY product_no, start_timestamp;
+
+SELECT product_no, price FROM products
+FOR system_time AS OF :'ts3'
+ORDER BY product_no, start_timestamp;
+
+-- BETWEEN ... AND ...
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts2'
+ORDER BY product_no, start_timestamp;
+
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts3'
+ORDER BY product_no, start_timestamp;
+
+SELECT product_no, price FROM products
+FOR system_time BETWEEN :'ts1' AND :'ts3'
+ORDER BY product_no, start_timestamp;
+
+/*
+ * JOINS
+ */
+
+CREATE TABLE stestx (x int, y int);
+INSERT INTO stestx VALUES (11, 1), (22, 2), (33, 3);
+
+SELECT a, x, y
+FROM stestx
+INNER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+LEFT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+RIGHT OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+SELECT a, x, y
+FROM stestx
+FULL OUTER JOIN stest0 ON stestx.y = stest0.a;
+
+DROP TABLE stestx;
+
+-- views
+CREATE VIEW stest1v AS SELECT a FROM stest0;
+CREATE VIEW stest2v AS select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a;
+SELECT * FROM stest1v;
+SELECT * FROM stest2v;
+
+DROP VIEW stest1v;
+DROP VIEW stest2v;
+-- CTEs
+WITH foo AS (SELECT a FROM stest0) SELECT * FROM foo;
+
+WITH foo AS (select a from stest0 for system_time from '2000-01-01 00:00:00.00000' to 'infinity' ORDER BY a) SELECT * FROM foo;
+
+-- inheritance
+CREATE TABLE stest1 () INHERITS (stest0);
+SELECT * FROM stest1;
+
+\d stest1
+
+INSERT INTO stest1 VALUES (4);
+SELECT a FROM stest1;
+
+DROP TABLE stest0, stest1;
#71Li Japin
japinli@hotmail.com
In reply to: Surafel Temesgen (#70)
Re: WIP: System Versioned Temporal Table

On Jan 27, 2021, at 12:39 AM, Surafel Temesgen <surafel3000@gmail.com<mailto:surafel3000@gmail.com>> wrote:

On Tue, Jan 26, 2021 at 2:33 PM Vik Fearing <vik@postgresfriends.org<mailto:vik@postgresfriends.org>> wrote:
I'm still in the weeds of reviewing this patch, but why should this
fail? It should not fail.

Attached is rebased patch that include isolation test

Thanks for updating the patch. However it cannot apply to master (e5d8a9990).

Here are some comments on system-versioning-temporal-table_2021_v13.patch.

+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row verson.

verson -> version

+    By default, the column names will be StartTime and EndTime, though
+    you can specify different names if you choose.

In fact, it is start_time and end_time, not StartTime and EndTime.
I think it's better to use <literal> label around start_time and end_time.

+    column will be automatically added to the Primary Key of the
+    table.

Should we mention the unique constraints?

+    The system versioning period end column will be added to the
+    Primary Key of the table as a way of ensuring that concurrent
+    INSERTs conflict correctly.

Same as above.

Since the get_row_start_time_col_name() and get_row_end_time_col_name()
are similar, IMO we can pass a flag to get StartTime/EndTime column name,
thought?

--
Regrads,
Japin Li.
ChengDu WenWu Information Technology Co.,Ltd.

#72Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Li Japin (#71)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Thu, Feb 25, 2021 at 3:28 PM Li Japin <japinli@hotmail.com> wrote:

On Jan 27, 2021, at 12:39 AM, Surafel Temesgen <surafel3000@gmail.com>
wrote:

On Tue, Jan 26, 2021 at 2:33 PM Vik Fearing <vik@postgresfriends.org>
wrote:

I'm still in the weeds of reviewing this patch, but why should this
fail? It should not fail.

Attached is rebased patch that include isolation test

Thanks for updating the patch. However it cannot apply to master
(e5d8a9990).

Here are some comments on system-versioning-temporal-table_2021_v13.patch.

+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row verson.

verson -> version

+    By default, the column names will be StartTime and EndTime, though
+    you can specify different names if you choose.

In fact, it is start_time and end_time, not StartTime and EndTime.
I think it's better to use <literal> label around start_time and end_time.

+    column will be automatically added to the Primary Key of the
+    table.

Should we mention the unique constraints?

+    The system versioning period end column will be added to the
+    Primary Key of the table as a way of ensuring that concurrent
+    INSERTs conflict correctly.

Same as above.

Since the get_row_start_time_col_name() and get_row_end_time_col_name()
are similar, IMO we can pass a flag to get StartTime/EndTime column name,
thought?

--
Regrads,
Japin Li.
ChengDu WenWu Information Technology Co.,Ltd.

The patch (system-versioning-temporal-table_2021_v13.patch) does not apply

successfully.

http://cfbot.cputube.org/patch_32_2316.log

Hunk #1 FAILED at 80.
1 out of 1 hunk FAILED -- saving rejects to file
src/test/regress/parallel_schedule.rej
patching file src/test/regress/serial_schedule
Hunk #1 succeeded at 126 (offset -1 lines).

Therefore it is a minor change so I rebased the patch, please take a look
at that.

--
Ibrar Ahmed

Attachments:

system-versioning-temporal-table_2021_v14.patchapplication/octet-stream; name=system-versioning-temporal-table_2021_v14.patchDownload
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 1e9a4625cc..55f0d65681 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -4917,6 +4917,104 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+ <sect1 id="ddl-temporal">
+  <title>Temporal Data</title>
+
+   <indexterm>
+    <primary>temporal data</primary>
+   </indexterm>
+   <indexterm>
+    <primary>system versioning</primary>
+   </indexterm>
+   <indexterm>
+    <primary>SYSTEM_TIME</primary>
+   </indexterm>
+
+   <para>
+    <productname>PostgreSQL</productname> implements a table option
+    for system versioning that records each change permanently,
+    allowing access to the full history of data changes.
+   </para>
+
+   <para>
+    System versioning is optional and can be defined easily for a table:
+<programlisting>
+CREATE TABLE products (
+    product_no integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row verson.
+    The data type of these columns will be TIMESTAMP WITH TIME ZONE.
+    By default, the column names will be StartTime and EndTime, though
+    you can specify different names if you choose. The StartTime
+    column will be automatically added to the Primary Key of the
+    table.
+   </para>
+   <para>
+    StartTime will be generated always when a new row version is added
+    by INSERT or UPDATE, leaving EndTime set at Infinity. EndTime will
+    be generated always when a row version is UPDATEd or DELETEd.
+    This sequence of commands produces the full history shown in
+    the resulting query:
+<programlisting>
+INSERT INTO products VALUES (100, 'Washing Machine', 300.0);
+INSERT INTO products VALUES (200, 'Extended Warranty', 50.0);
+INSERT INTO products VALUES (300, 'Laptop', 250.0);
+UPDATE products SET price = 75.0 WHERE product_no = 200;
+UPDATE products SET price = 350.0 WHERE product_no = 300;
+UPDATE products SET product_no = 400 WHERE product_no = 100;
+DELETE FROM products WHERE product_no = 300;
+INSERT INTO products VALUES (500, 'Spare Parts', 25.0);
+
+SELECT * FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no, start_timestamp;
+ product_no |       name        | price |           start_timestamp           |            end_timestamp            
+------------+-------------------+-------+-------------------------------------+-------------------------------------
+        100 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.020361 2021 PST | Mon Jan 11 03:47:36.027029 2021 PST
+        200 | Extended Warranty |  50.0 | Mon Jan 11 03:47:36.022847 2021 PST | Mon Jan 11 03:47:36.02641 2021 PST
+        200 | Extended Warranty |  75.0 | Mon Jan 11 03:47:36.02641 2021 PST  | infinity
+        300 | Laptop            | 250.0 | Mon Jan 11 03:47:36.023316 2021 PST | Mon Jan 11 03:47:36.026779 2021 PST
+        300 | Laptop            | 350.0 | Mon Jan 11 03:47:36.026779 2021 PST | Mon Jan 11 03:47:36.029086 2021 PST
+        400 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.027029 2021 PST | infinity
+        500 | Spare Parts       |  25.0 | Mon Jan 11 03:47:36.032077 2021 PST | infinity
+(7 rows)
+</programlisting>
+    Note that this is
+    implemented by having an UPDATE become an INSERT of the data from
+    the old row version with EndTime set, plus the actual UPDATE,
+    increasing the number of actual visible rows in the table.
+    DELETE becomes an UPDATE of the old row version with EndTime set.
+    This means that UPDATEs and DELETEs still cause bloat that needs
+    to be removed by VACUUM.
+   </para>
+   <para>
+    Temporal queries are discussed in <xref linkend="temporal-queries"/>.
+   </para>
+   <para>
+    The system versioning period end column will be added to the
+    Primary Key of the table as a way of ensuring that concurrent
+    INSERTs conflict correctly. This alters the way that Foreign
+    Keys work for tables with system versioning.
+   </para>
+   <para>
+    The user may never set the system time column values explicitly
+    on INSERT for generated columns. Currently, UPDATE does allow
+    setting the period start and end dates to DEFAULT values.
+   </para>
+   <para>
+    SELECT * currently returns the period start and end columns, even
+    when added implicitly. 
+   </para>
+   <para>
+    The SQL Standard defines that the timestamps used should be the
+    CURRENT_TIMESTAMP value defined at statement start. This currently
+    leads to anomalies in some cases where the EndTime can be set to
+    a value earlier than the StartTime.
+   </para>
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index bc0b3cc9fe..54062375be 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -2649,4 +2649,92 @@ SELECT * FROM t;
 
  </sect1>
 
+ <sect1 id="temporal-queries">
+  <title>Temporal Queries</title>
+
+   <para>
+    <productname>PostgreSQL</productname> implements an option to
+    to add system versioning onto a user table. Such tables can
+    be accessed normally to see the current contents, but also
+    allow you to access data using both temporal and
+    historical queries.  Queries must specify a period name,
+    which for system versioning must be <quote>SYSTEM_TIME</quote>.
+   </para>
+
+   <para>
+    Historical queries show the changes to rows over time, such as the
+    following query that shows changes to a company's products and pricing:
+<programlisting>
+SELECT * FROM products FOR system_time BETWEEN '-infinity' AND 'infinity' ORDER BY product_no, start_timestamp;
+ product_no |       name        | price |           start_timestamp           |            end_timestamp            
+------------+-------------------+-------+-------------------------------------+-------------------------------------
+        100 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.020361 2021 PST | Mon Jan 11 08:47:36.027029 2021 PST
+        200 | Extended Warranty |  50.0 | Mon Jan 11 03:47:36.022847 2021 PST | Mon Jan 11 05:47:36.02641 2021 PST
+        200 | Extended Warranty |  75.0 | Mon Jan 11 05:47:36.02641 2021 PST  | infinity
+        300 | Laptop            | 250.0 | Mon Jan 11 03:47:36.023316 2021 PST | Mon Jan 11 06:47:36.026779 2021 PST
+        300 | Laptop            | 350.0 | Mon Jan 11 06:47:36.026779 2021 PST | Mon Jan 11 07:47:36.029086 2021 PST
+        400 | Washing Machine   | 300.0 | Mon Jan 11 08:47:36.027029 2021 PST | infinity
+        500 | Spare Parts       |  25.0 | Mon Jan 11 09:47:36.032077 2021 PST | infinity
+(7 rows)
+</programlisting>
+    This query shows the full history over all time since it includes
+    all changes between a timestamp of '-infinity' to 'infinity'.
+    The user can specify tighter time ranges to filter away unwanted
+    parts of the change history.
+   </para>
+
+   <para>
+    Normal non-temporal queries show the current contents of the
+    system versioned table, just like normal tables:
+<programlisting>
+SELECT product_no, name, price FROM products ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        200 | Extended Warranty |  75.0 
+        400 | Washing Machine   | 300.0 
+        500 | Spare Parts       |  25.0 
+(3 rows)
+</programlisting>
+    which is achieved by generating adding WHERE clauses to exclude
+    the historical row versions from the query. This can be seen
+    more clearly using <command>EXPLAIN</command>:
+<programlisting>
+EXPLAIN (COSTS OFF) SELECT * FROM products;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Seq Scan on products
+   Filter: (end_timestamp = 'infinity'::timestamp with time zone)
+(2 rows)
+</programlisting>
+    which shows that the presence of historical row versions will
+    affect the performance of all types of query on tables with
+    system versioning.
+   </para>
+
+   <para>
+    Temporal queries show the table rows at a specific timestamp, e.g.
+<programlisting>
+SELECT product_no, name, price FROM products FOR system_time AS OF 'Mon Jan 11 07:30:00 2021 PST' ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        100 | Washing Machine   | 300.0 
+        200 | Extended Warranty |  75.0 
+        300 | Laptop            | 350.0 
+(3 rows)
+</programlisting>
+
+    or
+
+<programlisting>
+SELECT product_no, name, price FROM products FOR system_time AS OF 'Mon Jan 11 09:30:00 2021 PST' ORDER BY product_no;
+ product_no |       name        | price 
+------------+-------------------+-------
+        200 | Extended Warranty |  75.0 
+        400 | Washing Machine   | 300.0 
+(2 rows)
+</programlisting>
+   </para>
+
+ </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c25ef5abd6..ba0512ea15 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -159,6 +161,19 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table by adding two columns which
+      record the period start and period end for the validity of each row version.
+      The column names will be StartTime and EndTime respectively, though the data
+      types are TIMESTAMP WITH TIME ZONE. See
+      <xref linkend="ddl-temporal"/>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -178,6 +193,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty then history records are also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 3b2b227683..5b45954d86 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -981,6 +1007,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      Specifies the pair of columns that hold the row start
+      timestamp and row end timestamp column names.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1235,6 +1271,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This clause specifies that the table contains multiple historical
+      row versions that may be viewed using temporal queries.
+      If period columns are not specified explicitly the default columns
+      StartTime and EndTime will be added automatically.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index ab91105599..f80a67b579 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,11 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+<replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -582,6 +587,42 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Allows temporal queries for tables defined with system versioning.
+        This specifies a single timestamp that is used in place of the
+        normal transaction snapshot to determine which row version of a
+        row is visible for this query. At most one row version will be
+        returned for any row.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        For tables with system versioning this specifies that all
+        row versions visible at any point over the time range
+        will be visible to this query.
+        Note that this form of query allows multiple row versions to be
+        returned for one row rather than just a single row. It allows
+        the query to inspect the history of UPDATEs and DELETEs
+        that have occured to row after the initial INSERT, over
+        the time range specified. To see all changes over time
+        specify a start_time of '-Infinity' and an end_time of 'Infinity'.
+       </para>
+       <para>
+        Optionally, <replaceable class="parameter">SYMMETRIC</replaceable>
+        or <replaceable class="parameter">ASYMMETRIC</replaceable> may
+        be specified for the time range.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 902f59440c..bb222b4f6c 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -167,6 +167,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->has_system_versioning = constr->has_system_versioning;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -484,6 +485,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->has_system_versioning != constr2->has_system_versioning)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->has_system_versioning = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index f05e2d2347..16fa0521b2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1015,6 +1015,11 @@ CopyFrom(CopyFromState cstate)
 					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
 											   CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->has_system_versioning)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 559fa1d2e5..8629117f52 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -57,6 +57,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -74,6 +75,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -168,6 +170,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -422,11 +427,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddConstraint(List **wqueue,
@@ -1573,6 +1579,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1581,6 +1588,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		/* open the relation, we already hold a lock on it */
 		rel = table_open(myrelid, NoLock);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->has_system_versioning)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate table with system versioning")));
+
 		/* don't throw error for "TRUNCATE foo, foo" */
 		if (list_member_oid(relids, myrelid))
 		{
@@ -3874,8 +3889,10 @@ AlterTableGetLockLevel(List *cmds)
 				 */
 			case AT_AddColumn:	/* may rewrite heap, in some cases and visible
 								 * to SELECT */
+			case AT_AddSystemVersioning:
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PeriodColumn:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -3904,6 +3921,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4472,6 +4490,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
 			/* No command-specific prep needed */
 			pass = AT_PASS_MISC;
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4528,6 +4547,35 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  castNode(AlterTableCmd, lfirst(lcmd)),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to be specified
+			 */
+			if (context)
+			{
+				if (context->hasSystemVersioning)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4627,16 +4675,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -4860,6 +4908,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
 			break;
+
 		default:				/* oops */
 			elog(ERROR, "unrecognized alter table type: %d",
 				 (int) cmd->subtype);
@@ -4916,7 +4965,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5292,6 +5341,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5329,6 +5379,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5396,6 +5447,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg_internal("rewriting table \"%s\"",
@@ -5484,6 +5541,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5554,6 +5618,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -5589,6 +5657,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 											RelationGetRelationName(oldrel)),
 									 errtableconstraint(oldrel, con->name)));
 						break;
+
 					case CONSTR_FOREIGN:
 						/* Nothing to do here */
 						break;
@@ -6405,6 +6474,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -6724,6 +6801,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7167,6 +7251,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7267,6 +7358,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -7674,6 +7772,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -7879,11 +7984,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -7926,6 +8032,32 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+#ifdef NOTUSED
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+#endif
+
+	/*
+	 * Reviewers note: We should be disallowing DROP COLUMN on a
+	 * system time column, but DROP SYSTEM VERSIONING is currently
+	 * kluged to generate multiple DropColumn subcommands, which
+	 * means we need to allow this for now, even though an explicit
+	 * DROP COLUMN will crash the server. Needs work.
+	 */
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -7986,11 +8118,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -8011,9 +8147,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -11474,6 +11610,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12218,10 +12360,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -12555,6 +12700,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -15933,7 +16085,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642dba6c..93c8c2dec2 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -428,6 +429,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 2993ba43e3..2ddee7884f 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -367,6 +367,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 	MemoryContextSwitchTo(oldContext);
 }
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+void
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -520,6 +640,13 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		WCOKind		wco_kind;
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Constraints might reference the tableoid column, so (re-)initialize
 		 * tts_tableOid before evaluating them.
@@ -917,6 +1044,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple while attempting delete with system versioning");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1411,6 +1562,40 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			/*
+			 * Insert a new row to represent the old row with EndTime set
+			 * Note that this creates a new row version rather than updating in place
+			 * the existing row version.
+			 */
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple while attempting update with system versioning");
+			}
+			else
+			{
+				ExecSetRowEndTime(estate, mslot, resultRelInfo);
+				table_tuple_insert(resultRelationDesc, mslot,
+								   estate->es_output_cid,
+								   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+
+			/*
+			 * Set the StartTime for the soon to be newly updated row
+			 */
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index aaba1ec2c4..45313303ce 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3463,6 +3463,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioning);
 }
 
 static CreateStmt *
@@ -4876,6 +4877,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5779,6 +5804,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index c2d73626fc..408eb1ecc6 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1259,6 +1259,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioning);
 
 	return true;
 }
@@ -2976,6 +2977,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3804,6 +3826,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..cb9e7eef01 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,126 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexpr, BoolExpr))
+	{
+		BoolExpr *blexpr = (BoolExpr *) lexpr;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, SYSTEM_VERSIONING_DEFAULT_START_NAME) == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = SYSTEM_VERSIONING_DEFAULT_START_NAME;
+		n->constraints = list_make1((Node *) c);
+	}
+	else if (strcmp(name, SYSTEM_VERSIONING_DEFAULT_END_NAME) == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = SYSTEM_VERSIONING_DEFAULT_END_NAME;
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+		elog(ERROR, "unexpected temporal column name");
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8fc432bfe1..6e53d5a4f4 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2652,6 +2652,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioning);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 545b56bcaf..40daee9b6a 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index f3e46e0959..1c6cc008cb 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5947fa418..45ed77586c 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -34,12 +34,14 @@
 #include "foreign/fdwapi.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
+#include "parser/parse_clause.h"
 #include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +82,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioning_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioning_table(RangeTblEntry *rte);
 
 
 /*
@@ -2361,3 +2366,193 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if this is a system versioned relation and the where clause did not
+ * already contain a filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioning_table(rte) ||
+			check_system_versioning_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filters history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioning_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioning_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioning_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioning_column_walker(node, rte);
+}
+
+static bool
+check_system_versioning_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->has_system_versioning;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0f3a70c49a..6077701c84 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -47,6 +47,7 @@
 #include "rewrite/rewriteManip.h"
 #include "utils/builtins.h"
 #include "utils/rel.h"
+#include "optimizer/plancat.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -455,6 +456,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1230,6 +1236,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1327,6 +1342,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2286,6 +2306,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 652be0b96d..d5bf579749 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -134,6 +134,20 @@ typedef struct SelectLimit
 	LimitOption limitOption;
 } SelectLimit;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioning;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -152,7 +166,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -177,7 +190,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -250,6 +262,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	PartitionBoundSpec	*partboundspec;
 	RoleSpec			*rolespec;
 	struct SelectLimit	*selectlimit;
+	TemporalClause *temporalClause;
+	struct GenerateType	*GenerateType;
+	struct OptionWith	*OptionWith;
 }
 
 %type <node>	stmt schema_stmt
@@ -384,12 +399,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <importqual> import_qualification
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
 
 %type <list>	parse_toplevel stmtmulti
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith opt_definition func_args func_args_list
+				opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list alterfunc_opt_list
@@ -508,7 +525,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -546,7 +563,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <list>	ColQualList
 %type <node>	ColConstraint ColConstraintElem ConstraintAttr
@@ -677,7 +694,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -692,7 +709,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -703,7 +720,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -724,7 +741,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 /*
  * The grammar likewise thinks these tokens are keywords, but they are never
@@ -751,6 +768,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -790,6 +810,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2141,6 +2166,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PeriodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2168,7 +2201,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2307,7 +2348,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2316,8 +2369,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2326,6 +2391,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3233,12 +3307,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioning = ($11)->systemVersioning;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3252,12 +3327,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioning = ($14)->systemVersioning;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3271,13 +3347,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioning = ($10)->systemVersioning;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3291,13 +3368,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioning = ($13)->systemVersioning;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3311,13 +3389,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioning = ($12)->systemVersioning;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3331,13 +3410,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioning = ($15)->systemVersioning;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3414,6 +3494,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3510,6 +3591,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3592,12 +3683,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3615,6 +3706,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3637,6 +3729,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -4013,9 +4129,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioning = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4151,7 +4292,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -11829,7 +11970,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -11908,12 +12049,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -11921,6 +12066,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -12019,7 +12177,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
+
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
 
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15440,6 +15645,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15516,6 +15722,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15546,6 +15753,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16013,6 +16221,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -16103,6 +16312,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -16148,6 +16358,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16264,16 +16475,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16679,23 +16880,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexpr, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index bdf8ec46e2..52b45528c7 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -97,6 +98,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1139,6 +1141,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->has_system_versioning = (tupdesc->constr && tupdesc->constr->has_system_versioning);
+		table_close(rel, NoLock);
+
+		if (!rte->has_system_versioning)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("temporal clause can only specified for a table with system versioning")));
+
+		changeTemporalToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3686,3 +3717,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTemporalToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 75266caeb4..b00214235d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -61,6 +61,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -72,6 +73,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -96,6 +99,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		hasSystemVersioning;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -252,6 +262,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.hasSystemVersioning = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -288,6 +302,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
 
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -295,6 +313,27 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time columns and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is prepended to the table
+	 * definition. This is an extension to the SQL Standard.
+	 */
+	if (!cxt.hasSystemVersioning && stmt->systemVersioning)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_START_NAME);
+		endCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_END_NAME);
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(list_make2(startCol, endCol), stmt->tableElts);
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -306,6 +345,36 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	if (cxt.hasSystemVersioning)
+	{
+		ListCell   *lc;
+
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+
+		/*
+		 * End time column is added to primary and unique key constraint
+		 * implicitly to make history and current data co-exist.
+		 */
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -746,6 +815,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row start time must be timestamptz")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->hasSystemVersioning = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->hasSystemVersioning = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1433,6 +1558,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time must reference the row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time must reference the row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3173,7 +3327,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3200,7 +3354,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3237,6 +3391,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.hasSystemVersioning = false;
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3271,6 +3428,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PeriodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3280,6 +3445,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->has_system_versioning) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3447,6 +3629,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.hasSystemVersioning)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->hasSystemVersioning = cxt.hasSystemVersioning;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->hasSystemVersioning = cxt.hasSystemVersioning;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3478,7 +3675,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 875de7ba28..e000a4040a 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -137,6 +137,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -188,6 +191,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			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 0672f497c6..9ad9e9f2b9 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -932,14 +932,24 @@ rewriteTargetListIU(List *targetList,
 								NameStr(att_tup->attname)),
 						 errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.",
 								   NameStr(att_tup->attname))));
-
-			if (att_tup->attgenerated && new_tle && !apply_default)
+#ifdef NOTUSED
+			if (att_tup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+				att_tup->attgenerated == ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
-						(errcode(ERRCODE_GENERATED_ALWAYS),
-						 errmsg("column \"%s\" can only be updated to DEFAULT",
-								NameStr(att_tup->attname)),
-						 errdetail("Column \"%s\" is a generated column.",
-								   NameStr(att_tup->attname))));
+					(errcode(ERRCODE_GENERATED_ALWAYS),
+					 errmsg("column \"%s\" cannot be updated",
+							NameStr(att_tup->attname)),
+					 errdetail("Column \"%s\" is a system versioning column.",
+							   NameStr(att_tup->attname))));
+			else if (att_tup->attgenerated && new_tle && !apply_default)
+#endif
+			if (att_tup->attgenerated && new_tle && !apply_default)
+				 ereport(ERROR,
+					(errcode(ERRCODE_GENERATED_ALWAYS),
+					 errmsg("column \"%s\" can only be updated to DEFAULT",
+							NameStr(att_tup->attname)),
+					 errdetail("Column \"%s\" is a generated column.",
+							   NameStr(att_tup->attname))));
 		}
 
 		if (att_tup->attgenerated)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 05bb698cf4..ea60639b5b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -59,7 +60,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1236,6 +1239,10 @@ ProcessUtilitySlow(ParseState *pstate,
 					AlterTableStmt *atstmt = (AlterTableStmt *) parsetree;
 					Oid			relid;
 					LOCKMODE	lockmode;
+					ListCell   *s;
+					Relation	rel;
+					bool		hasSystemVersioning = false;
+					TupleDesc	tupdesc;
 
 					/*
 					 * Figure out lock mode, and acquire lock.  This also does
@@ -1246,6 +1253,83 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							hasSystemVersioning = tupdesc->constr && tupdesc->constr->has_system_versioning;
+							if (hasSystemVersioning)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already defined with system versioning")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning to table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statement
+							 */
+							startTimeCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_START_NAME);
+							endTimeCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_END_NAME);
+
+							/*
+							 * create alter table add column cmd and append to the end
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							hasSystemVersioning = tupdesc->constr && tupdesc->constr->has_system_versioning;
+							if (!hasSystemVersioning)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not defined with system versioning")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1256,6 +1340,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.hasSystemVersioning = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ef510cd01..8312a36261 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 												sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->has_system_versioning = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+			attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+			constr->has_system_versioning = true;
 
 		/* If the column has a default, fill it into the attrdef array */
 		if (attp->atthasdef)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index eb988d7eb4..5f43790d42 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15896,6 +15896,10 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 20af5a92b4..c89cf2f4bc 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2061,6 +2061,10 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated always as identity";
 			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 				default_str = "generated by default as identity";
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 			{
 				default_str = psprintf("generated always as (%s) stored",
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f45d47aab7..3a7ffd4464 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,7 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		has_system_versioning;
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 3db42abf08..e6175749dc 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -208,6 +208,9 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_attribute_relid_attnum_index, 2659, on pg_attribute
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 83e2965531..be9790a6be 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern void ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..9af47ba697 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,16 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+
+#define SYSTEM_VERSIONING_DEFAULT_START_NAME "start_timestamp"
+#define SYSTEM_VERSIONING_DEFAULT_END_NAME "end_timestamp"
+
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
+
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index e22df890ef..773c2325eb 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -485,6 +485,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 236832a2ca..890c6cbac9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1009,6 +1009,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		has_system_versioning;	/* relation has system versioning */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1818,7 +1819,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1899,7 +1900,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
+	AT_AddSystemVersioning,		/* ADD system versioning */
+	AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */
+	AT_DropSystemVersioning,	/* DROP system versioning */
+	AT_PeriodColumn				/* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2125,6 +2129,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioning;	/* true with system versioning */
 } CreateStmt;
 
 /* ----------
@@ -2174,7 +2179,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3602,4 +3609,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 8d1d6c1b42..dd2d6bce2e 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28083aaac9..db02eeaab3 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -308,6 +308,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -401,6 +402,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -449,6 +451,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 176b9f37c1..2f84c286ba 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -200,7 +200,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index bfa4a6b0f2..3a6251f800 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 841062b4b3..4184ea14f4 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool hasSystemVersioning;	/* true if table has system versioning */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 5d6b79e66e..11a6c7b32e 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -32,6 +32,7 @@ test: fk-partitioned-2
 test: eval-plan-qual
 test: eval-plan-qual-trigger
 test: lock-update-delete
+test: lock-update-delete-system-versioned
 test: lock-update-traversal
 test: inherit-temp
 test: insert-conflict-do-nothing
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index c77b0d7342..fc12531808 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -80,7 +80,7 @@ test: brin gin gist spgist privileges init_privs security_label collate matview
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort system_versioning
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 0264a97324..6eedc57f6e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -126,6 +126,7 @@ test: drop_operator
 test: password
 test: identity
 test: generated
+test: system_versioning
 test: join_hash
 test: create_table_like
 test: alter_generic
#73Surafel Temesgen
surafel3000@gmail.com
In reply to: Ibrar Ahmed (#72)
Re: WIP: System Versioned Temporal Table

hi Ibrar,
thank you for rebasing

On Mon, Mar 8, 2021 at 9:34 AM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

Since the get_row_start_time_col_name() and get_row_end_time_col_name()
are similar, IMO we can pass a flag to get StartTime/EndTime column name,
thought?

For me your option is better. i will change to it in my next
patch if no objection

regards
Surafel

#74Vik Fearing
vik@postgresfriends.org
In reply to: Surafel Temesgen (#73)
Re: WIP: System Versioned Temporal Table

On 3/10/21 5:49 PM, Surafel Temesgen wrote:

hi Ibrar,
thank you for rebasing

On Mon, Mar 8, 2021 at 9:34 AM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

Since the get_row_start_time_col_name() and get_row_end_time_col_name()
are similar, IMO we can pass a flag to get StartTime/EndTime column name,
thought?

For me your option is better. i will change to it in my next
patch if no objection

I have plenty of objection. I'm sorry that I am taking so long with my
review. I am still working on it and it is coming soon, I promise.
--
Vik Fearing

#75Surafel Temesgen
surafel3000@gmail.com
In reply to: Vik Fearing (#74)
Re: WIP: System Versioned Temporal Table

On Wed, Mar 10, 2021 at 9:02 AM Vik Fearing <vik@postgresfriends.org> wrote:

I have plenty of objection. I'm sorry that I am taking so long with my
review. I am still working on it and it is coming soon, I promise.

okay take your time

regards
Surafel

#76vignesh C
vignesh21@gmail.com
In reply to: Ibrar Ahmed (#72)
Re: WIP: System Versioned Temporal Table

On Mon, Mar 8, 2021 at 11:04 PM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

On Thu, Feb 25, 2021 at 3:28 PM Li Japin <japinli@hotmail.com> wrote:

On Jan 27, 2021, at 12:39 AM, Surafel Temesgen <surafel3000@gmail.com> wrote:

On Tue, Jan 26, 2021 at 2:33 PM Vik Fearing <vik@postgresfriends.org> wrote:

I'm still in the weeds of reviewing this patch, but why should this
fail? It should not fail.

Attached is rebased patch that include isolation test

Thanks for updating the patch. However it cannot apply to master (e5d8a9990).

Here are some comments on system-versioning-temporal-table_2021_v13.patch.

+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row verson.

verson -> version

+    By default, the column names will be StartTime and EndTime, though
+    you can specify different names if you choose.

In fact, it is start_time and end_time, not StartTime and EndTime.
I think it's better to use <literal> label around start_time and end_time.

+    column will be automatically added to the Primary Key of the
+    table.

Should we mention the unique constraints?

+    The system versioning period end column will be added to the
+    Primary Key of the table as a way of ensuring that concurrent
+    INSERTs conflict correctly.

Same as above.

Since the get_row_start_time_col_name() and get_row_end_time_col_name()
are similar, IMO we can pass a flag to get StartTime/EndTime column name,
thought?

--
Regrads,
Japin Li.
ChengDu WenWu Information Technology Co.,Ltd.

The patch (system-versioning-temporal-table_2021_v13.patch) does not apply successfully.

http://cfbot.cputube.org/patch_32_2316.log

Hunk #1 FAILED at 80.
1 out of 1 hunk FAILED -- saving rejects to file src/test/regress/parallel_schedule.rej
patching file src/test/regress/serial_schedule
Hunk #1 succeeded at 126 (offset -1 lines).

Therefore it is a minor change so I rebased the patch, please take a look at that.

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Regards,
Vignesh

#77Simon Riggs
simon.riggs@enterprisedb.com
In reply to: vignesh C (#76)
1 attachment(s)
Re: WIP: System Versioned Temporal Table

On Wed, 14 Jul 2021 at 12:48, vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

OK, so I've rebased the patch against current master to take it to v15.

I've then worked on the patch some myself to make v16 (attached),
adding these things:

* Add code, docs and test to remove the potential anomaly where
endtime < starttime, using the sqlstate 2201H as pointed out by Vik
* Add code and tests to handle multiple changes in a transaction
correctly, according to SQL Std
* Add code and tests to make Foreign Keys behave correctly, according to SQL Std
* Fixed nascent bug in relcache setup code
* Various small fixes from Japin's review - thanks! I've used
starttime and endtime as default column names
* Additional tests and docs to show that the functionality works with
or without PKs on the table

I am now satisfied that the patch does not have any implementation
anomalies in behavioral design, but it is still a long way short in
code architecture.

There are various aspects still needing work. This is not yet ready
for Commit, but it is appropriate now to ask for initial design
guidance on architecture and code placement by a Committer, so I am
setting this to Ready For Committer, in the hope that we get the
review in SeptCF and a later version can be submitted for later commit
in JanCF. With the right input, this patch is about a person-month
away from being ready, assuming we don't hit any blocking issues.

Major Known Issues
* SQLStd says that it should not be possible to update historical
rows, but those tests show we fail to prevent that and there is code
marked NOT_USED in those areas
* The code is structured poorly around
parse-analyze/rewriter/optimizer/executor and that needs positive
design recommendations, rather than critical review
* Joins currently fail because of the botched way WHERE clauses are
added, resulting in duplicate names
* Views probably don't work, but there are no tests
* CREATE TABLE (LIKE foo) doesn't correctly copy across all features -
test for that added, with test failure accepted for now
* ALTER TABLE is still incomplete and also broken; I suggest we remove
that for the first version of the patch to reduce patch size for an
initial commit.

Minor Known Issues
* Logical replication needs some minor work, no tests yet
* pg_dump support looks like it exists and might work easily, but
there are no tests yet
* Correlation names don't work in FROM clause - shift/reduce errors
from double use of AS
* Add test and code to prevent triggers referencing period cols in the
WHEN clause
* No tests yet to prove you can't set various parameters/settings on
the period time start/end cols
* Code needs some cleanup in a few places
* Not really sure what value is added by
lock-update-delete-system-versioned.spec

* IMHO we should make the PK definition use "endtime DESC", so that
the current version is always the first row found in the PK for any
key, since historical indexes will grow bigger over time

There are no expected issues with integration with MERGE, since SQLStd
explains how to handle that.

Other reviews are welcome.

--
Simon Riggs http://www.EnterpriseDB.com/

Attachments:

system-versioning-temporal-table_v16.patchapplication/octet-stream; name=system-versioning-temporal-table_v16.patchDownload
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index e0ffb020bf..9f557a92ef 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -4944,6 +4944,111 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
    </para>
  </sect1>
 
+ <sect1 id="ddl-temporal">
+  <title>Temporal Data</title>
+
+   <indexterm>
+    <primary>temporal data</primary>
+   </indexterm>
+   <indexterm>
+    <primary>system versioning</primary>
+   </indexterm>
+   <indexterm>
+    <primary>SYSTEM_TIME</primary>
+   </indexterm>
+
+   <para>
+    <productname>PostgreSQL</productname> implements a table option
+    for system versioning that records each change permanently,
+    allowing access to the full history of data changes.
+   </para>
+
+   <para>
+    System versioning is optional and can be defined easily for a table:
+<programlisting>
+CREATE TABLE products (
+    pno integer,
+    name text,
+    price numeric
+) WITH SYSTEM VERSIONING;
+</programlisting>
+    When system versioning is specified two columns are added which
+    record the start timestamp and end timestamp of each row version.
+    The data type of these columns will be TIMESTAMP WITH TIME ZONE.
+    By default, the column names will be <literal>starttime</literal>
+    and <literal>endtime</literal>, though you can specify different
+    names if you choose. The <literal>starttime</literal> column
+    will be automatically added to the Primary Key of the table.
+   </para>
+   <para>
+    <literal>starttime</literal> is generated always when a new row
+    version is added by INSERT or UPDATE, with <literal>endtime</literal>
+    set at <literal>Infinity</literal>.
+    System versioning implements an UPDATE and DELETE as normal,
+    plus an additional row inserted to record the now-old version
+    of the row, with the <literal>endtime</literal> set, but only
+    for the first change in any transaction. Multiple updates or
+    insert-deletes don't record history.
+    This sequence of commands produces the full history shown in
+    the resulting query:
+<programlisting>
+INSERT INTO products VALUES (100, 'Wash Machine', 300.0);
+INSERT INTO products VALUES (200, 'Ext Warranty', 50.0);
+INSERT INTO products VALUES (300, 'Laptop', 250.0);
+UPDATE products SET price = 75.0 WHERE pno = 200;
+UPDATE products SET price = 350.0 WHERE pno = 300;
+UPDATE products SET pno = 400 WHERE pno = 100;
+DELETE FROM products WHERE pno = 300;
+INSERT INTO products VALUES (500, 'Spare Parts', 25.0);
+
+SELECT * FROM products FOR system_time FROM '-infinity' TO 'infinity' ORDER BY product_no, start_timestamp;
+ pno |     name     | price |             starttime               |              endtime
+------------+-------------------+-------+-------------------------------------+-------------------------
+ 100 | Wash Machine | 300.0 | Mon Jan 11 03:47:36.020361 2021 PST | Mon Jan 11 03:47:36.027029 2021 PST
+ 200 | Ext Warranty |  50.0 | Mon Jan 11 03:47:36.022847 2021 PST | Mon Jan 11 03:47:36.026411 2021 PST
+ 200 | Ext Warranty |  75.0 | Mon Jan 11 03:47:36.026411 2021 PST | infinity
+ 300 | Laptop       | 250.0 | Mon Jan 11 03:47:36.023316 2021 PST | Mon Jan 11 03:47:36.026779 2021 PST
+ 300 | Laptop       | 350.0 | Mon Jan 11 03:47:36.026779 2021 PST | Mon Jan 11 03:47:36.029086 2021 PST
+ 400 | Wash Machine | 300.0 | Mon Jan 11 03:47:36.027029 2021 PST | infinity
+ 500 | Spare Parts  |  25.0 | Mon Jan 11 03:47:36.032077 2021 PST | infinity
+(7 rows)
+</programlisting>
+    With system versioning, UPDATE and DELETE increase the number of
+    visible rows in the table. System versioning works alongside MVCC,
+    so in addition, UPDATEs, DELETEs and aborted transactions will still
+    cause bloat that needs to be removed by VACUUM.
+   </para>
+   <para>
+    Temporal queries are discussed in <xref linkend="temporal-queries"/>.
+   </para>
+   <para>
+    A Primary Key constraint is not required, but if one exists then
+    the system versioning period end column will be added to the
+    Primary Key of the table to ensure UPDATEs do not cause uniqueness
+    violations.  Foreign Keys referencing a system versioned table must
+    match against a current row, hence FKs must match against all columns
+    of the PK except the period end columns. FK lookups will filter out
+    historical rows.
+   </para>
+   <para>
+    The user may never set the system time column values explicitly
+    on INSERT for generated columns.
+   </para>
+   <para>
+    SELECT * currently returns the period start and end columns, even
+    when added implicitly. 
+   </para>
+   <para>
+    The SQL Standard defines that the timestamps used should be the
+    CURRENT_TIMESTAMP value defined at statement start. This will
+    cause an ERROR with SQLState 2201H, rather than allow a period end
+    to be set to a value earlier than the period start time. This
+    situation could arise when an UPDATE attempts to update a tuple
+    modified by a transaction that started earlier than the updating
+    transaction.
+   </para>
+ </sect1>
+
  <sect1 id="ddl-others">
   <title>Other Database Objects</title>
 
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index 834b83b509..dcb5141f0b 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -2703,4 +2703,92 @@ SELECT * FROM t;
 
  </sect1>
 
+ <sect1 id="temporal-queries">
+  <title>Temporal Queries</title>
+
+   <para>
+    <productname>PostgreSQL</productname> implements an option to
+    to add system versioning onto a user table. Such tables can
+    be accessed normally to see the current contents, but also
+    allow you to access data using both temporal and
+    historical queries.  Queries must specify a period name,
+    which for system versioning must be <quote>SYSTEM_TIME</quote>.
+   </para>
+
+   <para>
+    Historical queries show the changes to rows over time, such as the
+    following query that shows changes to a company's products and pricing:
+<programlisting>
+SELECT * FROM products FOR system_time BETWEEN '-infinity' AND 'infinity' ORDER BY pno, starttime;
+ pno |       name        | price |              starttime              |            endtime
+-----+-------------------+-------+-------------------------------------+-------------------------------------
+ 100 | Washing Machine   | 300.0 | Mon Jan 11 03:47:36.020361 2021 PST | Mon Jan 11 08:47:36.027029 2021 PST
+ 200 | Extended Warranty |  50.0 | Mon Jan 11 03:47:36.022847 2021 PST | Mon Jan 11 05:47:36.02641 2021 PST
+ 200 | Extended Warranty |  75.0 | Mon Jan 11 05:47:36.02641 2021 PST  | infinity
+ 300 | Laptop            | 250.0 | Mon Jan 11 03:47:36.023316 2021 PST | Mon Jan 11 06:47:36.026779 2021 PST
+ 300 | Laptop            | 350.0 | Mon Jan 11 06:47:36.026779 2021 PST | Mon Jan 11 07:47:36.029086 2021 PST
+ 400 | Washing Machine   | 300.0 | Mon Jan 11 08:47:36.027029 2021 PST | infinity
+ 500 | Spare Parts       |  25.0 | Mon Jan 11 09:47:36.032077 2021 PST | infinity
+(7 rows)
+</programlisting>
+    This query shows the full history over all time since it includes
+    all changes between a timestamp of '-infinity' to 'infinity'.
+    The user can specify tighter time ranges to filter away unwanted
+    parts of the change history.
+   </para>
+
+   <para>
+    Normal non-temporal queries show the current contents of the
+    system versioned table, just like normal tables:
+<programlisting>
+SELECT pno, name, price FROM products ORDER BY pno;
+ pno |     name     | price 
+-----+--------------+-------
+ 200 | Ext Warranty |  75.0 
+ 400 | Wash Machine | 300.0 
+ 500 | Spare Parts  |  25.0 
+(3 rows)
+</programlisting>
+    which is achieved by generating extra WHERE clauses to exclude
+    the historical row versions from the query. This can be seen
+    more clearly using <command>EXPLAIN</command>:
+<programlisting>
+EXPLAIN (COSTS OFF) SELECT * FROM products;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Seq Scan on products
+   Filter: (endtime = 'infinity'::timestamp with time zone)
+(2 rows)
+</programlisting>
+    which shows that the presence of historical row versions will
+    affect the performance of all types of query on tables with
+    system versioning.
+   </para>
+
+   <para>
+    Temporal queries show the table rows at a specific timestamp, e.g.
+<programlisting>
+SELECT pno, name, price FROM products FOR system_time AS OF 'Mon Jan 11 07:30:00 2021 PST' ORDER BY pno;
+ pno |     name     | price 
+-----+--------------+-------
+ 100 | Wash Machine | 300.0 
+ 200 | Ext Warranty |  75.0 
+ 300 | Laptop       | 350.0 
+(3 rows)
+</programlisting>
+
+    or
+
+<programlisting>
+SELECT pno, name, price FROM products FOR system_time AS OF 'Mon Jan 11 09:30:00 2021 PST' ORDER BY pno;
+ pno |     name     | price 
+-----+--------------+-------
+ 200 | Ext Warranty |  75.0 
+ 400 | Wash Machine | 300.0 
+(2 rows)
+</programlisting>
+   </para>
+
+ </sect1>
+
 </chapter>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 81291577f8..57811e88c7 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -41,7 +41,9 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
 <phrase>where <replaceable class="parameter">action</replaceable> is one of:</phrase>
 
     ADD [ COLUMN ] [ IF NOT EXISTS ] <replaceable class="parameter">column_name</replaceable> <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">column_constraint</replaceable> [ ... ] ]
+    ADD SYSTEM VERSIONING
     DROP [ COLUMN ] [ IF EXISTS ] <replaceable class="parameter">column_name</replaceable> [ RESTRICT | CASCADE ]
+    DROP SYSTEM VERSIONING
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> [ SET DATA ] TYPE <replaceable class="parameter">data_type</replaceable> [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ USING <replaceable class="parameter">expression</replaceable> ]
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET DEFAULT <replaceable class="parameter">expression</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> DROP DEFAULT
@@ -161,6 +163,19 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ADD SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form enables system versioning to the table by adding two columns which
+      record the period start and period end for the validity of each row version.
+      The column names will be starttime and endtime respectively, though the data
+      types are TIMESTAMP WITH TIME ZONE. See
+      <xref linkend="ddl-temporal"/>
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DROP COLUMN [ IF EXISTS ]</literal></term>
     <listitem>
@@ -180,6 +195,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>DROP SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This form drops system versioning from the table.
+      Indexes and table constraints involving system versioning columns will be
+      automatically dropped along with system versioning columns. If the table is
+      not empty then history records are also removed.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET DATA TYPE</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 15aed2f251..9f4f0519b8 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -31,6 +31,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ PARTITION BY { RANGE | LIST | HASH } ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [, ... ] ) ]
 [ USING <replaceable class="parameter">method</replaceable> ]
 [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) | WITHOUT OIDS ]
+[ WITH SYSTEM VERSIONING ]
 [ ON COMMIT { PRESERVE ROWS | DELETE ROWS | DROP } ]
 [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
 
@@ -67,8 +68,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
   DEFAULT <replaceable>default_expr</replaceable> |
   GENERATED ALWAYS AS ( <replaceable>generation_expr</replaceable> ) STORED |
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
+  GENERATED ALWAYS AS ROW START |
+  GENERATED ALWAYS AS ROW END |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
+  PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> ) |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -912,6 +916,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW START</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row insertion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
+    <term><literal>GENERATED ALWAYS AS ROW END</literal><indexterm><primary>generated column</primary></indexterm></term>
+    <listitem>
+     <para>
+      This clause creates the column as a generated column.
+      The column cannot be written to, and when read the
+      row deletion time will be returned.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>UNIQUE</literal> (column constraint)</term>
     <term><literal>UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
@@ -1018,6 +1044,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>PERIOD FOR SYSTEM_TIME ( <replaceable class="parameter">row_start_time_column</replaceable>, <replaceable class="parameter">row_end_time_column</replaceable> )</literal></term>
+    <listitem>
+     <para>
+      Specifies the pair of columns that hold the row start
+      timestamp and row end timestamp column names.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="sql-createtable-exclude">
     <term><literal>EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ]</literal></term>
     <listitem>
@@ -1272,6 +1308,18 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>WITH SYSTEM VERSIONING</literal></term>
+    <listitem>
+     <para>
+      This clause specifies that the table contains multiple historical
+      row versions that may be viewed using temporal queries.
+      If period columns are not specified explicitly the default columns
+      StartTime and EndTime will be added automatically.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>ON COMMIT</literal></term>
     <listitem>
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index fa676b1698..326ab99588 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -60,6 +60,15 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ LATERAL ] ROWS FROM( <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] ) ] [, ...] )
                 [ WITH ORDINALITY ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) [ AS <replaceable class="parameter">join_using_alias</replaceable> ] ]
+    <replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable>
+    <replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+    <replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN ASYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+    <replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME BETWEEN SYMMETRIC <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable>
+    <replaceable class="parameter">table_name</replaceable> FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable>
+  
+  <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
+  
+
 
 <phrase>and <replaceable class="parameter">grouping_element</replaceable> can be one of:</phrase>
 
@@ -582,6 +591,42 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME AS OF <replaceable class="parameter">expression</replaceable></literal></term>
+      <listitem>
+       <para>
+        Allows temporal queries for tables defined with system versioning.
+        This specifies a single timestamp that is used in place of the
+        normal transaction snapshot to determine which row version of a
+        row is visible for this query. At most one row version will be
+        returned for any row.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><literal>FOR SYSTEM_TIME BETWEEN <replaceable class="parameter">start_time</replaceable> AND <replaceable class="parameter">end_time</replaceable></literal></term>
+      <term><literal>FOR SYSTEM_TIME FROM <replaceable class="parameter">start_time</replaceable> TO <replaceable class="parameter">end_time</replaceable></literal></term>
+      <listitem>
+       <para>
+        For tables with system versioning this specifies that all
+        row versions visible at any point over the time range
+        will be visible to this query.
+        Note that this form of query allows multiple row versions to be
+        returned for one row rather than just a single row. It allows
+        the query to inspect the history of UPDATEs and DELETEs
+        that have occured to row after the initial INSERT, over
+        the time range specified. To see all changes over time
+        specify a start_time of '-Infinity' and an end_time of 'Infinity'.
+       </para>
+       <para>
+        Optionally, <replaceable class="parameter">SYMMETRIC</replaceable>
+        or <replaceable class="parameter">ASYMMETRIC</replaceable> may
+        be specified for the time range.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">join_type</replaceable></term>
       <listitem>
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index 4c63bd4dc6..8e895876af 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -168,6 +168,7 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 
 		cpy->has_not_null = constr->has_not_null;
 		cpy->has_generated_stored = constr->has_generated_stored;
+		cpy->has_system_versioning = constr->has_system_versioning;
 
 		if ((cpy->num_defval = constr->num_defval) > 0)
 		{
@@ -477,6 +478,8 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_generated_stored != constr2->has_generated_stored)
 			return false;
+		if (constr1->has_system_versioning != constr2->has_system_versioning)
+			return false;
 		n = constr1->num_defval;
 		if (n != (int) constr2->num_defval)
 			return false;
@@ -847,6 +850,7 @@ BuildDescForRelation(List *schema)
 
 		constr->has_not_null = true;
 		constr->has_generated_stored = false;
+		constr->has_system_versioning = false;
 		constr->defval = NULL;
 		constr->missing = NULL;
 		constr->num_defval = 0;
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 40a54ad0bd..9fbc1f8330 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -1026,6 +1026,11 @@ CopyFrom(CopyFromState cstate)
 					ExecComputeStoredGenerated(resultRelInfo, estate, myslot,
 											   CMD_INSERT);
 
+				/* Set system time columns */
+				if (resultRelInfo->ri_RelationDesc->rd_att->constr &&
+					resultRelInfo->ri_RelationDesc->rd_att->constr->has_system_versioning)
+					ExecSetRowStartTime(estate, myslot, resultRelInfo);
+
 				/*
 				 * If the target is a plain table, check the constraints of
 				 * the tuple.
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index b18de38e73..c449d9f0a5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -59,6 +59,7 @@
 #include "commands/typecmds.h"
 #include "commands/user.h"
 #include "executor/executor.h"
+#include "executor/nodeModifyTable.h"
 #include "foreign/fdwapi.h"
 #include "foreign/foreign.h"
 #include "miscadmin.h"
@@ -77,6 +78,7 @@
 #include "parser/parser.h"
 #include "partitioning/partbounds.h"
 #include "partitioning/partdesc.h"
+#include "optimizer/plancat.h"
 #include "pgstat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
@@ -181,6 +183,9 @@ typedef struct AlteredTableInfo
 	bool		chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
 	char		newrelpersistence;	/* if above is true */
 	Expr	   *partition_constraint;	/* for attach partition validation */
+	bool		systemVersioningAdded;	/* is system time column added? */
+	bool		systemVersioningRemoved;	/* is system time column removed? */
+	AttrNumber	attnum;			/* which column is system end time column */
 	/* true, if validating default due to some other attach/detach */
 	bool		validate_default;
 	/* Objects to rebuild after completing ALTER TYPE operations */
@@ -454,11 +459,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
 static void ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
 							 AlterTableCmd *cmd, LOCKMODE lockmode,
 							 AlterTableUtilityContext *context);
-static ObjectAddress ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+static ObjectAddress ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 									  DropBehavior behavior,
 									  bool recurse, bool recursing,
 									  bool missing_ok, LOCKMODE lockmode,
-									  ObjectAddresses *addrs);
+									  ObjectAddresses *addrs,
+									  AlterTableUtilityContext *context);
 static ObjectAddress ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
 									IndexStmt *stmt, bool is_rebuild, LOCKMODE lockmode);
 static ObjectAddress ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
@@ -1626,6 +1632,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 		bool		recurse = rv->inh;
 		Oid			myrelid;
 		LOCKMODE	lockmode = AccessExclusiveLock;
+		TupleDesc	tupdesc;
 
 		myrelid = RangeVarGetRelidExtended(rv, lockmode,
 										   0, RangeVarCallbackForTruncate,
@@ -1644,6 +1651,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 		 */
 		truncate_check_activity(rel);
 
+		tupdesc = RelationGetDescr(rel);
+
+		/* throw error for system versioned table */
+		if (tupdesc->constr && tupdesc->constr->has_system_versioning)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("cannot truncate table with system versioning")));
+
 		rels = lappend(rels, rel);
 		relids = lappend_oid(relids, myrelid);
 
@@ -4101,6 +4116,8 @@ AlterTableGetLockLevel(List *cmds)
 			case AT_SetAccessMethod:	/* must rewrite heap */
 			case AT_SetTableSpace:	/* must rewrite heap */
 			case AT_AlterColumnType:	/* must rewrite heap */
+			case AT_PeriodColumn:
+			case AT_AddSystemVersioning:
 				cmd_lockmode = AccessExclusiveLock;
 				break;
 
@@ -4129,6 +4146,7 @@ AlterTableGetLockLevel(List *cmds)
 				 * Subcommands that may be visible to concurrent SELECTs
 				 */
 			case AT_DropColumn: /* change visible to SELECT */
+			case AT_DropSystemVersioning:	/* change visible to SELECT */
 			case AT_AddColumnToView:	/* CREATE VIEW */
 			case AT_DropOids:	/* used to equiv to DropColumn */
 			case AT_EnableAlwaysRule:	/* may change SELECT rules */
@@ -4794,6 +4812,35 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode,
 						  lfirst_node(AlterTableCmd, lcmd),
 						  lockmode, pass, context);
 
+			/*
+			 * Both system time columns have to be specified
+			 */
+			if (context)
+			{
+				if (context->hasSystemVersioning)
+				{
+					if (!context->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column not specified")));
+
+					if (!context->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column not specified")));
+
+					if (context->periodStart && strcmp(context->periodStart, context->startTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period start time column name must be the same as the name of row start time column")));
+
+					if (context->periodEnd && strcmp(context->periodEnd, context->endTimeColName) != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("period end time column name must be the same as  the name of row end time column")));
+				}
+			}
+
 			/*
 			 * After the ALTER TYPE pass, do cleanup work (this is not done in
 			 * ATExecAlterColumnType since it should be done only once if
@@ -4902,16 +4949,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 										   lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, false, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_DropColumnRecurse:	/* DROP COLUMN with recursion */
-			address = ATExecDropColumn(wqueue, rel, cmd->name,
+			address = ATExecDropColumn(wqueue, tab, rel, cmd->name,
 									   cmd->behavior, true, false,
 									   cmd->missing_ok, lockmode,
-									   NULL);
+									   NULL, context);
 			break;
 		case AT_AddIndex:		/* ADD INDEX */
 			address = ATExecAddIndex(tab, rel, (IndexStmt *) cmd->def, false,
@@ -5198,7 +5245,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/* Transform the AlterTableStmt */
 	atstmt = transformAlterTableStmt(RelationGetRelid(rel),
 									 atstmt,
-									 context->queryString,
+									 context,
 									 &beforeStmts,
 									 &afterStmts);
 
@@ -5586,6 +5633,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	BulkInsertState bistate;
 	int			ti_options;
 	ExprState  *partqualstate = NULL;
+	ResultRelInfo *resultRelInfo;
 
 	/*
 	 * Open the relation(s).  We have surely already locked the existing
@@ -5623,6 +5671,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 	 */
 
 	estate = CreateExecutorState();
+	resultRelInfo = makeNode(ResultRelInfo);
 
 	/* Build the needed expression execution states */
 	foreach(l, tab->constraints)
@@ -5690,6 +5739,12 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		ListCell   *lc;
 		Snapshot	snapshot;
 
+		InitResultRelInfo(resultRelInfo,
+						  oldrel,
+						  0,	/* dummy rangetable index */
+						  NULL,
+						  0);
+
 		if (newrel)
 			ereport(DEBUG1,
 					(errmsg_internal("rewriting table \"%s\"",
@@ -5778,6 +5833,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				slot_getallattrs(oldslot);
 				ExecClearTuple(newslot);
 
+				/* Only current data have to be in */
+				if (tab->systemVersioningRemoved)
+				{
+					if (oldslot->tts_values[tab->attnum - 1] != PG_INT64_MAX)
+						continue;
+				}
+
 				/* copy attributes */
 				memcpy(newslot->tts_values, oldslot->tts_values,
 					   sizeof(Datum) * oldslot->tts_nvalid);
@@ -5851,6 +5913,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+			/* Set system time columns */
+			if (tab->systemVersioningAdded)
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
+
 			/* Now check any constraints on the possibly-changed tuple */
 			econtext->ecxt_scantuple = insertslot;
 
@@ -6115,6 +6181,12 @@ alter_table_type_to_string(AlterTableType cmdtype)
 			return "ALTER COLUMN ... DROP IDENTITY";
 		case AT_ReAddStatistics:
 			return NULL;		/* not real grammar */
+		case AT_AddSystemVersioning:
+			return "ALTER TABLE ... ADD SYSTEM VERSIONING";
+		case AT_DropSystemVersioning:
+			return "ALTER TABLE ... DROP SYSTEM VERSIONING";
+		case AT_PeriodColumn:
+			return "ALTER TABLE ... ADD PERIOD COLUMN";
 	}
 
 	return NULL;
@@ -6803,6 +6875,14 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 	}
 
+	if (colDef->generated == ATTRIBUTE_ROW_START_TIME ||
+		colDef->generated == ATTRIBUTE_ROW_END_TIME)
+	{
+		/* must do a rewrite for system time columns */
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		tab->systemVersioningAdded = true;
+	}
+
 	/*
 	 * Tell Phase 3 to fill in the default expression, if there is one.
 	 *
@@ -7123,6 +7203,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 				 errmsg("column \"%s\" of relation \"%s\" is an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Check that the attribute is not in a primary key
 	 *
@@ -7566,6 +7653,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/*
 	 * Creating a column as identity implies NOT NULL, so adding the identity
 	 * to an existing column that is not NOT NULL would create a state that
@@ -7666,6 +7760,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 				 errmsg("column \"%s\" of relation \"%s\" is not an identity column",
 						colName, RelationGetRelationName(rel))));
 
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	if (generatedEl)
 	{
 		attTup->attidentity = defGetInt32(generatedEl);
@@ -8073,6 +8174,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 				 errmsg("cannot alter system column \"%s\"",
 						colName)));
 
+	if (attrtuple->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attrtuple->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 	/* Generate new proposed attoptions (text array) */
 	datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions,
 							&isnull);
@@ -8304,11 +8412,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
  * checked recursively.
  */
 static ObjectAddress
-ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
+ATExecDropColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, const char *colName,
 				 DropBehavior behavior,
 				 bool recurse, bool recursing,
 				 bool missing_ok, LOCKMODE lockmode,
-				 ObjectAddresses *addrs)
+				 ObjectAddresses *addrs,
+				 AlterTableUtilityContext *context)
 {
 	HeapTuple	tuple;
 	Form_pg_attribute targetatt;
@@ -8351,6 +8460,32 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 
 	attnum = targetatt->attnum;
 
+#ifdef NOTUSED
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+#endif
+
+	/*
+	 * Reviewers note: We should be disallowing DROP COLUMN on a
+	 * system time column, but DROP SYSTEM VERSIONING is currently
+	 * kluged to generate multiple DropColumn subcommands, which
+	 * means we need to allow this for now, even though an explicit
+	 * DROP COLUMN will crash the server. Needs work.
+	 */
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_END_TIME)
+	{
+		tab->attnum = attnum;
+		tab->systemVersioningRemoved = true;
+		tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
+		context->endTimeColName = NameStr(targetatt->attname);
+	}
+	if (targetatt->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		context->startTimeColName = NameStr(targetatt->attname);
+
 	/* Can't drop a system attribute */
 	if (attnum <= 0)
 		ereport(ERROR,
@@ -8412,11 +8547,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 			Oid			childrelid = lfirst_oid(child);
 			Relation	childrel;
 			Form_pg_attribute childatt;
+			AlteredTableInfo *childtab;
 
 			/* find_inheritance_children already got lock */
 			childrel = table_open(childrelid, NoLock);
 			CheckTableNotInUse(childrel, "ALTER TABLE");
 
+			/* Find or create work queue entry for this table */
+			childtab = ATGetQueueEntry(wqueue, childrel);
+
 			tuple = SearchSysCacheCopyAttName(childrelid, colName);
 			if (!HeapTupleIsValid(tuple))	/* shouldn't happen */
 				elog(ERROR, "cache lookup failed for attribute \"%s\" of relation %u",
@@ -8437,9 +8576,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
 				if (childatt->attinhcount == 1 && !childatt->attislocal)
 				{
 					/* Time to delete this child column, too */
-					ATExecDropColumn(wqueue, childrel, colName,
+					ATExecDropColumn(wqueue, childtab, childrel, colName,
 									 behavior, true, true,
-									 false, lockmode, addrs);
+									 false, lockmode, addrs, context);
 				}
 				else
 				{
@@ -10928,6 +11067,7 @@ transformFkeyCheckAttrs(Relation pkrel,
 	ListCell   *indexoidscan;
 	int			i,
 				j;
+	int			nattrs = numattrs;
 
 	/*
 	 * Reject duplicate appearances of columns in the referenced-columns list.
@@ -10947,6 +11087,30 @@ transformFkeyCheckAttrs(Relation pkrel,
 		}
 	}
 
+	/*
+	 * SQL Standard requires that an FK must refer to a current row of the pkrel,
+	 * if it system versioned. Thus the FK cannot contain the period start or end
+	 * time columns in the referenced-columns list.
+	 */
+	if (pkrel->rd_att->constr &&
+		pkrel->rd_att->constr->has_system_versioning)
+	{
+		for (i = 0; i < numattrs; i++)
+			if (pkrel->rd_att->constr->sv_starttime == attnums[i] ||
+				pkrel->rd_att->constr->sv_endtime == attnums[i])
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+						 errmsg("foreign key referenced-columns list must not contain system_time period columns"
+								" of a system versioned table")));
+
+		/*
+		 * System versioned tables must have a unique index on the PK plus period
+		 * end time, so we are looking for an index that has one more attribute
+		 * than the number of attrs in the referenced-columns list.
+		 */
+		nattrs++;
+	}
+
 	/*
 	 * Get the list of index OIDs for the table from the relcache, and look up
 	 * each one in the pg_index syscache, and match unique indexes to the list
@@ -10970,7 +11134,7 @@ transformFkeyCheckAttrs(Relation pkrel,
 		 * partial index; forget it if there are any expressions, too. Invalid
 		 * indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
+		if (indexStruct->indnkeyatts == nattrs &&
 			indexStruct->indisunique &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
@@ -10995,11 +11159,17 @@ transformFkeyCheckAttrs(Relation pkrel,
 			 * start of this function, and we checked above that the number of
 			 * index columns agrees, so if we find a match for each attnums[]
 			 * entry then we must have a one-to-one match in some order.
+			 *
+			 * If pkrel is a system versioned table then its PK will include
+			 * period end time, but that will never match anything in the
+			 * referenced-columns list because of our check above. So we don't
+			 * try to match that. i.e. we have numattrs columns to match, yet
+			 * we're looking for an index with numattrs+1 columns in it.
 			 */
 			for (i = 0; i < numattrs; i++)
 			{
 				found = false;
-				for (j = 0; j < numattrs; j++)
+				for (j = 0; j < nattrs; j++)
 				{
 					if (attnums[i] == indexStruct->indkey.values[j])
 					{
@@ -11012,6 +11182,27 @@ transformFkeyCheckAttrs(Relation pkrel,
 					break;
 			}
 
+			/*
+			 * Check that the last remaining unmatched column of the index
+			 * is the period endtime of the system versioned pkrel.
+			 */
+			if (found && nattrs != numattrs)
+			{
+				found = false;
+				for (j = 0; j < nattrs; j++)
+				{
+					if (pkrel->rd_att->constr->sv_endtime == indexStruct->indkey.values[j])
+					{
+						/*
+						 * FK code will automatically add starttime = 'infinity'
+						 * so we don't need to add any opclasses for that match here.
+						 */
+						found = true;
+						break;
+					}
+				}
+			}
+
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
 			 * spec, and there would be a lot of interesting semantic problems
@@ -12040,6 +12231,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter type of column \"%s\" twice",
 						colName)));
+	if (attTup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		attTup->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
 
 	/* Look up the target type (should not fail, since prep found it) */
 	typeTuple = typenameType(NULL, typeName, &targettypmod);
@@ -12821,10 +13018,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 		{
 			List	   *beforeStmts;
 			List	   *afterStmts;
+			AlterTableUtilityContext context;
+
+			context.queryString = cmd;
 
 			stmt = (Node *) transformAlterTableStmt(oldRelId,
 													(AlterTableStmt *) stmt,
-													cmd,
+													&context,
 													&beforeStmts,
 													&afterStmts);
 			querytree_list = list_concat(querytree_list, beforeStmts);
@@ -13177,6 +13377,13 @@ ATExecAlterColumnGenericOptions(Relation rel,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot alter system column \"%s\"", colName)));
 
+	if (atttableform->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+		atttableform->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("column \"%s\" of relation \"%s\" is system time column",
+						colName, RelationGetRelationName(rel))));
+
 
 	/* Initialize buffers for new tuple values */
 	memset(repl_val, 0, sizeof(repl_val));
@@ -16801,7 +17008,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
 			 * Generated columns cannot work: They are computed after BEFORE
 			 * triggers, but partition routing is done before all triggers.
 			 */
-			if (attform->attgenerated)
+			if (attform->attgenerated && attform->attgenerated != ATTRIBUTE_ROW_START_TIME
+				&& attform->attgenerated != ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
 						 errmsg("cannot use generated column in partition key"),
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 4df05a0b33..a9777d9f5d 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -25,6 +25,7 @@
 #include "nodes/nodeFuncs.h"
 #include "parser/analyze.h"
 #include "parser/parse_relation.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
 #include "rewrite/rewriteManip.h"
@@ -425,6 +426,11 @@ DefineView(ViewStmt *stmt, const char *queryString,
 
 	viewParse = parse_analyze(rawstmt, queryString, NULL, 0, NULL);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(viewParse);
+
 	/*
 	 * The grammar should ensure that the result is a single SELECT Query.
 	 * However, it doesn't forbid SELECT INTO, so we have to check for that.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c24684aa6f..e7cff4b565 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -572,6 +572,145 @@ ExecGetUpdateNewTuple(ResultRelInfo *relinfo,
 }
 
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		/*
+		 * We set infinity for row end time column for a tuple because row end
+		 * time is not yet known.
+		 */
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = DirectFunctionCall3(timestamptz_in,
+									  CStringGetDatum("infinity"),
+									  ObjectIdGetDatum(InvalidOid),
+									  Int32GetDatum(-1));
+
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Set row end time column for a tuple.
+ */
+bool
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+{
+	Relation	rel = resultRelInfo->ri_RelationDesc;
+	TupleDesc	tupdesc = RelationGetDescr(rel);
+	int			natts = tupdesc->natts;
+	MemoryContext oldContext;
+	Datum	   *values;
+	bool	   *nulls;
+	bool		same_xact = false;
+
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+
+	oldContext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+
+	values = palloc(sizeof(*values) * natts);
+	nulls = palloc(sizeof(*nulls) * natts);
+
+	slot_getallattrs(slot);
+	memcpy(nulls, slot->tts_isnull, sizeof(*nulls) * natts);
+
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			Assert(!nulls[i]);
+			values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+
+			/*
+			 * Avoid anomalies, as directed by SQL Standard,
+			 * 13.4 General Rule 15 a) iii) Case 1.
+			 */
+			if (GetCurrentTransactionStartTimestamp() < values[i])
+				ereport(ERROR,
+						(errcode(ERRCODE_DATA_EXCEPTION_INVALID_ROW_VERSION),
+						 errmsg("data exception — invalid row version")));
+			else if (GetCurrentTransactionStartTimestamp() == values[i])
+				same_xact = true;
+		}
+		else if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			Datum		val;
+
+			val = GetCurrentTransactionStartTimestamp();
+
+			values[i] = val;
+			nulls[i] = false;
+		}
+		else
+		{
+			if (!nulls[i])
+				values[i] = datumCopy(slot->tts_values[i], attr->attbyval, attr->attlen);
+		}
+	}
+
+	ExecClearTuple(slot);
+	memcpy(slot->tts_values, values, sizeof(*values) * natts);
+	memcpy(slot->tts_isnull, nulls, sizeof(*nulls) * natts);
+	ExecStoreVirtualTuple(slot);
+	ExecMaterializeSlot(slot);
+
+	MemoryContextSwitchTo(oldContext);
+
+	return !same_xact;
+}
+
 /* ----------------------------------------------------------------
  *		ExecInsert
  *
@@ -738,6 +877,13 @@ ExecInsert(ModifyTableState *mtstate,
 			return NULL;
 		}
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * insert into foreign table: let the FDW do it
 		 */
@@ -774,6 +920,13 @@ ExecInsert(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_INSERT);
 
+		/*
+		 * Set row start time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+
 		/*
 		 * Check any RLS WITH CHECK policies.
 		 *
@@ -1151,6 +1304,30 @@ ExecDelete(ModifyTableState *mtstate,
 	}
 	else
 	{
+		/*
+		 * Set row end time
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple while attempting delete with system versioning");
+			}
+			else
+			{
+				if (ExecSetRowEndTime(estate, mslot, resultRelInfo))
+					table_tuple_insert(resultRelationDesc, mslot,
+									   estate->es_output_cid,
+									   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+		}
+
 		/*
 		 * delete the tuple
 		 *
@@ -1698,6 +1875,40 @@ ExecUpdate(ModifyTableState *mtstate,
 			ExecComputeStoredGenerated(resultRelInfo, estate, slot,
 									   CMD_UPDATE);
 
+		/*
+		 * Set row end time and insert
+		 */
+		if (resultRelationDesc->rd_att->constr &&
+			resultRelationDesc->rd_att->constr->has_system_versioning)
+		{
+			TupleTableSlot *mslot = NULL;
+
+			/*
+			 * Insert a new row to represent the old row with EndTime set
+			 * Note that this creates a new row version rather than updating in place
+			 * the existing row version.
+			 */
+			mslot = table_slot_create(resultRelationDesc, NULL);
+			if (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
+											   mslot))
+			{
+				elog(ERROR, "failed to fetch tuple while attempting update with system versioning");
+			}
+			else
+			{
+				if (ExecSetRowEndTime(estate, mslot, resultRelInfo))
+					table_tuple_insert(resultRelationDesc, mslot,
+									   estate->es_output_cid,
+									   0, NULL);
+			}
+			ExecDropSingleTupleTableSlot(mslot);
+
+			/*
+			 * Set the StartTime for the soon to be newly updated row
+			 */
+			ExecSetRowStartTime(estate, slot, resultRelInfo);
+		}
+
 		/*
 		 * Check any RLS UPDATE WITH CHECK policies
 		 *
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 38251c2b8e..8cfe4c6226 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3523,6 +3523,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 	COPY_STRING_FIELD(tablespacename);
 	COPY_STRING_FIELD(accessMethod);
 	COPY_SCALAR_FIELD(if_not_exists);
+	COPY_SCALAR_FIELD(systemVersioning);
 }
 
 static CreateStmt *
@@ -4939,6 +4940,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
+_copyRowTime(const RowTime * from)
+{
+	RowTime    *newnode = makeNode(RowTime);
+
+	COPY_STRING_FIELD(start_time);
+	COPY_STRING_FIELD(end_time);
+
+	return newnode;
+}
+
+static TemporalClause *
+_copyTemporalClause(const TemporalClause * from)
+{
+	TemporalClause *newnode = makeNode(TemporalClause);
+
+	COPY_SCALAR_FIELD(kind);
+	COPY_NODE_FIELD(from);
+	COPY_NODE_FIELD(to);
+	COPY_NODE_FIELD(relation);
+
+	return newnode;
+}
+
 
 /*
  * copyObjectImpl -- implementation of copyObject(); see nodes/nodes.h
@@ -5854,6 +5879,12 @@ copyObjectImpl(const void *from)
 		case T_PartitionCmd:
 			retval = _copyPartitionCmd(from);
 			break;
+		case T_RowTime:
+			retval = _copyRowTime(from);
+			break;
+		case T_TemporalClause:
+			retval = _copyTemporalClause(from);
+			break;
 
 			/*
 			 * MISCELLANEOUS NODES
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 8a1762000c..d0182f5a49 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1293,6 +1293,7 @@ _equalCreateStmt(const CreateStmt *a, const CreateStmt *b)
 	COMPARE_STRING_FIELD(tablespacename);
 	COMPARE_STRING_FIELD(accessMethod);
 	COMPARE_SCALAR_FIELD(if_not_exists);
+	COMPARE_SCALAR_FIELD(systemVersioning);
 
 	return true;
 }
@@ -3025,6 +3026,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
+_equalRowTime(const RowTime * a, const RowTime * b)
+{
+	COMPARE_STRING_FIELD(start_time);
+	COMPARE_STRING_FIELD(end_time);
+
+	return true;
+}
+
+static bool
+_equalTemporalClause(const TemporalClause * a, const TemporalClause * b)
+{
+
+	COMPARE_SCALAR_FIELD(kind);
+	COMPARE_NODE_FIELD(from);
+	COMPARE_NODE_FIELD(to);
+	COMPARE_NODE_FIELD(relation);
+
+	return true;
+}
+
 /*
  * Stuff from pg_list.h
  */
@@ -3862,6 +3884,12 @@ equal(const void *a, const void *b)
 		case T_PartitionCmd:
 			retval = _equalPartitionCmd(a, b);
 			break;
+		case T_RowTime:
+			retval = _equalRowTime(a, b);
+			break;
+		case T_TemporalClause:
+			retval = _equalTemporalClause(a, b);
+			break;
 
 		default:
 			elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 01c110cd2f..cb9e7eef01 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -815,3 +815,126 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
 	v->va_cols = va_cols;
 	return v;
 }
+
+Node *
+makeAndExpr(Node *lexpr, Node *rexpr, int location)
+{
+	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
+	if (IsA(lexpr, BoolExpr))
+	{
+		BoolExpr *blexpr = (BoolExpr *) lexpr;
+
+		if (blexpr->boolop == AND_EXPR)
+		{
+			blexpr->args = lappend(blexpr->args, rexpr);
+			return (Node *) blexpr;
+		}
+	}
+	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
+}
+
+Node *
+makeTypeCast(Node *arg, TypeName *typename, int location)
+{
+	TypeCast   *n = makeNode(TypeCast);
+
+	n->arg = arg;
+	n->typeName = typename;
+	n->location = location;
+	return (Node *) n;
+}
+
+/*
+ * makeColumnRefFromName -
+ *	  creates a ColumnRef node using column name
+ */
+ColumnRef *
+makeColumnRefFromName(char *colname)
+{
+	ColumnRef  *c = makeNode(ColumnRef);
+
+	c->location = -1;
+	c->fields = lcons(makeString(colname), NIL);
+
+	return c;
+}
+
+/*
+ * makeTemporalColumnDef -
+ *       create a ColumnDef node for system time column
+ */
+ColumnDef *
+makeTemporalColumnDef(char *name)
+{
+	ColumnDef  *n = makeNode(ColumnDef);
+
+	if (strcmp(name, SYSTEM_VERSIONING_DEFAULT_START_NAME) == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_START_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+		n->colname = SYSTEM_VERSIONING_DEFAULT_START_NAME;
+		n->constraints = list_make1((Node *) c);
+	}
+	else if (strcmp(name, SYSTEM_VERSIONING_DEFAULT_END_NAME) == 0)
+	{
+		Constraint *c = makeNode(Constraint);
+
+		c->contype = CONSTR_ROW_END_TIME;
+		c->raw_expr = NULL;
+		c->cooked_expr = NULL;
+		c->location = -1;
+
+		n->colname = SYSTEM_VERSIONING_DEFAULT_END_NAME;
+		n->constraints = list_make1((Node *) c);
+	}
+	else
+		elog(ERROR, "unexpected temporal column name");
+	n->typeName = makeTypeNameFromNameList(list_make2(makeString("pg_catalog"),
+													  makeString("timestamptz")));
+	n->inhcount = 0;
+	n->is_local = true;
+	n->is_from_type = false;
+	n->storage = 0;
+	n->raw_default = NULL;
+	n->cooked_default = NULL;
+	n->collOid = InvalidOid;
+	n->location = -1;
+
+	return n;
+}
+
+/*
+ * makeAddColCmd -
+ *       create add column AlterTableCmd node
+ */
+AlterTableCmd *
+makeAddColCmd(ColumnDef *coldef)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_AddColumn;
+	n->def = (Node *) coldef;
+	n->missing_ok = false;
+
+	return n;
+}
+
+/*
+ * makeDropColCmd -
+ *       create drop column AlterTableCmd node
+ */
+AlterTableCmd *
+makeDropColCmd(char *name)
+{
+	AlterTableCmd *n = makeNode(AlterTableCmd);
+
+	n->subtype = AT_DropColumn;
+	n->name = name;
+	n->missing_ok = false;
+
+	return n;
+}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 87561cbb6f..f0af5a0bef 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2716,6 +2716,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 	WRITE_STRING_FIELD(tablespacename);
 	WRITE_STRING_FIELD(accessMethod);
 	WRITE_BOOL_FIELD(if_not_exists);
+	WRITE_BOOL_FIELD(systemVersioning);
 }
 
 static void
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 2cd691191c..5d850b1271 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -56,6 +56,8 @@
 #include "optimizer/tlist.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
+#include "parser/parse_clause.h"
+#include "parser/parse_relation.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index c9f7a09d10..11ca7bca0a 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -28,6 +28,7 @@
 #include "optimizer/optimizer.h"
 #include "optimizer/paramassign.h"
 #include "optimizer/pathnode.h"
+#include "optimizer/plancat.h"
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/prep.h"
@@ -912,6 +913,15 @@ SS_process_ctes(PlannerInfo *root)
 		 */
 		if (cte->cterefcount == 0 && cmdType == CMD_SELECT)
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
 			continue;
@@ -958,6 +968,15 @@ SS_process_ctes(PlannerInfo *root)
 			 !contain_outer_selfref(cte->ctequery)) &&
 			!contain_volatile_functions(cte->ctequery))
 		{
+			Query	   *query;
+
+			query = (Query *) cte->ctequery;
+
+			/*
+			 * Check and add filter clause to remove historical row versions.
+			 */
+			add_history_data_filter(query);
+
 			inline_cte(root, cte);
 			/* Make a dummy entry in cte_plan_ids */
 			root->cte_plan_ids = lappend_int(root->cte_plan_ids, -1);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c5194fdbbf..577c075e5e 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -41,6 +41,7 @@
 #include "optimizer/plancat.h"
 #include "optimizer/prep.h"
 #include "parser/parse_relation.h"
+#include "parser/parse_clause.h"
 #include "parser/parsetree.h"
 #include "partitioning/partdesc.h"
 #include "rewrite/rewriteManip.h"
@@ -52,6 +53,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
 #include "utils/syscache.h"
+#include "utils/memutils.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -79,6 +81,8 @@ static void set_baserel_partition_key_exprs(Relation relation,
 											RelOptInfo *rel);
 static void set_baserel_partition_constraint(Relation relation,
 											 RelOptInfo *rel);
+static bool check_system_versioning_column(Node *node, RangeTblEntry *rte);
+static bool check_system_versioning_table(RangeTblEntry *rte);
 
 
 /*
@@ -2408,3 +2412,193 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
 		rel->partition_qual = partconstr;
 	}
 }
+
+/*
+ * get_row_end_time_col_name
+ *
+ * Retrieve the row end time column name of the given relation.
+ */
+char *
+get_row_end_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * get_row_start_time_col_name
+ *
+ * Retrieve the row start time column name of the given relation.
+ */
+char *
+get_row_start_time_col_name(Relation rel)
+{
+	TupleDesc	tupdesc;
+	char	   *name = NULL;
+	int			natts;
+
+	tupdesc = RelationGetDescr(rel);
+	natts = tupdesc->natts;
+	for (int i = 0; i < natts; i++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+		if (attr->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			name = NameStr(attr->attname);
+			break;
+		}
+	}
+
+	return name;
+}
+
+/*
+ * add_history_data_filter
+ *
+ * Add history data filter clause to where clause specification
+ * if this is a system versioned relation and the where clause did not
+ * already contain a filter condition involving system time column.
+ */
+void
+add_history_data_filter(Query *query)
+{
+	ListCell   *l;
+
+	foreach(l, query->rtable)
+	{
+		RangeTblEntry *rte = lfirst_node(RangeTblEntry, l);
+
+		if (!check_system_versioning_table(rte) ||
+			check_system_versioning_column(query->jointree->quals, rte))
+		{
+			continue;
+		}
+		else
+		{
+			Node	   *wClause;
+			Relation	relation;
+			ColumnRef  *c;
+			A_Const    *n;
+			ParseState *pstate;
+			ParseNamespaceItem *newnsitem;
+
+			relation = table_open(rte->relid, NoLock);
+
+			/*
+			 * Create a condition that filters history data and attach it to
+			 * the existing where clause.
+			 */
+			c = makeColumnRefFromName(get_row_end_time_col_name(relation));
+			n = makeNode(A_Const);
+			n->val.type = T_String;
+			n->val.val.str = "infinity";
+			n->location = -1;
+
+			wClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", (Node *) c, (Node *) n, 0);
+
+			/*
+			 * Create a dummy ParseState and insert the target relation as its
+			 * sole rangetable entry.  We need a ParseState for transformExpr.
+			 */
+			pstate = make_parsestate(NULL);
+			newnsitem = addRangeTableEntryForRelation(pstate,
+													  relation,
+													  AccessShareLock,
+													  NULL,
+													  false,
+													  true);
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
+			wClause = transformWhereClause(pstate,
+										   wClause,
+										   EXPR_KIND_WHERE,
+										   "WHERE");
+			if (query->jointree->quals != NULL)
+			{
+				query->jointree->quals = make_and_qual(query->jointree->quals, wClause);
+			}
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+			{
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+
+				j->quals = make_and_qual(j->quals, wClause);
+			}
+			else
+			{
+				query->jointree->quals = wClause;
+			}
+
+			table_close(relation, NoLock);
+		}
+
+	}
+}
+
+/*
+ * Check for references to system versioned columns
+ */
+static bool
+check_system_versioning_column_walker(Node *node, RangeTblEntry *rte)
+{
+
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var))
+	{
+		Var		   *var = (Var *) node;
+		Oid			relid;
+		AttrNumber	attnum;
+		char		result;
+
+		relid = rte->relid;
+		attnum = var->varattno;
+		result = get_attgenerated(relid, attnum);
+
+		if (OidIsValid(relid) && AttributeNumberIsValid(attnum) &&
+			(result == ATTRIBUTE_ROW_START_TIME || result == ATTRIBUTE_ROW_END_TIME))
+			return true;
+	}
+	return expression_tree_walker(node, check_system_versioning_column_walker,
+								  rte);
+}
+
+static bool
+check_system_versioning_column(Node *node, RangeTblEntry *rte)
+{
+	return check_system_versioning_column_walker(node, rte);
+}
+
+static bool
+check_system_versioning_table(RangeTblEntry *rte)
+{
+	Relation	rel;
+	TupleDesc	tupdesc;
+	bool		result = false;
+
+	if (rte->relid == 0)
+		return false;
+
+	rel = table_open(rte->relid, NoLock);
+	tupdesc = RelationGetDescr(rel);
+	result = tupdesc->constr && tupdesc->constr->has_system_versioning;
+
+	table_close(rel, NoLock);
+
+	return result;
+}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 438b077004..8896440505 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_agg.h"
 #include "parser/parse_clause.h"
@@ -477,6 +478,11 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
@@ -1269,6 +1275,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* process the FROM clause */
 	transformFromClause(pstate, stmt->fromClause);
 
+	/* Add temporal filter clause to the rest of where clause */
+	if (pstate->p_tempwhere != NULL)
+	{
+		if (stmt->whereClause)
+			stmt->whereClause = makeAndExpr(stmt->whereClause, pstate->p_tempwhere, 0);
+		else
+			stmt->whereClause = pstate->p_tempwhere;
+	}
+
 	/* transform targetlist */
 	qry->targetList = transformTargetList(pstate, stmt->targetList,
 										  EXPR_KIND_SELECT_TARGET);
@@ -1367,6 +1382,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
 		parseCheckAggregates(pstate, qry);
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	return qry;
 }
 
@@ -2357,6 +2377,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
+	 * Check and add filter clause to filter out historical data.
+	 */
+	add_history_data_filter(qry);
+
 	assign_query_collations(pstate, qry);
 
 	return qry;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39a2849eba..2a52badc67 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -141,6 +141,20 @@ typedef struct GroupClause
 	List   *list;
 } GroupClause;
 
+/* Private struct for the result of generated_type production */
+typedef struct GenerateType
+{
+	ConstrType	contype;
+	Node	   *raw_expr;
+} GenerateType;
+
+/* Private struct for the result of OptWith production */
+typedef struct OptionWith
+{
+	List   *options;
+	bool	systemVersioning;
+} OptionWith;
+
 /* ConstraintAttributeSpec yields an integer bitmask of these flags: */
 #define CAS_NOT_DEFERRABLE			0x01
 #define CAS_DEFERRABLE				0x02
@@ -159,7 +173,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
 static void updateRawStmtEnd(RawStmt *rs, int end_location);
 static Node *makeColumnRef(char *colname, List *indirection,
 						   int location, core_yyscan_t yyscanner);
-static Node *makeTypeCast(Node *arg, TypeName *typename, int location);
 static Node *makeStringConst(char *str, int location);
 static Node *makeStringConstCast(char *str, int location, TypeName *typename);
 static Node *makeIntConst(int val, int location);
@@ -184,7 +197,6 @@ static void insertSelectOptions(SelectStmt *stmt,
 static Node *makeSetOp(SetOperation op, bool all, Node *larg, Node *rarg);
 static Node *doNegate(Node *n, int location);
 static void doNegateFloat(Value *v);
-static Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeOrExpr(Node *lexpr, Node *rexpr, int location);
 static Node *makeNotExpr(Node *expr, int location);
 static Node *makeAArrayExpr(List *elements, int location);
@@ -260,6 +272,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct SelectLimit	*selectlimit;
 	SetQuantifier	 setquantifier;
 	struct GroupClause  *groupclause;
+	TemporalClause *temporalClause;
+	struct GenerateType *GenerateType;
+	struct OptionWith   *OptionWith;
 }
 
 %type <node>	stmt toplevel_stmt schema_stmt routine_body_stmt
@@ -395,11 +410,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	vacuum_relation
 %type <selectlimit> opt_select_limit select_limit limit_clause
 
+%type <GenerateType> generated_type
+%type <OptionWith> OptWith
+
 %type <list>	parse_toplevel stmtmulti routine_body_stmt_list
 				OptTableElementList TableElementList OptInherit definition
 				OptTypedTableElementList TypedTableElementList
 				reloptions opt_reloptions
-				OptWith opt_definition func_args func_args_list
+				opt_definition func_args func_args_list
 				func_args_with_defaults func_args_with_defaults_list
 				aggr_args aggr_args_list
 				func_as createfunc_opt_list opt_createfunc_opt_list alterfunc_opt_list
@@ -521,7 +539,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <range>	relation_expr_opt_alias
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
-
+%type <temporalClause>	temporal_clause
 %type <str>		generic_option_name
 %type <node>	generic_option_arg
 %type <defelt>	generic_option_elem alter_generic_option_elem
@@ -559,7 +577,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> col_name_keyword reserved_keyword
 %type <keyword> bare_label_keyword
 
-%type <node>	TableConstraint TableLikeClause
+%type <node>	TableConstraint TableLikeClause optSystemTimeColumn
 %type <ival>	TableLikeOptionList TableLikeOption
 %type <str>		column_compression opt_column_compression
 %type <list>	ColQualList
@@ -692,7 +710,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PERIOD PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -707,7 +725,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
 	SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
 	START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
-	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
+	SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P SYSTEM_TIME
 
 	TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
 	TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@@ -718,7 +736,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	UNLISTEN UNLOGGED UNTIL UPDATE USER USING
 
 	VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
-	VERBOSE VERSION_P VIEW VIEWS VOLATILE
+	VERBOSE VERSION_P VERSIONING VIEW VIEWS VOLATILE
 
 	WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
 
@@ -739,7 +757,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * as NOT, at least with respect to their left-hand subexpression.
  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
  */
-%token		NOT_LA NULLS_LA WITH_LA
+%token		NOT_LA NULLS_LA WITH_LA FOR_LA
 
 /*
  * The grammar likewise thinks these tokens are keywords, but they are never
@@ -766,6 +784,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %nonassoc	'<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 %nonassoc	BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
 %nonassoc	ESCAPE			/* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+%nonassoc		SYSTEM_P
+%nonassoc		VERSIONING
+%nonassoc		DAY_P
 /*
  * To support target_el without AS, it used to be necessary to assign IDENT an
  * explicit precedence just less than Op.  While that's not really necessary
@@ -805,6 +826,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left		'(' ')'
 %left		TYPECAST
 %left		'.'
+%left		YEAR_P
+%left		MONTH_P
+%left		HOUR_P
+%left		MINUTE_P
+%left		TO
 /*
  * These might seem to be low-precedence, but actually they are not part
  * of the arithmetic hierarchy at all in their use as JOIN operators.
@@ -2181,6 +2207,14 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			| ADD_P  optSystemTimeColumn
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_PeriodColumn;
+					n->def = $2;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/* ALTER TABLE <name> ADD IF NOT EXISTS <coldef> */
 			| ADD_P IF_P NOT EXISTS columnDef
 				{
@@ -2208,7 +2242,15 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> ALTER [COLUMN] <colname> {SET DEFAULT <expr>|DROP DEFAULT} */
+			/* ALTER TABLE <name> ADD SYSTEM VERSIONING */
+			| ADD_P SYSTEM_P VERSIONING
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_AddSystemVersioning;
+					n->def = NULL;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			| ALTER opt_column ColId alter_column_default
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
@@ -2356,7 +2398,19 @@ alter_table_cmd:
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column IF_P EXISTS ColId opt_drop_behavior
+			| DROP IF_P EXISTS ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $4;
+					n->behavior = $5;
+					n->missing_ok = true;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN IF_P EXISTS ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2365,8 +2419,20 @@ alter_table_cmd:
 					n->missing_ok = true;
 					$$ = (Node *)n;
 				}
-			/* ALTER TABLE <name> DROP [COLUMN] <colname> [RESTRICT|CASCADE] */
-			| DROP opt_column ColId opt_drop_behavior
+			/* ALTER TABLE <name> DROP <colname> [RESTRICT|CASCADE] */
+			| DROP ColId opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropColumn;
+					n->name = $2;
+					n->behavior = $3;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
+			/*
+			 * Redundancy here is needed to avoid shift/reduce conflicts.
+			 */
+			| DROP COLUMN ColId opt_drop_behavior
 				{
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_DropColumn;
@@ -2375,6 +2441,15 @@ alter_table_cmd:
 					n->missing_ok = false;
 					$$ = (Node *)n;
 				}
+			/* ALTER TABLE <name> DROP SYSTEM VERSIONING [RESTRICT|CASCADE] */
+			|  DROP SYSTEM_P VERSIONING opt_drop_behavior
+				{
+					AlterTableCmd *n = makeNode(AlterTableCmd);
+					n->subtype = AT_DropSystemVersioning;
+					n->behavior = $4;
+					n->missing_ok = false;
+					$$ = (Node *)n;
+				}
 			/*
 			 * ALTER TABLE <name> ALTER [COLUMN] <colname> [SET DATA] TYPE <typename>
 			 *		[ USING <expression> ]
@@ -3282,12 +3357,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $6;
+					n->systemVersioning = ($11)->systemVersioning;
 					n->inhRelations = $8;
 					n->partspec = $9;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $10;
-					n->options = $11;
+					n->options = ($11)->options;
 					n->oncommit = $12;
 					n->tablespacename = $13;
 					n->if_not_exists = false;
@@ -3301,12 +3377,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $9;
+					n->systemVersioning = ($14)->systemVersioning;
 					n->inhRelations = $11;
 					n->partspec = $12;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $13;
-					n->options = $14;
+					n->options = ($14)->options;
 					n->oncommit = $15;
 					n->tablespacename = $16;
 					n->if_not_exists = true;
@@ -3320,13 +3397,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $7;
+					n->systemVersioning = ($10)->systemVersioning;
 					n->inhRelations = NIL;
 					n->partspec = $8;
 					n->ofTypename = makeTypeNameFromNameList($6);
 					n->ofTypename->location = @6;
 					n->constraints = NIL;
 					n->accessMethod = $9;
-					n->options = $10;
+					n->options = ($10)->options;
 					n->oncommit = $11;
 					n->tablespacename = $12;
 					n->if_not_exists = false;
@@ -3340,13 +3418,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $10;
+					n->systemVersioning = ($13)->systemVersioning;
 					n->inhRelations = NIL;
 					n->partspec = $11;
 					n->ofTypename = makeTypeNameFromNameList($9);
 					n->ofTypename->location = @9;
 					n->constraints = NIL;
 					n->accessMethod = $12;
-					n->options = $13;
+					n->options = ($13)->options;
 					n->oncommit = $14;
 					n->tablespacename = $15;
 					n->if_not_exists = true;
@@ -3360,13 +3439,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$4->relpersistence = $2;
 					n->relation = $4;
 					n->tableElts = $8;
+					n->systemVersioning = ($12)->systemVersioning;
 					n->inhRelations = list_make1($7);
 					n->partbound = $9;
 					n->partspec = $10;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $11;
-					n->options = $12;
+					n->options = ($12)->options;
 					n->oncommit = $13;
 					n->tablespacename = $14;
 					n->if_not_exists = false;
@@ -3380,13 +3460,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
 					$7->relpersistence = $2;
 					n->relation = $7;
 					n->tableElts = $11;
+					n->systemVersioning = ($15)->systemVersioning;
 					n->inhRelations = list_make1($10);
 					n->partbound = $12;
 					n->partspec = $13;
 					n->ofTypename = NULL;
 					n->constraints = NIL;
 					n->accessMethod = $14;
-					n->options = $15;
+					n->options = ($15)->options;
 					n->oncommit = $16;
 					n->tablespacename = $17;
 					n->if_not_exists = true;
@@ -3463,6 +3544,7 @@ TableElement:
 			columnDef							{ $$ = $1; }
 			| TableLikeClause					{ $$ = $1; }
 			| TableConstraint					{ $$ = $1; }
+			| optSystemTimeColumn					{ $$ = $1; }
 		;
 
 TypedTableElement:
@@ -3570,6 +3652,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
+			PERIOD FOR_LA SYSTEM_TIME '(' name ',' name ')'
+				{
+					RowTime *n = makeNode(RowTime);
+					n->start_time = $5;
+					n->end_time = $7;
+					$$ = (Node *)n;
+				}
+		;
+
 /* DEFAULT NULL is already the default for Postgres.
  * But define it here and carry it forward into the system
  * to make it explicit.
@@ -3652,12 +3744,12 @@ ColConstraintElem:
 					n->location = @1;
 					$$ = (Node *)n;
 				}
-			| GENERATED generated_when AS '(' a_expr ')' STORED
+			| GENERATED generated_when AS generated_type
 				{
 					Constraint *n = makeNode(Constraint);
-					n->contype = CONSTR_GENERATED;
+					n->contype = ($4)->contype;
 					n->generated_when = $2;
-					n->raw_expr = $5;
+					n->raw_expr = ($4)->raw_expr;
 					n->cooked_expr = NULL;
 					n->location = @1;
 
@@ -3675,6 +3767,7 @@ ColConstraintElem:
 
 					$$ = (Node *)n;
 				}
+
 			| REFERENCES qualified_name opt_column_list key_match key_actions
 				{
 					Constraint *n = makeNode(Constraint);
@@ -3697,6 +3790,30 @@ generated_when:
 			| BY DEFAULT	{ $$ = ATTRIBUTE_IDENTITY_BY_DEFAULT; }
 		;
 
+generated_type:
+			'(' a_expr ')' STORED
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_GENERATED;
+					n->raw_expr = $2;
+					$$ = n;
+				}
+			| ROW START
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_START_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+			| ROW END_P
+				{
+					GenerateType *n = (GenerateType *) palloc(sizeof(GenerateType));
+					n->contype = CONSTR_ROW_END_TIME;
+					n->raw_expr = NULL;
+					$$ = n;
+				}
+		;
+
 /*
  * ConstraintAttr represents constraint attributes, which we parse as if
  * they were independent constraint clauses, in order to avoid shift/reduce
@@ -4074,9 +4191,34 @@ table_access_method_clause:
 
 /* WITHOUT OIDS is legacy only */
 OptWith:
-			WITH reloptions				{ $$ = $2; }
-			| WITHOUT OIDS				{ $$ = NIL; }
-			| /*EMPTY*/					{ $$ = NIL; }
+			WITH reloptions
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = $2;
+					n->systemVersioning = false;
+					$$ = n;
+				}
+			| WITHOUT OIDS
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = false;
+					$$ = n;
+				}
+			| WITH SYSTEM_P VERSIONING
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = true;
+					$$ = n;
+				}
+			| /*EMPTY*/
+				{
+					OptionWith *n = (OptionWith *) palloc(sizeof(OptionWith));
+					n->options = NIL;
+					n->systemVersioning = false;
+					$$ = n;
+				}
 		;
 
 OnCommitOption:  ON COMMIT DROP				{ $$ = ONCOMMIT_DROP; }
@@ -4242,7 +4384,7 @@ create_as_target:
 					$$->rel = $1;
 					$$->colNames = $2;
 					$$->accessMethod = $3;
-					$$->options = $4;
+					$$->options = ($4)->options;
 					$$->onCommit = $5;
 					$$->tableSpaceName = $6;
 					$$->viewQuery = NULL;
@@ -12003,7 +12145,7 @@ having_clause:
 
 for_locking_clause:
 			for_locking_items						{ $$ = $1; }
-			| FOR READ ONLY							{ $$ = NIL; }
+			| FOR READ ONLY                                                 { $$ = NIL; }
 		;
 
 opt_for_locking_clause:
@@ -12082,12 +12224,16 @@ from_list:
 /*
  * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr opt_alias_clause
+table_ref:	relation_expr alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| relation_expr opt_alias_clause tablesample_clause
+			| relation_expr %prec UMINUS
+				{
+					$$ = (Node *) $1;
+				}
+			| relation_expr alias_clause tablesample_clause
 				{
 					RangeTableSample *n = (RangeTableSample *) $3;
 					$1->alias = $2;
@@ -12095,6 +12241,19 @@ table_ref:	relation_expr opt_alias_clause
 					n->relation = (Node *) $1;
 					$$ = (Node *) n;
 				}
+
+			| relation_expr tablesample_clause
+				{
+					RangeTableSample *n = (RangeTableSample *) $2;
+					/* relation_expr goes inside the RangeTableSample node */
+					n->relation = (Node *) $1;
+					$$ = (Node *) n;
+				}
+			| relation_expr temporal_clause
+				{
+					$2->relation = (Node *)$1;
+					$$ = (Node *)$2;
+				}
 			| func_table func_alias_clause
 				{
 					RangeFunction *n = (RangeFunction *) $1;
@@ -12193,7 +12352,54 @@ table_ref:	relation_expr opt_alias_clause
 					$$ = (Node *) $2;
 				}
 		;
+temporal_clause:  FOR_LA SYSTEM_TIME AS OF a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = AS_OF;
+					$$->from = NULL;
+					$$->to = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_T;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN SYMMETRIC a_expr AND a_expr
+				{
+					MinMaxExpr *g = makeNode(MinMaxExpr);
+					MinMaxExpr *l = makeNode(MinMaxExpr);
+					$$ = makeNode(TemporalClause);
 
+					$$->kind = BETWEEN_SYMMETRIC;
+					l->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+							@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					l->op = IS_LEAST;
+					l->location = @1;
+					$$->from = (Node *)l;
+
+					g->args = list_make2((Node *)makeTypeCast($5, SystemTypeName("timestamptz"),
+						@2),(Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7));
+					g->op = IS_GREATEST;
+					g->location = @1;
+					$$->to = (Node *)g;
+				}
+			| FOR_LA SYSTEM_TIME BETWEEN ASYMMETRIC a_expr AND a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = BETWEEN_ASYMMETRIC;
+					$$->from = (Node *)makeTypeCast($5, SystemTypeName("timestamptz"), @5);
+					$$->to = (Node *)makeTypeCast($7, SystemTypeName("timestamptz"), @7);
+				}
+			| FOR_LA SYSTEM_TIME FROM a_expr TO a_expr
+				{
+					$$ = makeNode(TemporalClause);
+					$$->kind = FROM_TO;
+					$$->from = (Node *)makeTypeCast($4, SystemTypeName("timestamptz"), @4);
+					$$->to = (Node *)makeTypeCast($6, SystemTypeName("timestamptz"), @6);
+				}
+		;
 
 /*
  * It may seem silly to separate joined_table from table_ref, but there is
@@ -15661,6 +15867,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15738,6 +15945,7 @@ unreserved_keyword:
 			| SUPPORT
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLES
 			| TABLESPACE
 			| TEMP
@@ -15768,6 +15976,7 @@ unreserved_keyword:
 			| VALUE_P
 			| VARYING
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16239,6 +16448,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
@@ -16330,6 +16540,7 @@ bare_label_keyword:
 			| SYMMETRIC
 			| SYSID
 			| SYSTEM_P
+			| SYSTEM_TIME
 			| TABLE
 			| TABLES
 			| TABLESAMPLE
@@ -16375,6 +16586,7 @@ bare_label_keyword:
 			| VARIADIC
 			| VERBOSE
 			| VERSION_P
+			| VERSIONING
 			| VIEW
 			| VIEWS
 			| VOLATILE
@@ -16491,16 +16703,6 @@ makeColumnRef(char *colname, List *indirection,
 	return (Node *) c;
 }
 
-static Node *
-makeTypeCast(Node *arg, TypeName *typename, int location)
-{
-	TypeCast *n = makeNode(TypeCast);
-	n->arg = arg;
-	n->typeName = typename;
-	n->location = location;
-	return (Node *) n;
-}
-
 static Node *
 makeStringConst(char *str, int location)
 {
@@ -16905,23 +17107,6 @@ doNegateFloat(Value *v)
 		v->val.str = psprintf("-%s", oldval);
 }
 
-static Node *
-makeAndExpr(Node *lexpr, Node *rexpr, int location)
-{
-	/* Flatten "a AND b AND c ..." to a single BoolExpr on sight */
-	if (IsA(lexpr, BoolExpr))
-	{
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
-
-		if (blexpr->boolop == AND_EXPR)
-		{
-			blexpr->args = lappend(blexpr->args, rexpr);
-			return (Node *) blexpr;
-		}
-	}
-	return (Node *) makeBoolExpr(AND_EXPR, list_make2(lexpr, rexpr), location);
-}
-
 static Node *
 makeOrExpr(Node *lexpr, Node *rexpr, int location)
 {
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index b3f151d33b..8fd4a19702 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -31,6 +31,7 @@
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
+#include "optimizer/plancat.h"
 #include "parser/analyze.h"
 #include "parser/parse_clause.h"
 #include "parser/parse_coerce.h"
@@ -97,6 +98,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);
 static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
 								  Oid rangeopfamily, Oid rangeopcintype, Oid *inRangeFunc,
 								  Node *clause);
+static void changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);
 
 
 /*
@@ -1138,6 +1140,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		rte->tablesample = transformRangeTableSample(pstate, rts);
 		return rel;
 	}
+	else if (IsA(n, TemporalClause))
+	{
+		TemporalClause *tc = (TemporalClause *) n;
+		RangeVar   *rv = (RangeVar *) tc->relation;
+		RangeTblRef *rtr;
+		ParseNamespaceItem *nsitem;
+		RangeTblEntry *rte;
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		nsitem = transformTableEntry(pstate, rv);
+		rte = nsitem->p_rte;
+		rel = table_open(rte->relid, NoLock);
+		tupdesc = RelationGetDescr(rel);
+		rte->has_system_versioning = (tupdesc->constr && tupdesc->constr->has_system_versioning);
+		table_close(rel, NoLock);
+
+		if (!rte->has_system_versioning)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("temporal clause can only specified for a table with system versioning")));
+
+		changeTemporalToWhereClause(pstate, tc, rte);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
+		rtr = makeNode(RangeTblRef);
+		rtr->rtindex = nsitem->p_rtindex;
+		return (Node *) rtr;
+	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
@@ -3704,3 +3735,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ * changeTemporalToWhereClause
+ *		make where clause from temporal clause specification.
+ */
+static void
+changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+{
+	Node	   *fClause = NULL;
+	Node	   *tClause = NULL;
+	Node	   *cClause = NULL;
+	ColumnRef  *s;
+	ColumnRef  *e;
+	Relation	rel;
+
+	rel = table_open(rte->relid, NoLock);
+	s = makeColumnRefFromName(get_row_start_time_col_name(rel));
+	e = makeColumnRefFromName(get_row_end_time_col_name(rel));
+	if (tc->kind == AS_OF)
+	{
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == BETWEEN_T)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+	else if (tc->kind == BETWEEN_ASYMMETRIC)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, tClause, 0);
+
+	}
+	else if (tc->kind == BETWEEN_SYMMETRIC)
+	{
+		tc->to = makeTypeCast((Node *) tc->to, typeStringToTypeName("timestamptz"), -1);
+		tc->from = makeTypeCast((Node *) tc->from, typeStringToTypeName("timestamptz"), -1);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+	}
+	else if (tc->kind == FROM_TO)
+	{
+		cClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", tc->from, tc->to, 0);
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
+		tClause = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", (Node *) s, tc->to, 0);
+
+		fClause = makeAndExpr(fClause, tClause, 0);
+		fClause = makeAndExpr(fClause, cClause, 0);
+	}
+
+	if (pstate->p_tempwhere != NULL)
+		pstate->p_tempwhere = makeAndExpr(pstate->p_tempwhere, fClause, 0);
+	else
+		pstate->p_tempwhere = fClause;
+
+	table_close(rel, NoLock);
+}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 675e400839..7c2fdeb08c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -58,6 +58,7 @@
 #include "parser/parse_type.h"
 #include "parser/parse_utilcmd.h"
 #include "parser/parser.h"
+#include "optimizer/plancat.h"
 #include "rewrite/rewriteManip.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -69,6 +70,8 @@
 #include "utils/typcache.h"
 
 
+#include <string.h>
+
 /* State shared by transformCreateStmt and its subroutines */
 typedef struct
 {
@@ -93,6 +96,11 @@ typedef struct
 	bool		ispartitioned;	/* true if table is partitioned */
 	PartitionBoundSpec *partbound;	/* transformed FOR VALUES */
 	bool		ofType;			/* true if statement contains OF typename */
+	bool		hasSystemVersioning;	/* true if table is system versioned */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } CreateStmtContext;
 
 /* State shared by transformCreateSchemaStmt and its subroutines */
@@ -116,6 +124,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
 									 Constraint *constraint);
 static void transformTableLikeClause(CreateStmtContext *cxt,
 									 TableLikeClause *table_like_clause);
+static void transformPeriodColumn(CreateStmtContext *cxt,
+								  RowTime * cols);
 static void transformOfType(CreateStmtContext *cxt,
 							TypeName *ofTypename);
 static CreateStatsStmt *generateClonedExtStatsStmt(RangeVar *heapRel,
@@ -242,6 +252,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 	cxt.ispartitioned = stmt->partspec != NULL;
 	cxt.partbound = stmt->partbound;
 	cxt.ofType = (stmt->ofTypename != NULL);
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.hasSystemVersioning = false;
+
 
 	Assert(!stmt->ofTypename || !stmt->inhRelations);	/* grammar enforces */
 
@@ -278,6 +292,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 				transformTableLikeClause(&cxt, (TableLikeClause *) element);
 				break;
 
+			case T_RowTime:
+				transformPeriodColumn(&cxt, (RowTime *) element);
+				break;
+
 			default:
 				elog(ERROR, "unrecognized node type: %d",
 					 (int) nodeTag(element));
@@ -285,6 +303,27 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+	 * If there are no system time columns and the user specified "WITH SYSTEM
+	 * VERSIONING", default system time columns is prepended to the table
+	 * definition. This is an extension to the SQL Standard.
+	 */
+	if (!cxt.hasSystemVersioning && stmt->systemVersioning)
+	{
+		ColumnDef  *startCol;
+		ColumnDef  *endCol;
+
+		startCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_START_NAME);
+		endCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_END_NAME);
+		if (stmt->tableElts == NIL)
+			stmt->tableElts = list_make2(startCol, endCol);
+		else
+			stmt->tableElts = lappend(list_make2(startCol, endCol), stmt->tableElts);
+
+		transformColumnDefinition(&cxt, startCol);
+		transformColumnDefinition(&cxt, endCol);
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.  (This may
@@ -296,6 +335,36 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 
 	Assert(stmt->constraints == NIL);
 
+	if (cxt.hasSystemVersioning)
+	{
+		ListCell   *lc;
+
+		if (!cxt.startTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period start time column not specified")));
+
+		if (!cxt.endTimeColName)
+			ereport(ERROR,
+					(errcode(ERRCODE_SYNTAX_ERROR),
+					 errmsg("period end time column not specified")));
+
+		/*
+		 * End time column is added to primary and unique key constraint
+		 * implicitly to make history and current data co-exist.
+		 */
+		foreach(lc, cxt.ixconstraints)
+		{
+			Constraint *constraint = lfirst_node(Constraint, lc);
+
+			if ((constraint->contype == CONSTR_PRIMARY ||
+				 constraint->contype == CONSTR_UNIQUE) && constraint->keys != NIL)
+			{
+				constraint->keys = lappend(constraint->keys, makeString(cxt.endTimeColName));
+			}
+		}
+	}
+
 	/*
 	 * Postprocess constraints that give rise to index definitions.
 	 */
@@ -747,6 +816,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
+			case CONSTR_ROW_START_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row start time must be timestamptz")));
+
+					if (cxt->startTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row start time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_START_TIME;
+					cxt->startTimeColName = column->colname;
+					cxt->hasSystemVersioning = true;
+					column->is_not_null = true;
+					break;
+				}
+
+			case CONSTR_ROW_END_TIME:
+				{
+					Type		ctype;
+					Form_pg_type typform;
+					char	   *typname;
+
+					ctype = typenameType(cxt->pstate, column->typeName, NULL);
+					typform = (Form_pg_type) GETSTRUCT(ctype);
+					typname = NameStr(typform->typname);
+					ReleaseSysCache(ctype);
+
+					if (strcmp(typname, "timestamptz") != 0)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("data type of row end time must be timestamptz")));
+
+					if (cxt->endTimeColName)
+						ereport(ERROR,
+								(errcode(ERRCODE_SYNTAX_ERROR),
+								 errmsg("row end time specified more than once")));
+
+					column->generated = ATTRIBUTE_ROW_END_TIME;
+					cxt->endTimeColName = column->colname;
+					cxt->hasSystemVersioning = true;
+					column->is_not_null = true;
+					break;
+				}
+
 			case CONSTR_CHECK:
 				cxt->ckconstraints = lappend(cxt->ckconstraints, constraint);
 				break;
@@ -1444,6 +1569,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
+ * transformPeriodColumn
+ *		transform a period node within CREATE TABLE
+ */
+static void
+transformPeriodColumn(CreateStmtContext *cxt, RowTime * col)
+{
+	cxt->periodStart = col->start_time;
+	cxt->periodEnd = col->end_time;
+
+	if (!cxt->startTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time column not specified")));
+	if (!cxt->endTimeColName)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time column not specified")));
+
+	if (strcmp(cxt->periodStart, cxt->startTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period start time must reference the row start time column")));
+	if (strcmp(cxt->periodEnd, cxt->endTimeColName) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("period end time must reference the row end time column")));
+}
+
 static void
 transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
 {
@@ -3283,7 +3437,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
  */
 AlterTableStmt *
 transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-						const char *queryString,
+						AlterTableUtilityContext *context,
 						List **beforeStmts, List **afterStmts)
 {
 	Relation	rel;
@@ -3304,7 +3458,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
-	pstate->p_sourcetext = queryString;
+	pstate->p_sourcetext = context->queryString;
 	nsitem = addRangeTableEntryForRelation(pstate,
 										   rel,
 										   AccessShareLock,
@@ -3341,6 +3495,9 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	cxt.ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 	cxt.partbound = NULL;
 	cxt.ofType = false;
+	cxt.startTimeColName = NULL;
+	cxt.endTimeColName = NULL;
+	cxt.hasSystemVersioning = false;
 
 	/*
 	 * Transform ALTER subcommands that need it (most don't).  These largely
@@ -3375,6 +3532,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 					newcmds = lappend(newcmds, cmd);
 					break;
 				}
+			case AT_PeriodColumn:
+				{
+					RowTime    *rtime = castNode(RowTime, cmd->def);
+
+					context->periodStart = rtime->start_time;
+					context->periodEnd = rtime->end_time;
+				}
+				break;
 
 			case AT_AddConstraint:
 			case AT_AddConstraintRecurse:
@@ -3384,6 +3549,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 				 */
 				if (IsA(cmd->def, Constraint))
 				{
+					Constraint *constraint = castNode(Constraint, cmd->def);
+
+					/*
+					 * End time column is added to primary and unique key
+					 * constraint implicitly to make history data and current
+					 * data co-exist.
+					 */
+					if ((rel->rd_att->constr &&
+						 rel->rd_att->constr->has_system_versioning) &&
+						(constraint->contype == CONSTR_PRIMARY || constraint->contype == CONSTR_UNIQUE))
+					{
+						char	   *endColNme;
+
+						endColNme = get_row_end_time_col_name(rel);
+						constraint->keys = lappend(constraint->keys, makeString(endColNme));
+					}
+
 					transformTableConstraint(&cxt, (Constraint *) cmd->def);
 					if (((Constraint *) cmd->def)->contype == CONSTR_FOREIGN)
 						skipValidation = false;
@@ -3551,6 +3733,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
+	if (cxt.hasSystemVersioning)
+	{
+		if (cxt.startTimeColName)
+		{
+			context->hasSystemVersioning = cxt.hasSystemVersioning;
+			context->startTimeColName = cxt.startTimeColName;
+		}
+
+		if (cxt.endTimeColName)
+		{
+			context->hasSystemVersioning = cxt.hasSystemVersioning;
+			context->endTimeColName = cxt.endTimeColName;
+		}
+	}
+
 	/*
 	 * Transfer anything we already have in cxt.alist into save_alist, to keep
 	 * it separate from the output of transformIndexConstraints.
@@ -3582,7 +3779,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		{
 			IndexStmt  *idxstmt = (IndexStmt *) istmt;
 
-			idxstmt = transformIndexStmt(relid, idxstmt, queryString);
+			idxstmt = transformIndexStmt(relid, idxstmt, context->queryString);
 			newcmd = makeNode(AlterTableCmd);
 			newcmd->subtype = OidIsValid(idxstmt->indexOid) ? AT_AddIndexConstraint : AT_AddIndex;
 			newcmd->def = (Node *) idxstmt;
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 875de7ba28..e000a4040a 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -137,6 +137,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	 */
 	switch (cur_token)
 	{
+		case FOR:
+			cur_token_length = 3;
+			break;
 		case NOT:
 			cur_token_length = 3;
 			break;
@@ -188,6 +191,10 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
 	/* Replace cur_token if needed, based on lookahead */
 	switch (cur_token)
 	{
+		case FOR:
+			if (next_token == SYSTEM_TIME)
+				cur_token = FOR_LA;
+			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 54fd6d6fb2..5b123cd1f4 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -910,14 +910,24 @@ rewriteTargetListIU(List *targetList,
 								NameStr(att_tup->attname)),
 						 errdetail("Column \"%s\" is an identity column defined as GENERATED ALWAYS.",
 								   NameStr(att_tup->attname))));
-
-			if (att_tup->attgenerated && new_tle && !apply_default)
+#ifdef NOTUSED
+			if (att_tup->attgenerated == ATTRIBUTE_ROW_START_TIME ||
+				att_tup->attgenerated == ATTRIBUTE_ROW_END_TIME)
 				ereport(ERROR,
-						(errcode(ERRCODE_GENERATED_ALWAYS),
-						 errmsg("column \"%s\" can only be updated to DEFAULT",
-								NameStr(att_tup->attname)),
-						 errdetail("Column \"%s\" is a generated column.",
-								   NameStr(att_tup->attname))));
+					(errcode(ERRCODE_GENERATED_ALWAYS),
+					 errmsg("column \"%s\" cannot be updated",
+							NameStr(att_tup->attname)),
+					 errdetail("Column \"%s\" is a system versioning column.",
+							   NameStr(att_tup->attname))));
+			else if (att_tup->attgenerated && new_tle && !apply_default)
+#endif
+			if (att_tup->attgenerated && new_tle && !apply_default)
+				 ereport(ERROR,
+					(errcode(ERRCODE_GENERATED_ALWAYS),
+					 errmsg("column \"%s\" can only be updated to DEFAULT",
+							NameStr(att_tup->attname)),
+					 errdetail("Column \"%s\" is a generated column.",
+							   NameStr(att_tup->attname))));
 		}
 
 		if (att_tup->attgenerated)
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 27fbf1f3aa..0028d8dac9 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/twophase.h"
 #include "access/xact.h"
@@ -59,7 +60,9 @@
 #include "commands/vacuum.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "parser/parse_utilcmd.h"
+#include "optimizer/plancat.h"
 #include "postmaster/bgwriter.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteRemove.h"
@@ -1253,6 +1256,10 @@ ProcessUtilitySlow(ParseState *pstate,
 					Oid			relid;
 					LOCKMODE	lockmode;
 					ListCell   *cell;
+					ListCell   *s;
+					Relation	rel;
+					bool		hasSystemVersioning = false;
+					TupleDesc	tupdesc;
 
 					/*
 					 * Disallow ALTER TABLE .. DETACH CONCURRENTLY in a
@@ -1281,6 +1288,83 @@ ProcessUtilitySlow(ParseState *pstate,
 					lockmode = AlterTableGetLockLevel(atstmt->cmds);
 					relid = AlterTableLookupRelation(atstmt, lockmode);
 
+					/*
+					 * Change add and remove system versioning to individual
+					 * ADD and DROP column command
+					 */
+					foreach(s, atstmt->cmds)
+					{
+						AlterTableCmd *cmd = (AlterTableCmd *) lfirst(s);
+
+						if (cmd->subtype == AT_AddSystemVersioning)
+						{
+							ColumnDef  *startTimeCol;
+							ColumnDef  *endTimeCol;
+
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							hasSystemVersioning = tupdesc->constr && tupdesc->constr->has_system_versioning;
+							if (hasSystemVersioning)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is already defined with system versioning")));
+
+							/*
+							 * TODO create composite primary key
+							 */
+							if (RelationGetPrimaryKeyIndex(rel) != InvalidOid)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("can not add system versioning to table with primary key")));
+
+							/*
+							 * we use defualt column names for system
+							 * versioning in ALTER TABLE ADD system versioning statement
+							 */
+							startTimeCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_START_NAME);
+							endTimeCol = makeTemporalColumnDef(SYSTEM_VERSIONING_DEFAULT_END_NAME);
+
+							/*
+							 * create alter table add column cmd and append to the end
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(startTimeCol));
+							atstmt->cmds = lappend(atstmt->cmds, (Node *) makeAddColCmd(endTimeCol));
+
+							/*
+							 * delete current listCell
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+
+						}
+
+						if (cmd->subtype == AT_DropSystemVersioning)
+						{
+							rel = relation_open(relid, NoLock);
+							tupdesc = RelationGetDescr(rel);
+							hasSystemVersioning = tupdesc->constr && tupdesc->constr->has_system_versioning;
+							if (!hasSystemVersioning)
+								ereport(ERROR,
+										(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+										 errmsg("table is not defined with system versioning")));
+
+							/*
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
+							 */
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_end_time_col_name(rel)));
+							atstmt->cmds = lappend(atstmt->cmds, makeDropColCmd(get_row_start_time_col_name(rel)));
+
+							/*
+							 * delete current listCell because we don't need
+							 * it anymore
+							 */
+							atstmt->cmds = list_delete_cell(atstmt->cmds, s);
+							relation_close(rel, NoLock);
+						}
+					}
+
 					if (OidIsValid(relid))
 					{
 						AlterTableUtilityContext atcontext;
@@ -1291,6 +1375,11 @@ ProcessUtilitySlow(ParseState *pstate,
 						atcontext.relid = relid;
 						atcontext.params = params;
 						atcontext.queryEnv = queryEnv;
+						atcontext.startTimeColName = NULL;
+						atcontext.endTimeColName = NULL;
+						atcontext.periodEnd = NULL;
+						atcontext.periodStart = NULL;
+						atcontext.hasSystemVersioning = false;
 
 						/* ... ensure we have an event trigger context ... */
 						EventTriggerAlterTableStart(parsetree);
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 13d9994af3..37e08b904b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -515,6 +515,7 @@ RelationBuildTupleDesc(Relation relation)
 													sizeof(TupleConstr));
 	constr->has_not_null = false;
 	constr->has_generated_stored = false;
+	constr->has_system_versioning = false;
 
 	/*
 	 * Form a scan key that selects only user attributes (attnum > 0).
@@ -568,6 +569,16 @@ RelationBuildTupleDesc(Relation relation)
 			constr->has_not_null = true;
 		if (attp->attgenerated == ATTRIBUTE_GENERATED_STORED)
 			constr->has_generated_stored = true;
+		if (attp->attgenerated == ATTRIBUTE_ROW_START_TIME)
+		{
+			constr->sv_starttime = attnum;
+			constr->has_system_versioning = true;
+		}
+		if (attp->attgenerated == ATTRIBUTE_ROW_END_TIME)
+		{
+			constr->sv_endtime = attnum;
+			constr->has_system_versioning = true;
+		}
 		if (attp->atthasdef)
 			ndef++;
 
@@ -664,6 +675,7 @@ RelationBuildTupleDesc(Relation relation)
 	 */
 	if (constr->has_not_null ||
 		constr->has_generated_stored ||
+		constr->has_system_versioning ||
 		ndef > 0 ||
 		attrmiss ||
 		relation->rd_rel->relchecks > 0)
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 9874a77805..7acf4f4205 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -153,6 +153,7 @@ Section: Class 21 - Cardinality Violation
 Section: Class 22 - Data Exception
 
 22000    E    ERRCODE_DATA_EXCEPTION                                         data_exception
+2201H    E    ERRCODE_DATA_EXCEPTION_INVALID_ROW_VERSION					 data_exception_invalid_row_version
 2202E    E    ERRCODE_ARRAY_ELEMENT_ERROR
 # SQL99's actual definition of "array element error" is subscript error
 2202E    E    ERRCODE_ARRAY_SUBSCRIPT_ERROR                                  array_subscript_error
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 90ac445bcd..5c0ac4d36f 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -16083,6 +16083,10 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 						if (tbinfo->attgenerated[j] == ATTRIBUTE_GENERATED_STORED)
 							appendPQExpBuffer(q, " GENERATED ALWAYS AS (%s) STORED",
 											  tbinfo->attrdefs[j]->adef_expr);
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_START_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW START");
+						else if (tbinfo->attgenerated[j] == ATTRIBUTE_ROW_END_TIME)
+							appendPQExpBuffer(q, " GENERATED ALWAYS AS ROW END");
 						else
 							appendPQExpBuffer(q, " DEFAULT %s",
 											  tbinfo->attrdefs[j]->adef_expr);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8333558bda..6d9d91de9e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2237,6 +2237,10 @@ describeOneTableDetails(const char *schemaname,
 				default_str = "generated always as identity";
 			else if (identity[0] == ATTRIBUTE_IDENTITY_BY_DEFAULT)
 				default_str = "generated by default as identity";
+			else if (generated[0] == ATTRIBUTE_ROW_START_TIME)
+				default_str = "generated always as row start";
+			else if (generated[0] == ATTRIBUTE_ROW_END_TIME)
+				default_str = "generated always as row end";
 			else if (generated[0] == ATTRIBUTE_GENERATED_STORED)
 			{
 				default_str = psprintf("generated always as (%s) stored",
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index f45d47aab7..607d080597 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -43,6 +43,9 @@ typedef struct TupleConstr
 	uint16		num_check;
 	bool		has_not_null;
 	bool		has_generated_stored;
+	bool		has_system_versioning;
+	AttrNumber	sv_starttime;	/* which column is starttime */
+	AttrNumber	sv_endtime;		/* which column is endtime */
 } TupleConstr;
 
 /*
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 5c1ec9313e..048bcbd508 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -216,6 +216,9 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_attribute_relid_attnum_index, 2659, AttributeRelidN
 
 #define		  ATTRIBUTE_GENERATED_STORED	's'
 
+#define		  ATTRIBUTE_ROW_START_TIME	'S'
+#define		  ATTRIBUTE_ROW_END_TIME	'E'
+
 #endif							/* EXPOSE_TO_CLIENT_CODE */
 
 #endif							/* PG_ATTRIBUTE_H */
diff --git a/src/include/executor/nodeModifyTable.h b/src/include/executor/nodeModifyTable.h
index 83e2965531..6df221e57d 100644
--- a/src/include/executor/nodeModifyTable.h
+++ b/src/include/executor/nodeModifyTable.h
@@ -22,5 +22,7 @@ extern void ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 extern ModifyTableState *ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags);
 extern void ExecEndModifyTable(ModifyTableState *node);
 extern void ExecReScanModifyTable(ModifyTableState *node);
+extern void ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
+extern bool ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo);
 
 #endif							/* NODEMODIFYTABLE_H */
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 48a7ebfe45..f9f66ad80d 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -105,5 +105,16 @@ extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg,
 extern GroupingSet *makeGroupingSet(GroupingSetKind kind, List *content, int location);
 
 extern VacuumRelation *makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols);
+extern Node *makeAndExpr(Node *lexpr, Node *rexpr, int location);
+extern Node *makeTypeCast(Node *arg, TypeName *typename, int location);
+extern ColumnRef *makeColumnRefFromName(char *colname);
+extern ColumnDef *makeTemporalColumnDef(char *name);
+
+#define SYSTEM_VERSIONING_DEFAULT_START_NAME "starttime"
+#define SYSTEM_VERSIONING_DEFAULT_END_NAME "endtime"
+
+extern AlterTableCmd *makeDropColCmd(char *name);
+extern AlterTableCmd *makeAddColCmd(ColumnDef *coldef);
+
 
 #endif							/* MAKEFUNC_H */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 6a4d82f0a8..9dc95b3fa0 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -490,6 +490,8 @@ typedef enum NodeTag
 	T_PartitionRangeDatum,
 	T_PartitionCmd,
 	T_VacuumRelation,
+	T_RowTime,
+	T_TemporalClause,
 
 	/*
 	 * TAGS FOR REPLICATION GRAMMAR PARSE NODES (replnodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e28248af32..9727681400 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1023,6 +1023,7 @@ typedef struct RangeTblEntry
 	char		relkind;		/* relation kind (see pg_class.relkind) */
 	int			rellockmode;	/* lock level that query requires on the rel */
 	struct TableSampleClause *tablesample;	/* sampling info, or NULL */
+	bool		has_system_versioning;	/* relation has system versioning */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1850,7 +1851,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
 /* ----------------------
- *	Alter Table
+ *     Alter Table
  * ----------------------
  */
 typedef struct AlterTableStmt
@@ -1934,7 +1935,10 @@ typedef enum AlterTableType
 	AT_AddIdentity,				/* ADD IDENTITY */
 	AT_SetIdentity,				/* SET identity column options */
 	AT_DropIdentity,			/* DROP IDENTITY */
-	AT_ReAddStatistics			/* internal to commands/tablecmds.c */
+	AT_ReAddStatistics,			/* internal to commands/tablecmds.c */
+	AT_AddSystemVersioning,     /* ADD system versioning */
+	AT_DropSystemVersioning,    /* DROP system versioning */
+	AT_PeriodColumn             /* Period column */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -2181,6 +2185,7 @@ typedef struct CreateStmt
 	char	   *tablespacename; /* table space to use, or NULL */
 	char	   *accessMethod;	/* table access method */
 	bool		if_not_exists;	/* just do nothing if it already exists? */
+	bool		systemVersioning;	/* true with system versioning */
 } CreateStmt;
 
 /* ----------
@@ -2230,7 +2235,9 @@ typedef enum ConstrType			/* types of constraints */
 	CONSTR_ATTR_DEFERRABLE,		/* attributes for previous constraint node */
 	CONSTR_ATTR_NOT_DEFERRABLE,
 	CONSTR_ATTR_DEFERRED,
-	CONSTR_ATTR_IMMEDIATE
+	CONSTR_ATTR_IMMEDIATE,
+	CONSTR_ROW_START_TIME,
+	CONSTR_ROW_END_TIME
 } ConstrType;
 
 /* Foreign key action codes */
@@ -3686,4 +3693,31 @@ typedef struct DropSubscriptionStmt
 	DropBehavior behavior;		/* RESTRICT or CASCADE behavior */
 } DropSubscriptionStmt;
 
+typedef struct RowTime
+{
+	NodeTag		type;
+	char	   *start_time;		/* Row start time */
+	char	   *end_time;		/* Row end time */
+}			RowTime;
+
+typedef enum TemporalClauseType
+{
+	AS_OF,
+	BETWEEN_T,
+	BETWEEN_SYMMETRIC,
+	BETWEEN_ASYMMETRIC,
+	FROM_TO
+}			TemporalClauseType;
+
+
+typedef struct TemporalClause
+{
+	NodeTag		type;
+	TemporalClauseType kind;
+	Node	   *relation;
+	Node	   *from;			/* starting time */
+	Node	   *to;				/* ending time */
+}			TemporalClause;
+
+
 #endif							/* PARSENODES_H */
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 8d1d6c1b42..dd2d6bce2e 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);
 extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
 
 extern bool has_stored_generated_columns(PlannerInfo *root, Index rti);
+extern char *get_row_start_time_col_name(Relation rel);
+extern char *get_row_end_time_col_name(Relation rel);
+extern void add_history_data_filter(Query *query);
 
 #endif							/* PLANCAT_H */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index f836acf876..10d199ed45 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -312,6 +312,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -406,6 +407,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("system_time", SYSTEM_TIME, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("table", TABLE, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tables", TABLES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("tablesample", TABLESAMPLE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
@@ -454,6 +456,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("version", VERSION_P, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("versioning", VERSIONING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("view", VIEW, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("views", VIEWS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("volatile", VOLATILE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 1500de2dd0..182a22c604 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -201,7 +201,7 @@ struct ParseState
 										 * with FOR UPDATE/FOR SHARE */
 	bool		p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
 									 * type text */
-
+	Node	   *p_tempwhere;	/* temporal where clause so far */
 	QueryEnvironment *p_queryEnv;	/* curr env, incl refs to enclosing env */
 
 	/* Flags telling about things found in the query: */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 1056bf081b..bb80a2a018 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -15,13 +15,14 @@
 #define PARSE_UTILCMD_H
 
 #include "parser/parse_node.h"
+#include "tcop/utility.h"
 
 struct AttrMap;					/* avoid including attmap.h here */
 
 
 extern List *transformCreateStmt(CreateStmt *stmt, const char *queryString);
 extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
-											   const char *queryString,
+											   AlterTableUtilityContext *context,
 											   List **beforeStmts,
 											   List **afterStmts);
 extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 212e9b3280..6caec53805 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -34,6 +34,11 @@ typedef struct AlterTableUtilityContext
 	Oid			relid;			/* OID of ALTER's target table */
 	ParamListInfo params;		/* any parameters available to ALTER TABLE */
 	QueryEnvironment *queryEnv; /* execution environment for ALTER TABLE */
+	bool hasSystemVersioning;	/* true if table has system versioning */
+	char	   *startTimeColName;	/* name of row start time column */
+	char	   *endTimeColName; /* name of row end time column */
+	char	   *periodStart;	/* name of period start time column */
+	char	   *periodEnd;		/* name of period end time column */
 } AlterTableUtilityContext;
 
 /*
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index f4c01006fc..6af99a4467 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -36,6 +36,7 @@ test: fk-partitioned-2
 test: eval-plan-qual
 test: eval-plan-qual-trigger
 test: lock-update-delete
+test: lock-update-delete-system-versioned
 test: lock-update-traversal
 test: inherit-temp
 test: insert-conflict-do-nothing
@@ -96,3 +97,4 @@ test: plpgsql-toast
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
+test: system-versioned
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 7be89178f0..709cc4ba94 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -86,7 +86,7 @@ test: brin_bloom brin_multi
 # ----------
 # Another group of parallel tests
 # ----------
-test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort
+test: create_table_like alter_generic alter_operator misc async dbsize misc_functions sysviews tsrf tid tidscan tidrangescan collate.icu.utf8 incremental_sort system_versioning
 
 # rules cannot run concurrently with any test that creates
 # a view or rule in the public schema
#78Daniel Westermann
dwe@dbi-services.com
In reply to: Simon Riggs (#77)
Re: WIP: System Versioned Temporal Table

Hi,

quick note: The documentation for this patch mentions:

The <literal>starttime</literal> column
+ will be automatically added to the Primary Key of the table.

A quick tests shows that the endtime column is added instead:

postgres=# create table t1 ( a int primary key generated always as identity, b text ) with system versioning;
CREATE TABLE
postgres=# \d t1
Table "public.t1"
Column | Type | Collation | Nullable | Default
-----------+--------------------------+-----------+----------+-------------------------------
a | integer | | not null | generated always as identity
b | text | | |
starttime | timestamp with time zone | | not null | generated always as row start
endtime | timestamp with time zone | | not null | generated always as row end
Indexes:
"t1_pkey" PRIMARY KEY, btree (a, endtime)

Regards
Daniel

#79Jaime Casanova
jcasanov@systemguards.com.ec
In reply to: Simon Riggs (#77)
Re: WIP: System Versioned Temporal Table

Hi,

This doesn't pass tests because of lack of some file. Can we fix that
please and send the patch again?

On Tue, Aug 10, 2021 at 7:20 AM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

On Wed, 14 Jul 2021 at 12:48, vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

OK, so I've rebased the patch against current master to take it to v15.

I've then worked on the patch some myself to make v16 (attached),
adding these things:

* Add code, docs and test to remove the potential anomaly where
endtime < starttime, using the sqlstate 2201H as pointed out by Vik
* Add code and tests to handle multiple changes in a transaction
correctly, according to SQL Std
* Add code and tests to make Foreign Keys behave correctly, according to
SQL Std
* Fixed nascent bug in relcache setup code
* Various small fixes from Japin's review - thanks! I've used
starttime and endtime as default column names
* Additional tests and docs to show that the functionality works with
or without PKs on the table

I am now satisfied that the patch does not have any implementation
anomalies in behavioral design, but it is still a long way short in
code architecture.

There are various aspects still needing work. This is not yet ready
for Commit, but it is appropriate now to ask for initial design
guidance on architecture and code placement by a Committer, so I am
setting this to Ready For Committer, in the hope that we get the
review in SeptCF and a later version can be submitted for later commit
in JanCF. With the right input, this patch is about a person-month
away from being ready, assuming we don't hit any blocking issues.

Major Known Issues
* SQLStd says that it should not be possible to update historical
rows, but those tests show we fail to prevent that and there is code
marked NOT_USED in those areas
* The code is structured poorly around
parse-analyze/rewriter/optimizer/executor and that needs positive
design recommendations, rather than critical review
* Joins currently fail because of the botched way WHERE clauses are
added, resulting in duplicate names
* Views probably don't work, but there are no tests
* CREATE TABLE (LIKE foo) doesn't correctly copy across all features -
test for that added, with test failure accepted for now
* ALTER TABLE is still incomplete and also broken; I suggest we remove
that for the first version of the patch to reduce patch size for an
initial commit.

Minor Known Issues
* Logical replication needs some minor work, no tests yet
* pg_dump support looks like it exists and might work easily, but
there are no tests yet
* Correlation names don't work in FROM clause - shift/reduce errors
from double use of AS
* Add test and code to prevent triggers referencing period cols in the
WHEN clause
* No tests yet to prove you can't set various parameters/settings on
the period time start/end cols
* Code needs some cleanup in a few places
* Not really sure what value is added by
lock-update-delete-system-versioned.spec

* IMHO we should make the PK definition use "endtime DESC", so that
the current version is always the first row found in the PK for any
key, since historical indexes will grow bigger over time

There are no expected issues with integration with MERGE, since SQLStd
explains how to handle that.

Other reviews are welcome.

--
Simon Riggs http://www.EnterpriseDB.com/

--

#80Jaime Casanova
jcasanov@systemguards.com.ec
In reply to: Simon Riggs (#77)
Re: WIP: System Versioned Temporal Table

On Tue, Aug 10, 2021 at 01:20:14PM +0100, Simon Riggs wrote:

On Wed, 14 Jul 2021 at 12:48, vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

OK, so I've rebased the patch against current master to take it to v15.

I've then worked on the patch some myself to make v16 (attached),
adding these things:

Hi Simon,

This one doesn't apply nor compile anymore.
Can we expect a rebase soon?

--
Jaime Casanova
Director de Servicios Profesionales
SystemGuards - Consultores de PostgreSQL

#81Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Jaime Casanova (#80)
Re: WIP: System Versioned Temporal Table

On Fri, 10 Sept 2021 at 19:30, Jaime Casanova
<jcasanov@systemguards.com.ec> wrote:

On Tue, Aug 10, 2021 at 01:20:14PM +0100, Simon Riggs wrote:

On Wed, 14 Jul 2021 at 12:48, vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

OK, so I've rebased the patch against current master to take it to v15.

I've then worked on the patch some myself to make v16 (attached),
adding these things:

Hi Simon,

This one doesn't apply nor compile anymore.
Can we expect a rebase soon?

Hi Jaime,

Sorry for not replying.

Yes, I will rebase again to assist the design input I have requested.
Please expect that on Sep 15.

Cheers

--
Simon Riggs http://www.EnterpriseDB.com/

#82Corey Huinker
corey.huinker@gmail.com
In reply to: Simon Riggs (#81)
Re: WIP: System Versioned Temporal Table

On Sun, Sep 12, 2021 at 12:02 PM Simon Riggs <simon.riggs@enterprisedb.com>
wrote:

On Fri, 10 Sept 2021 at 19:30, Jaime Casanova
<jcasanov@systemguards.com.ec> wrote:

On Tue, Aug 10, 2021 at 01:20:14PM +0100, Simon Riggs wrote:

On Wed, 14 Jul 2021 at 12:48, vignesh C <vignesh21@gmail.com> wrote:

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

OK, so I've rebased the patch against current master to take it to v15.

I've then worked on the patch some myself to make v16 (attached),
adding these things:

Hi Simon,

This one doesn't apply nor compile anymore.
Can we expect a rebase soon?

Hi Jaime,

Sorry for not replying.

Yes, I will rebase again to assist the design input I have requested.
Please expect that on Sep 15.

Cheers

--
Simon Riggs http://www.EnterpriseDB.com/

I've been interested in this patch, especially with how it will
interoperate with the work on application periods in
/messages/by-id/CA+renyUApHgSZF9-nd-a0+OPGharLQLO=mDHcY4_qQ0+noCUVg@mail.gmail.com
. I've written up a few observations and questions in that thread, and
wanted to do the same here, as the questions are a bit narrower but no less
interesting.

1. Much of what I have read about temporal tables seemed to imply or almost
assume that system temporal tables would be implemented as two actual
separate tables. Indeed, SQLServer appears to do it that way [1]https://docs.microsoft.com/en-us/azure/azure-sql/temporal-tables with
syntax like

WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.WebsiteUserInfoHistory));

Q 1.1. Was that implementation considered and if so, what made this
implementation more appealing?

2. The endtime column constraint which enforces GENERATED ALWAYS AS ROW END
seems like it would have appeal outside of system versioning, as a lot of
tables have a last_updated column, and it would be nice if it could handle
itself and not rely on fallible application programmers or require trigger
overhead.

Q 2.1. Is that something we could break out into its own patch?

3. It is possible to have bi-temporal tables (having both a system_time
period and a named application period) as described in [2]https://cs.ulb.ac.be/public/_media/teaching/infoh415/tempfeaturessql2011.pdf, the specific
example was

CREATE TABLE Emp(
ENo INTEGER,
EStart DATE,
EEnd DATE,
EDept INTEGER,
PERIOD FOR EPeriod (EStart, EEnd),
Sys_start TIMESTAMP(12) GENERATED ALWAYS AS ROW START,
Sys_end TIMESTAMP(12) GENERATED ALWAYS AS ROW END,
EName VARCHAR(30),
PERIOD FOR SYSTEM_TIME(Sys_start, Sys_end),
PRIMARY KEY (ENo, EPeriod WITHOUT OVERLAPS),
FOREIGN KEY (Edept, PERIOD EPeriod) REFERENCES Dept (DNo, PERIOD DPeriod)
) WITH SYSTEM VERSIONING

What's interesting here is that in the case of a bitemporal table, it was
the application period that got the defined primary key. The paper went on
that only the _current_ rows of the table needed to be unique for, as it
wasn't possible to create rows with past system temporal values. This
sounds like a partial index to me, and luckily postgres can do referential
integrity on any unique index, not just primary keys. In light of the
assumption of a history side-table, I guess I shouldn't be surprised.

Q 3.1. Do you think that it would be possible to implement system
versioning with just a unique index?
Q 3.2. Are there any barriers to using a partial index as the hitch for a
foreign key? Would it be any different than the implied "and endtime =
'infinity'" that's already being done?

4. The choice of 'infinity' seemed like a good one initially - it's not
null so it can be used in a primary key, it's not some hackish magic date
like SQLServer's '9999-12-31 23:59:59.9999999'. However, it may not jibe as
well with application versioning, which is built very heavily upon range
types (and multirange types), and those ranges are capable of saying that a
record is valid for an unbounded amount of time in the future, that's
represented with NULL, not infinity. It could be awkward to have the system
endtime be infinity and the application period endtime be NULL.

Q 4.1. Do you have any thoughts about how to resolve this?

5. System versioning columns were indicated with additional columns in
pg_attribute.

Q 5.1. If you were to implement application versioning yourself, would you
just add additional columns to pg_attribute for those?

6. The current effort to implement application versioning creates an
INFORMATION_SCHEMA view called PERIODS. I wasn't aware of this one before
but there seems to be precedent for it existing.

Q 6.1. Would system versioning belong in such a view?

7. This is a trifle, but the documentation is inconsistent about starttime
vs StartTime and endtime vs EndTime.

8. Overall, I'm really excited about both of these efforts, and I'm looking
for ways to combine the efforts, perhaps starting with a patch that
implements the SQL syntax, but raises not-implemented errors, and each
effort could then build off of that.

[1]: https://docs.microsoft.com/en-us/azure/azure-sql/temporal-tables
[2]: https://cs.ulb.ac.be/public/_media/teaching/infoh415/tempfeaturessql2011.pdf
https://cs.ulb.ac.be/public/_media/teaching/infoh415/tempfeaturessql2011.pdf

#83Corey Huinker
corey.huinker@gmail.com
In reply to: Corey Huinker (#82)
Re: WIP: System Versioned Temporal Table

1. Much of what I have read about temporal tables seemed to imply or
almost assume that system temporal tables would be implemented as two
actual separate tables. Indeed, SQLServer appears to do it that way [1]
with syntax like

WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.WebsiteUserInfoHistory));

Q 1.1. Was that implementation considered and if so, what made this
implementation more appealing?

I've been digging some more on this point, and I've reached the conclusion
that a separate history table is the better implementation. It would make
the act of removing system versioning into little more than a DROP TABLE,
plus adjusting the base table to reflect that it is no longer system
versioned.

What do you think of this method:

1. The regular table remains unchanged, but a pg_class attribute named
"relissystemversioned" would be set to true
2. I'm unsure if the standard allows dropping a column from a table while
it is system versioned, and the purpose behind system versioning makes me
believe the answer is a strong "no" and requiring DROP COLUMN to fail
on relissystemversioned = 't' seems pretty straightforward.
3. The history table would be given a default name of $FOO_history (space
permitting), but could be overridden with the history_table option.
4. The history table would have relkind = 'h'
5. The history table will only have rows that are not current, so it is
created empty.
6. As such, the table is effectively append-only, in a way that vacuum can
actually leverage, and likewise the fill factor of such a table should
never be less than 100.
7. The history table could only be updated only via system defined triggers
(insert,update,delete, alter to add columns), or row migration similar to
that found in partitioning. It seems like this would work as the two tables
working as partitions of the same table, but presently we can't have
multi-parent partitions.
8. The history table would be indexed the same as the base table, except
that all unique indexes would be made non-unique, and an index of pk +
start_time + end_time would be added
9. The primary key of the base table would remain the existing pk vals, and
would basically function normally, with triggers to carry forth changes to
the history table. The net effect of this is that the end_time value of all
rows in the main table would always be the chosen "current" value
(infinity, null, 9999-12-31, etc) and as such might not actually _need_ to
be stored.
10. Queries that omit the FOR SYSTEM_TIME clause, as well as ones that use
FOR SYSTEM_TIME AS OF CURRENT_TIMESTAMP, would simply use the base table
directly with no quals to add.
11. Queries that use FOR SYSTEM_TIME and not FOR SYSTEM_TIME AS
OF CURRENT_TIMESTAMP, then the query would do a union of the base table and
the history table with quals applied to both.
12. It's a fair question whether the history table would be something that
could be queried directly. I'm inclined to say no, because that allows for
things like SELECT FOR UPDATE, which of course we'd have to reject.
13. If a history table is directly referenceable, then SELECT permission
can be granted or revoked as normal, but all insert/update/delete/truncate
options would raise an error.
14. DROP SYSTEM VERSIONING from a table would be quite straightforward -
the history table would be dropped along with the triggers that reference
it, setting relissystemversioned = 'f' on the base table.

I think this would have some key advantages:

1. MVCC bloat is no worse than it was before.
2. No changes whatsoever to referential integrity.
3. DROP SYSTEM VERSIONING becomes an O(1) operation.

Thoughts?

I'm going to be making a similar proposal to the people doing the
application time effort, but I'm very much hoping that we can reach some
consensus and combine efforts.

#84Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Corey Huinker (#83)
Re: WIP: System Versioned Temporal Table

On Sun, 19 Sept 2021 at 01:16, Corey Huinker <corey.huinker@gmail.com> wrote:

1. Much of what I have read about temporal tables seemed to imply or almost assume that system temporal tables would be implemented as two actual separate tables. Indeed, SQLServer appears to do it that way [1] with syntax like

WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.WebsiteUserInfoHistory));

Q 1.1. Was that implementation considered and if so, what made this implementation more appealing?

I've been digging some more on this point, and I've reached the conclusion that a separate history table is the better implementation. It would make the act of removing system versioning into little more than a DROP TABLE, plus adjusting the base table to reflect that it is no longer system versioned.

Thanks for giving this a lot of thought. When you asked the question
the first time you hadn't discussed how that might work, but now we
have something to discuss.

10. Queries that omit the FOR SYSTEM_TIME clause, as well as ones that use FOR SYSTEM_TIME AS OF CURRENT_TIMESTAMP, would simply use the base table directly with no quals to add.
11. Queries that use FOR SYSTEM_TIME and not FOR SYSTEM_TIME AS OF CURRENT_TIMESTAMP, then the query would do a union of the base table and the history table with quals applied to both.

14. DROP SYSTEM VERSIONING from a table would be quite straightforward - the history table would be dropped along with the triggers that reference it, setting relissystemversioned = 'f' on the base table.

I think this would have some key advantages:

1. MVCC bloat is no worse than it was before.

The number of row versions stored in the database is the same for
both, just it would be split across two tables in this form.

2. No changes whatsoever to referential integrity.

The changes were fairly minor, but I see your thinking about indexes
as a simplification.

3. DROP SYSTEM VERSIONING becomes an O(1) operation.

It isn't top of mind to make this work well. The whole purpose of the
history is to keep it, not to be able to drop it quickly.

Thoughts?

There are 3 implementation routes that I see, so let me explain so
that others can join the discussion.

1. Putting all data in one table. This makes DROP SYSTEM VERSIONING
effectively impossible. It requires access to the table to be
rewritten to add in historical quals for non-historical access and it
requires some push-ups around indexes. (The current patch adds the
historic quals by kludging the parser, which is wrong place, since it
doesn't work for joins etc.. However, given that issue, the rest seems
to follow on naturally).

2. Putting data in a side table. This makes DROP SYSTEM VERSIONING
fairly trivial, but it complicates many DDL commands (please make a
list?) and requires the optimizer to know about this and cater to it,
possibly complicating plans. Neither issue is insurmountable, but it
becomes more intrusive.

The current patch could go in either of the first 2 directions with
further work.

3. Let the Table Access Method handle it. I call this out separately
since it avoids making changes to the rest of Postgres, which might be
a good thing, with the right TAM implementation.

My preferred approach would be to do this "for free" in the table
access method, but we're a long way from this in terms of actual
implementation. When Corey suggested earlier that we just put the
syntax in there, this was the direction I was thinking.

After waiting a day since I wrote the above, I think we should go with
(2) as Corey suggests, at least for now, and we can always add (3)
later.

--
Simon Riggs http://www.EnterpriseDB.com/

#85Hannu Krosing
hannuk@google.com
In reply to: Simon Riggs (#84)
Re: WIP: System Versioned Temporal Table

A side table has the nice additional benefit that we can very easily
version the *table structure* so when we ALTER TABLE and the table
structure changes we just make a new side table with now-currents
structure.

Also we may want different set of indexes on historic table(s) for
whatever reason

And we may even want to partition history tables for speed, storage
cost or just to drop very ancient history

-----
Hannu Krosing
Google Cloud - We have a long list of planned contributions and we are hiring.
Contact me if interested.

On Sun, Sep 19, 2021 at 8:32 PM Simon Riggs
<simon.riggs@enterprisedb.com> wrote:

Show quoted text

On Sun, 19 Sept 2021 at 01:16, Corey Huinker <corey.huinker@gmail.com> wrote:

1. Much of what I have read about temporal tables seemed to imply or almost assume that system temporal tables would be implemented as two actual separate tables. Indeed, SQLServer appears to do it that way [1] with syntax like

WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.WebsiteUserInfoHistory));

Q 1.1. Was that implementation considered and if so, what made this implementation more appealing?

I've been digging some more on this point, and I've reached the conclusion that a separate history table is the better implementation. It would make the act of removing system versioning into little more than a DROP TABLE, plus adjusting the base table to reflect that it is no longer system versioned.

Thanks for giving this a lot of thought. When you asked the question
the first time you hadn't discussed how that might work, but now we
have something to discuss.

10. Queries that omit the FOR SYSTEM_TIME clause, as well as ones that use FOR SYSTEM_TIME AS OF CURRENT_TIMESTAMP, would simply use the base table directly with no quals to add.
11. Queries that use FOR SYSTEM_TIME and not FOR SYSTEM_TIME AS OF CURRENT_TIMESTAMP, then the query would do a union of the base table and the history table with quals applied to both.

14. DROP SYSTEM VERSIONING from a table would be quite straightforward - the history table would be dropped along with the triggers that reference it, setting relissystemversioned = 'f' on the base table.

I think this would have some key advantages:

1. MVCC bloat is no worse than it was before.

The number of row versions stored in the database is the same for
both, just it would be split across two tables in this form.

2. No changes whatsoever to referential integrity.

The changes were fairly minor, but I see your thinking about indexes
as a simplification.

3. DROP SYSTEM VERSIONING becomes an O(1) operation.

It isn't top of mind to make this work well. The whole purpose of the
history is to keep it, not to be able to drop it quickly.

Thoughts?

There are 3 implementation routes that I see, so let me explain so
that others can join the discussion.

1. Putting all data in one table. This makes DROP SYSTEM VERSIONING
effectively impossible. It requires access to the table to be
rewritten to add in historical quals for non-historical access and it
requires some push-ups around indexes. (The current patch adds the
historic quals by kludging the parser, which is wrong place, since it
doesn't work for joins etc.. However, given that issue, the rest seems
to follow on naturally).

2. Putting data in a side table. This makes DROP SYSTEM VERSIONING
fairly trivial, but it complicates many DDL commands (please make a
list?) and requires the optimizer to know about this and cater to it,
possibly complicating plans. Neither issue is insurmountable, but it
becomes more intrusive.

The current patch could go in either of the first 2 directions with
further work.

3. Let the Table Access Method handle it. I call this out separately
since it avoids making changes to the rest of Postgres, which might be
a good thing, with the right TAM implementation.

My preferred approach would be to do this "for free" in the table
access method, but we're a long way from this in terms of actual
implementation. When Corey suggested earlier that we just put the
syntax in there, this was the direction I was thinking.

After waiting a day since I wrote the above, I think we should go with
(2) as Corey suggests, at least for now, and we can always add (3)
later.

--
Simon Riggs http://www.EnterpriseDB.com/

#86Corey Huinker
corey.huinker@gmail.com
In reply to: Simon Riggs (#84)
Re: WIP: System Versioned Temporal Table

Thanks for giving this a lot of thought. When you asked the question
the first time you hadn't discussed how that might work, but now we
have something to discuss.

My ultimate goal is to unify this effort with the application period
effort. Step 1 in that was to understand what each was doing and why they
were doing it. If you check out the other thread, you'll see a highly
similar message that I sent over there.

There are 3 implementation routes that I see, so let me explain so
that others can join the discussion.

1. Putting all data in one table. This makes DROP SYSTEM VERSIONING
effectively impossible. It requires access to the table to be
rewritten to add in historical quals for non-historical access and it
requires some push-ups around indexes. (The current patch adds the
historic quals by kludging the parser, which is wrong place, since it
doesn't work for joins etc.. However, given that issue, the rest seems
to follow on naturally).

2. Putting data in a side table. This makes DROP SYSTEM VERSIONING
fairly trivial, but it complicates many DDL commands (please make a
list?) and requires the optimizer to know about this and cater to it,
possibly complicating plans. Neither issue is insurmountable, but it
becomes more intrusive.

The current patch could go in either of the first 2 directions with
further work.

3. Let the Table Access Method handle it. I call this out separately
since it avoids making changes to the rest of Postgres, which might be
a good thing, with the right TAM implementation.

I'd like to hear more about this idea number 3.

I could see value in allowing the history table to be a foreign table,
perhaps writing to csv/parquet/whatever files, and that sort of setup could
be persuasive to a regulator who wants extra-double-secret-proof that
auditing cannot be tampered with. But with that we'd have to give up the
relkind idea, which itself was going to be a cheap way to prevent updates
outside of the system triggers.

#87Corey Huinker
corey.huinker@gmail.com
In reply to: Hannu Krosing (#85)
Re: WIP: System Versioned Temporal Table

On Sun, Sep 19, 2021 at 3:12 PM Hannu Krosing <hannuk@google.com> wrote:

A side table has the nice additional benefit that we can very easily
version the *table structure* so when we ALTER TABLE and the table
structure changes we just make a new side table with now-currents
structure.

It's true that would allow for perfect capture of changes to the table
structure, but how would you query the thing?

If a system versioned table was created with a column foo that is type
float, and then we dropped that column, how would we ever query what the
value of foo was in the past?

Would the columns returned from SELECT * change based on the timeframe
requested?

If we then later added another column that happened to also be named foo
but now was type JSONB, would we change the datatype returned based on the
time period being queried?

Is the change in structure a system versioning which itself must be
captured?

Also we may want different set of indexes on historic table(s) for
whatever reason

+1

And we may even want to partition history tables for speed, storage
cost or just to drop very ancient history

+1

#88Hannu Krosing
hannuk@google.com
In reply to: Corey Huinker (#87)
Re: WIP: System Versioned Temporal Table

On Mon, Sep 20, 2021 at 7:09 AM Corey Huinker <corey.huinker@gmail.com> wrote:

On Sun, Sep 19, 2021 at 3:12 PM Hannu Krosing <hannuk@google.com> wrote:

A side table has the nice additional benefit that we can very easily
version the *table structure* so when we ALTER TABLE and the table
structure changes we just make a new side table with now-currents
structure.

It's true that would allow for perfect capture of changes to the table structure, but how would you query the thing?

If a system versioned table was created with a column foo that is type float, and then we dropped that column, how would we ever query what the value of foo was in the past?

We can query that thing only in tables AS OF the time when the column
was still there.

We probably could get away with pretending the dropped columns to be
NULL in newer versions (and the versions before the column was added)

Even more tricky case would be changing the column data type.

Would the columns returned from SELECT * change based on the timeframe requested?

If we want to emulate real table history, then it should.

But the * thing was not really specified well even for original
PostgreSQL inheritance.

Maybe we could do SELECT (* AS OF 'yesterday afternoon'::timestamp) FROM ... :)

If we then later added another column that happened to also be named foo but now was type JSONB, would we change the datatype returned based on the time period being queried?

Many databases do allow returning multiple result sets, and actually
the PostgreSQL wire *protocol* also theoretically supports this, just
that it is not supported by any current client library.

With current libraries it would be possible to return a dynamic
version of row_to_json(t.*) which changes based on actual historical
table structure

Is the change in structure a system versioning which itself must be captured?

We do capture it (kind of) for logical decoding. That is, we decode
according to the structure in effect at the time of row creation,
though we currently miss the actual DDL itself.

So there is a lot to figure out and define, but at least storing the
history in a separate table gives a good foundation to build upon.

-----
Hannu Krosing
Google Cloud - We have a long list of planned contributions and we are hiring.
Contact me if interested.

#89Daniel Gustafsson
daniel@yesql.se
In reply to: Simon Riggs (#84)
Re: WIP: System Versioned Temporal Table

On 19 Sep 2021, at 20:32, Simon Riggs <simon.riggs@enterprisedb.com> wrote:

My preferred approach would be to do this "for free" in the table
access method, but we're a long way from this in terms of actual
implementation. When Corey suggested earlier that we just put the
syntax in there, this was the direction I was thinking.

After waiting a day since I wrote the above, I think we should go with
(2) as Corey suggests, at least for now, and we can always add (3)
later.

This patch no longer applies, are there plans on implementing the approaches
discussed above, or should we close this entry and open a new one when a
freshly baked pathed is ready?

--
Daniel Gustafsson https://vmware.com/

#90Vik Fearing
vik@postgresfriends.org
In reply to: Daniel Gustafsson (#89)
Re: WIP: System Versioned Temporal Table

On 11/15/21 10:47 AM, Daniel Gustafsson wrote:

On 19 Sep 2021, at 20:32, Simon Riggs <simon.riggs@enterprisedb.com> wrote:

My preferred approach would be to do this "for free" in the table
access method, but we're a long way from this in terms of actual
implementation. When Corey suggested earlier that we just put the
syntax in there, this was the direction I was thinking.

After waiting a day since I wrote the above, I think we should go with
(2) as Corey suggests, at least for now, and we can always add (3)
later.

This patch no longer applies, are there plans on implementing the approaches
discussed above, or should we close this entry and open a new one when a
freshly baked pathed is ready?

I spent a lot of time a while ago trying to fix this patch (not just
rebase it), and I think it should just be rejected, unfortunately.

The design decisions are just too flawed, and it conflates system_time
periods with system versioning which is very wrong.
--
Vik Fearing

#91Simon Riggs
simon.riggs@enterprisedb.com
In reply to: Daniel Gustafsson (#89)
Re: WIP: System Versioned Temporal Table

On Mon, 15 Nov 2021 at 09:47, Daniel Gustafsson <daniel@yesql.se> wrote:

On 19 Sep 2021, at 20:32, Simon Riggs <simon.riggs@enterprisedb.com> wrote:

My preferred approach would be to do this "for free" in the table
access method, but we're a long way from this in terms of actual
implementation. When Corey suggested earlier that we just put the
syntax in there, this was the direction I was thinking.

After waiting a day since I wrote the above, I think we should go with
(2) as Corey suggests, at least for now, and we can always add (3)
later.

This patch no longer applies, are there plans on implementing the approaches
discussed above, or should we close this entry and open a new one when a
freshly baked pathed is ready?

As I mentioned upthread, there are at least 2 different ways forward
(1) and (2), both of which need further work. I don't think that
additional work is impossible, but it is weeks of work, not days and
it needs to be done in collaboration with other thoughts on other
threads Corey refers to.

I have no plans on taking this patch further, but will give some help
to anyone that wishes to do that.

I suggest we Return with Feedback.

--
Simon Riggs http://www.EnterpriseDB.com/

#92Daniel Gustafsson
daniel@yesql.se
In reply to: Simon Riggs (#91)
Re: WIP: System Versioned Temporal Table

On 15 Nov 2021, at 11:50, Simon Riggs <simon.riggs@enterprisedb.com> wrote:

I have no plans on taking this patch further, but will give some help
to anyone that wishes to do that.

I suggest we Return with Feedback.

Fair enough, done that way.

--
Daniel Gustafsson https://vmware.com/

#93Trevor Gross
t.gross35@gmail.com
In reply to: Daniel Gustafsson (#92)
Re: WIP: System Versioned Temporal Table

Chiming in as a user, not so much a developer - I've been using system
versioned tables in MariaDB for about half a year now, would just like
to add some feedback about what they did right and wrong and how PG
could learn from their mistakes & successes.

2. Putting data in a side table. This makes DROP SYSTEM VERSIONING
fairly trivial, but it complicates many DDL commands (please make a
list?) and requires the optimizer to know about this and cater to it,
possibly complicating plans. Neither issue is insurmountable, but it
becomes more intrusive.

I'd vouch for this being the way to go; you completely sidestep issues
like partitioning, unique constraints, optimization, etc. Especially
true when 90% of the time, SELECTs will only be looking at
currently-active data. MDB seems to have gone with the single-table
approach (unless you partition) and I've run into a bug where I can't
add a unique constraint because historical data fails.

#### System versioning & Application versioning
I saw that there is an intent to harmonize system versioning with
application versioning. Haven't read the AV thread so not positive if
that meant intending to split tables by application versioning and
system versioning both: to me it seems like maybe it would be good to
use a separate table for SV, but keep AV in the same table. Reasons
include:

- ISO states only one AV config per table, but there's no reason this
always has to be the case; maybe you're storing products that are
active for a period of time, EOL for a period of time, and obsolete
for a period of time. If ISO sometime decides >1 AV config is OK,
there would be a mess trying to split that into tables.
- DB users who are allowed to change AV items likely won't be allowed
to rewrite history by changing SV items. My proposed schema would keep
these separate.
- Table schemas change, and all (SV active) AV items would logically
need to fit the active schema or be updated to do so. Different story
for SV, nothing there should ever need to be changed.
- Partitioning for AV tables isn't as clear as with SV and is likely
better to be user-defined

Sorry for acronyms, SV=system versioning, AV=application versioning

In general, I think AV should be treated literally as extra rows in
the main DB, plus the extra PK element and shortcut functions. SV
though, needs to have a lot more nuance.

#### ALTER TABLE
On to ideas about how ALTER TABLE could work. I don't think the
question was ever answered "Do schema changes need to be tracked?" I'm
generally in favor of saying that it should be possible to recreate
the table exactly as it was, schema and all, at a specific period of
time (perhaps for a view) using a fancy combination of SELECT ... AS
and such - but it doesn't need to be straightforward. In any case, no
data should ever be deleted by ALTER TABLE. As someone pointed out
earlier, speed and storage space of ALTER TABLE are likely low
considerations for system versioned tables.

- ADD COLUMN easy, add the column to both the current and historical
table, all null in historical
- DROP COLUMN delete the column from the current table. Historical is
difficult, because what happens if a new column with the same name is
added? Maybe `DROP COLUMN col1` would rename col1 to _col1_1642929683
(epoch time) in the historical table or something like that.
- RENAME COLUMN is a bit tricky too - from a usability standpoint, the
historical table should be renamed as well. A quick thought is maybe
`RENAME col1 TO new_name` would perform the rename in the historical
table, but also create _col1_1642929683 as an alias to new_name to
track that there was a change. I don't think there would be any name
violations in the history table because there would never be a column
name in history that isn't in current (because of the rename described
with DROP).
- Changing column data type: ouch. This needs to be mainly planned for
cases where data types are incompatible, possibly optimized for times
when they are compatible. Seems like another _col1_1642929683 rename
would be in order, and a new col1 created with the new datatype, and a
historical SELECT would automatically merge the two. Possible
optimization: if the old type fits into the new type, just change the
data type in history and make _col1_1642929683 an alias to it.
- Change defaults, nullability, constraints, etc: I think these can
safely be done for the current table only. Realistically, historical
tables could probably skip all checks, always (except their tuple PK),
since trying to enforce them would just be opening the door to bugs.
Trying to think of any times this isn't true.
- FKs: I'm generally in the same boat as above, thinking that these
don't need to affect historical tables. Section 2.5 in the paper I
link below discusses period joins, but I don't think any special
behavior is needed for now. Perhaps references could be kept in
history but not enforced
- Changing PK / adding/removing more columns to PK: Annoying and not
easily dealt with. Maybe just disallow
- Triggers: no affect on historical
- DROP TABLE bye bye, history & all

Things like row level security add extra complication but can probably
be disregarded. Maybe just have a `select history` permission or
similar.

An interesting idea could be to automatically add system versioning to
information_schema whenever it is added to a table. This would provide
a way to easily query historical DDL. It would also help solve how to
keep historical FKs. This would make it possible to perfectly recreate
system versioned parts of your database at any period of time, schema
and data both.

#### Partitioning
Allowing for partitioning and automatic rotation seems like a good
idea, should be possible with current syntax but maybe worth adding
some shortcuts like maria has.

#### Permissions
- MDB has the new 'delete history' schema privilege that defines who
can delete historical data before a certain time or drop system
versioning, seems like a good idea to implement. They also require
`@@system_versioning_alter_history=keep;` to be set before doing
anything ALTER TABLE; doesn't do much outside of serving as a reminder
that changing system versioned tables can be dangerous.¯\_(ツ)_/¯
- This part sucks and goes against everything ISO is going for, but
IMO there needs to be a way to insert/update/delete historical data.
Maybe there needs to be a new superduperuser role to do it and you
need to type the table name backwards to verify you want to insert,
but situations like data migration, fixing incorrectly stored data, or
removing accidental sensitive information demand it. This isn't a
priority though, and basic system versioning can be shipped without
it.

#### Misc
- Seems like a good idea to include MDB's option to exclude columns
from versioning (`WITHOUT SYSTEM VERSIONING` as a column argument).
This is relatively nuanced and I'm not sure if it's officially part of
ISO, but probably helpful for frequently updating small data in rows
with BLOBs. Easy enough to implement, just forget the column in the
historical table.
- I thought I saw somewhere that somebody was discussing adding both
row_start and row_end to the PK. Why would this be? Row_end should be
all that's needed to keep unique, but maybe I misread.

#### Links
- I haven't seen it linked here yet but this paper does a phenomenal
deep dive into SV and AV
https://sigmodrecord.org/publications/sigmodRecord/1209/pdfs/07.industry.kulkarni.pdf
- It's not perfect, but MDB's system versioning is pretty well thought
out. You get a good idea of their thought process going through this
page, worth a read
https://mariadb.com/kb/en/system-versioned-tables/#excluding-columns-from-versioning

#### Finally, the end
There's a heck of a lot of thought that could go into this thing,
probably worth making sure there's a formal agreement on what to be
done before coding starts (PGEP for postgres enhancement proposal,
like PEP? Not sure if something like that exists but it probably
should.). Large parts of the existing patch could likely be reused for
whatever is decided.

Best,
Trevor

Show quoted text

On Sun, Jan 23, 2022 at 2:47 AM Daniel Gustafsson <daniel@yesql.se> wrote:

On 15 Nov 2021, at 11:50, Simon Riggs <simon.riggs@enterprisedb.com> wrote:

I have no plans on taking this patch further, but will give some help
to anyone that wishes to do that.

I suggest we Return with Feedback.

Fair enough, done that way.

--
Daniel Gustafsson https://vmware.com/

#94Corey Huinker
corey.huinker@gmail.com
In reply to: Trevor Gross (#93)
Re: WIP: System Versioned Temporal Table

2. Putting data in a side table. This makes DROP SYSTEM VERSIONING
fairly trivial, but it complicates many DDL commands (please make a
list?) and requires the optimizer to know about this and cater to it,
possibly complicating plans. Neither issue is insurmountable, but it
becomes more intrusive.

I'd vouch for this being the way to go; you completely sidestep issues
like partitioning, unique constraints, optimization, etc. Especially
true when 90% of the time, SELECTs will only be looking at
currently-active data. MDB seems to have gone with the single-table
approach (unless you partition) and I've run into a bug where I can't
add a unique constraint because historical data fails.

#### System versioning & Application versioning
I saw that there is an intent to harmonize system versioning with
application versioning. Haven't read the AV thread so not positive if
that meant intending to split tables by application versioning and
system versioning both: to me it seems like maybe it would be good to
use a separate table for SV, but keep AV in the same table. Reasons
include:

The proposed AV uses just one table.

- ISO states only one AV config per table, but there's no reason this
always has to be the case; maybe you're storing products that are
active for a period of time, EOL for a period of time, and obsolete
for a period of time. If ISO sometime decides >1 AV config is OK,
there would be a mess trying to split that into tables.

The proposed AV (so far) allows for that.

- DB users who are allowed to change AV items likely won't be allowed
to rewrite history by changing SV items. My proposed schema would keep
these separate.
- Table schemas change, and all (SV active) AV items would logically
need to fit the active schema or be updated to do so. Different story
for SV, nothing there should ever need to be changed.

Yeah, there's a mess (which you state below) about what happens if you
create a table and then rename a column, or drop a column and add a
same-named column back of another type at a later date, etc. In theory,
this means that the valid set of columns and their types changes according
to the time range specified. I may not be remembering correctly, but Vik
stated that the SQL spec seemed to imply that you had to track all those
things.

- Partitioning for AV tables isn't as clear as with SV and is likely
better to be user-defined

So this was something I was asking various parties about at PgConf NYC just
a few weeks ago. I am supposing that the main reason for SV is a regulatory
concern, what tolerance to regulators have for the ability to manipulate
the SV side-table? Is it possible to directly insert rows into one? If not,
then moving rows into a new partition becomes impossible, and you'd be
stuck with the partitioning strategy (if any) that you defined at SV
creation time.

The feedback I got was "well, you're already a superuser, if a regulator
had a problem with that then they would have required that the SV table's
storage be outside the server, either a foreign table, a csv foreign data
wrapper of some sort, or a trigger writing to a non-db storage (which
wouldn't even need SV).

From that, I concluded that every single AV partition would have it's own
SV table, which could in turn be partitioned. In a sense, it might be
helpful to think of the SV tables as partitions of the main table, and the
period definition would effectively be the constraint that prunes the SV
partition.

Sorry for acronyms, SV=system versioning, AV=application versioning

In general, I think AV should be treated literally as extra rows in
the main DB, plus the extra PK element and shortcut functions. SV
though, needs to have a lot more nuance.

#### ALTER TABLE
On to ideas about how ALTER TABLE could work. I don't think the
question was ever answered "Do schema changes need to be tracked?" I'm
generally in favor of saying that it should be possible to recreate
the table exactly as it was, schema and all, at a specific period of
time (perhaps for a view) using a fancy combination of SELECT ... AS
and such - but it doesn't need to be straightforward. In any case, no
data should ever be deleted by ALTER TABLE. As someone pointed out
earlier, speed and storage space of ALTER TABLE are likely low
considerations for system versioned tables.

- ADD COLUMN easy, add the column to both the current and historical
table, all null in historical
- DROP COLUMN delete the column from the current table. Historical is
difficult, because what happens if a new column with the same name is
added? Maybe `DROP COLUMN col1` would rename col1 to _col1_1642929683
(epoch time) in the historical table or something like that.
- RENAME COLUMN is a bit tricky too - from a usability standpoint, the
historical table should be renamed as well. A quick thought is maybe
`RENAME col1 TO new_name` would perform the rename in the historical
table, but also create _col1_1642929683 as an alias to new_name to
track that there was a change. I don't think there would be any name
violations in the history table because there would never be a column
name in history that isn't in current (because of the rename described
with DROP).
- Changing column data type: ouch. This needs to be mainly planned for
cases where data types are incompatible, possibly optimized for times
when they are compatible. Seems like another _col1_1642929683 rename
would be in order, and a new col1 created with the new datatype, and a
historical SELECT would automatically merge the two. Possible
optimization: if the old type fits into the new type, just change the
data type in history and make _col1_1642929683 an alias to it.
- Change defaults, nullability, constraints, etc: I think these can
safely be done for the current table only. Realistically, historical
tables could probably skip all checks, always (except their tuple PK),
since trying to enforce them would just be opening the door to bugs.
Trying to think of any times this isn't true.
- FKs: I'm generally in the same boat as above, thinking that these
don't need to affect historical tables. Section 2.5 in the paper I
link below discusses period joins, but I don't think any special
behavior is needed for now. Perhaps references could be kept in
history but not enforced
- Changing PK / adding/removing more columns to PK: Annoying and not
easily dealt with. Maybe just disallow
- Triggers: no affect on historical
- DROP TABLE bye bye, history & all

You seem to have covered all the bases, and the only way I can think to
sensibly track all of those things is to allow for multiple SV tables, and
every time the main table is altered, you simply start fresh with a new,
empty SV table. You'd probably also slap a constraint on the previous SV
table to reflect the fact that no rows newer than X will ever be entered
there, which would further aid constraint exclusion.

Things like row level security add extra complication but can probably
be disregarded. Maybe just have a `select history` permission or
similar.

+1

An interesting idea could be to automatically add system versioning to
information_schema whenever it is added to a table. This would provide
a way to easily query historical DDL. It would also help solve how to
keep historical FKs. This would make it possible to perfectly recreate
system versioned parts of your database at any period of time, schema
and data both.

Interesting...

#### Misc
- Seems like a good idea to include MDB's option to exclude columns
from versioning (`WITHOUT SYSTEM VERSIONING` as a column argument).
This is relatively nuanced and I'm not sure if it's officially part of
ISO, but probably helpful for frequently updating small data in rows
with BLOBs. Easy enough to implement, just forget the column in the
historical table.

First I've heard of it. Others will know more.

- I thought I saw somewhere that somebody was discussing adding both
row_start and row_end to the PK. Why would this be? Row_end should be
all that's needed to keep unique, but maybe I misread.

I don't think they need to be part of the PK at all. The main table has the
PK that it knows of, and the SV tables are indexed independently.

In fact, I don't think row_end needs to be an actual stored value in the
main table, because it will never be anything other than null. How we
represent such an attribute is another question, but the answer to that
possibly ties into how we implement the virtual side of GENERATED ALWAYS
AS...

#### Links
- I haven't seen it linked here yet but this paper does a phenomenal
deep dive into SV and AV

https://sigmodrecord.org/publications/sigmodRecord/1209/pdfs/07.industry.kulkarni.pdf

My link is different but this seems to be the same PDF that has been cited
earlier for both SV and AV.

- It's not perfect, but MDB's system versioning is pretty well thought
out. You get a good idea of their thought process going through this
page, worth a read

https://mariadb.com/kb/en/system-versioned-tables/#excluding-columns-from-versioning

#### Finally, the end
There's a heck of a lot of thought that could go into this thing,
probably worth making sure there's a formal agreement on what to be
done before coding starts (PGEP for postgres enhancement proposal,
like PEP? Not sure if something like that exists but it probably
should.). Large parts of the existing patch could likely be reused for
whatever is decided.

Thanks for the input, it helps us get some momentum on this.

#95Vik Fearing
vik@postgresfriends.org
In reply to: Corey Huinker (#94)
Re: WIP: System Versioned Temporal Table

On 1/24/22 00:16, Corey Huinker wrote:

- Table schemas change, and all (SV active) AV items would logically
need to fit the active schema or be updated to do so. Different story
for SV, nothing there should ever need to be changed.

Yeah, there's a mess (which you state below) about what happens if you
create a table and then rename a column, or drop a column and add a
same-named column back of another type at a later date, etc. In theory,
this means that the valid set of columns and their types changes according
to the time range specified. I may not be remembering correctly, but Vik
stated that the SQL spec seemed to imply that you had to track all those
things.

The spec does not allow schema changes at all on a a system versioned
table, except to change the system versioning itself.
--
Vik Fearing

#96Jean Baro
jfbaro@gmail.com
In reply to: Vik Fearing (#95)
Re: WIP: System Versioned Temporal Table

Would these best practices be applicable by PostgreSQL to help avoid
breaking changes for temporal tables?

https://blog.datomic.com/2017/01/the-ten-rules-of-schema-growth.html

Thanks

On Tue, Feb 15, 2022 at 5:08 PM Vik Fearing <vik@postgresfriends.org> wrote:

Show quoted text

On 1/24/22 00:16, Corey Huinker wrote:

- Table schemas change, and all (SV active) AV items would logically
need to fit the active schema or be updated to do so. Different story
for SV, nothing there should ever need to be changed.

Yeah, there's a mess (which you state below) about what happens if you
create a table and then rename a column, or drop a column and add a
same-named column back of another type at a later date, etc. In theory,
this means that the valid set of columns and their types changes

according

to the time range specified. I may not be remembering correctly, but Vik
stated that the SQL spec seemed to imply that you had to track all those
things.

The spec does not allow schema changes at all on a a system versioned
table, except to change the system versioning itself.
--
Vik Fearing

#97Corey Huinker
corey.huinker@gmail.com
In reply to: Vik Fearing (#95)
Re: WIP: System Versioned Temporal Table

The spec does not allow schema changes at all on a a system versioned
table, except to change the system versioning itself.

That would greatly simplify things!