Subject: WIP: System Versioned Temporal Table

From: Surafel Temesgen <[email-masked]>
Date: Wed, 23 Oct 2019 18:56:50 +0300

Hi all ,

Temporal table is one of the main new features added in sql standard 2011.

---

From: Vik Fearing <[email-masked]>
Date: Wed, 23 Oct 2019 22:02:50 +0200

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.

---

From: Surafel Temesgen <[email-masked]>
Date: Thu, 24 Oct 2019 15:54:33 +0100

hi Vik,
On Wed, Oct 23, 2019 at 9:02 PM Vik Fearing <[email-masked]>

> 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

> 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

---

From: Vik Fearing <[email-masked]>
Date: Thu, 24 Oct 2019 17:49:32 +0200

On 24/10/2019 16:54, Surafel Temesgen wrote:
>
> hi Vik,
> On Wed, Oct 23, 2019 at 9:02 PM Vik Fearing
> <[email-masked] <mailto:[email-masked]>> wrote:
>  
>
>
> to PK will violate constraint because it allow multiple data in
> current data

I don't understand what you mean by this.

> its a right thing to add other keyword that contain those two word

Yes, we have to do that.

> it is almost in sql standard syntax except the above small difference.
> i can correct it 

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

---

From: Surafel Temesgen <[email-masked]>
Date: Fri, 25 Oct 2019 12:56:04 +0300

> 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

---

From: Vik Fearing <[email-masked]>
Date: Fri, 25 Oct 2019 21:45:46 +0200

On 25/10/2019 11:56, Surafel Temesgen wrote:
>
>
> On Thu, Oct 24, 2019 at 6:49 PM Vik Fearing
>     > <[email-masked]
>     <mailto:[email-masked]>
>     <mailto:[email-masked]
>     <mailto:[email-masked]>>> wrote:
>     >  
>     >
>     >
>     >     would be better to add both the start and the end column to
>     these
>     >     keys. 
>     > yes it have to be added to other constraint too but adding both
>     system
>     > time 
> 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: Surafel Temesgen <[email-masked]>
Date: Mon, 28 Oct 2019 15:48:08 +0300

---

From: Vik Fearing <[email-masked]>
Date: Mon, 28 Oct 2019 16:36:09 +0100

On 28/10/2019 13:48, Surafel Temesgen wrote:
>
>
> On Fri, Oct 25, 2019 at 10:45 PM Vik Fearing
> 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.

---

From: Surafel Temesgen <[email-masked]>
Date: Wed, 1 Jan 2020 13:50:34 +0300

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

regards
Surafel
From 0a1e51b6fcce03014e3ee355d42443add4da7a28 Mon Sep 17 00:00:00 2001
From: Surafel Temesgen <[email-masked]>
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

---

From: Vik Fearing <[email-masked]>
Date: Wed, 1 Jan 2020 22:12:09 +0100

> 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

---

From: Surafel Temesgen <[email-masked]>
Date: Fri, 3 Jan 2020 13:57:15 +0300

> gram.y: error: shift/reduce conflicts: 6 found, 0 expected
Rebased and conflict resolved i hope it build clean this time

regards
Surafel
From 640f8fc466acc90f0e46d65f21077f652651f34f Mon Sep 17 00:00:00 2001
From: Surafel Temesgen <[email-masked]>
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(-)

 
+   <varlistentry>
 
+   <varlistentry>

+   <varlistentry>
 
+   <varlistentry>
 
+   <varlistentry>

 
+     <varlistentry>
index 00bb4cb53d..7de3fdbd11 100644

 
index c93a788798..1a39d35f5a 100644
@@ -3225,6 +3225,11 @@ CopyFrom(CopyState cstate)
 
index 1c4394abea..a83085372c 100644
@@ -1515,6 +1516,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -1523,6 +1525,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -3585,6 +3595,7 @@ AlterTableGetLockLevel(List *cmds)
@@ -3615,6 +3626,7 @@ AlterTableGetLockLevel(List *cmds)
@@ -6124,6 +6136,12 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
@@ -6501,6 +6519,12 @@ ATExecAddIdentity(Relation rel, const char *colName,
 
@@ -6601,6 +6625,12 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 
@@ -6847,6 +6877,12 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 
@@ -10625,6 +10661,11 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
@@ -11608,6 +11649,12 @@ ATExecAlterColumnGenericOptions(Relation rel,

@@ -15080,7 +15127,7 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
index 06bc2b76ed..b028e5dcd8 100644
@@ -424,6 +425,11 @@ DefineView(ViewStmt *stmt, const char *queryString,

+	/*
index 59d1a31c97..7abe795ba4 100644
 
+/*
 
+		/*
+		 * Set row start time
+		 */
 
+		/*
index 8034d5a51c..3b697d6073 100644
 
 static CreateStmt *
 
+static RowTime *
 
 /*
 
 			/*
 			 * MISCELLANEOUS NODES
index 9c8070c640..3df01ef2d4 100644
 
 	return true;
 }
 
+static bool
 
 		default:
index f7d63df6e1..7fbc552ef4 100644
index a53d47371b..35f0a1cf8a 100644
 
 static void
index d6f2153593..73413ca6ae 100644
 
+	/*
index 3650e8329d..157078ec9e 100644
index d82fc5ab8b..7ed09f83c7 100644

 /*
@@ -2342,3 +2346,188 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
+			ParseNamespaceItem *newnsitem;
+			newnsitem = addRangeTableEntryForRelation(pstate,
+			addNSItemToQuery(pstate, newnsitem, false, true, true);
index 447a61ef8c..e57ecf2257 100644
@@ -1220,6 +1220,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
index ad5be902b0..2356a2be2e 100644

 

 

 

 
@@ -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:
@@ -3500,12 +3578,12 @@ ColConstraintElem:
 
@@ -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
+				{
@@ -3924,9 +4027,34 @@ table_access_method_clause:

 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:
 
 opt_for_locking_clause:
 
@@ -11858,10 +11990,10 @@ for_locking_item:
 		;
 
 for_locking_strength:
 
 locked_rels_list:
@@ -11900,7 +12032,7 @@ values_clause:
  *****************************************************************************/
 
 from_clause:
 
@@ -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 *
index 5fa42d307a..d99001e106 100644
@@ -97,6 +98,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);

 /*
@@ -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;
 }
+
+/*
index 42095ab830..8144d58d70 100644
 
+#include <string.h>

 

+	/*

+	/*

+/*
@@ -3078,6 +3188,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/*
@@ -3121,6 +3235,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
index 4474658ddf..0dc1fe9587 100644

 					/*
 
+
+					/*
index df025a5a30..ff7843cd37 100644
 
 	/*
 
index 799b6988b7..9f8704b498 100644
index f3c7eb96fa..8cb6eb8d3d 100644

index d17af13ee3..d3f74ddba7 100644
 
 /*
index 591e0d65ae..500a5441b8 100644

 
index 0495cae051..ab5be9ad2a 100644

index 31d9aedeeb..585ad10ce7 100644

 #endif							/* MAKEFUNC_H */
index baced7eec0..245e8aa3d0 100644
 
 	/*
index f67bd9fad5..6156aa5376 100644
 
 	/*

 
 /* ----------

 /* Reindex options */

+typedef struct RowTime
+{
+	NodeTag		type;
index c29a7091ec..fdce5d01db 100644
 
index 0fe4e6cb20..f8f80afc0a 100644
index d25819aa28..fdb4da1b70 100644

---

From: Vik Fearing <[email-masked]>
Date: Fri, 3 Jan 2020 14:22:34 +0100

On 03/01/2020 11:57, Surafel Temesgen wrote:
>
>
> On Thu, Jan 2, 2020 at 12:12 AM Vik Fearing

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

---

From: Surafel Temesgen <[email-masked]>
Date: Sun, 5 Jan 2020 13:16:34 +0300

> 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] only failed on travis
because of warning. i will add more test

> 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

regards
Surafel

---

From: Surafel Temesgen <[email-masked]>
Date: Sun, 5 Jan 2020 13:26:33 +0300

> Yes, I understand that.  I'm saying you should also add the row start
that allow the same primary key value row to be insert as long
as insertion time is different

regards
Surafel

---

From: Vik Fearing <[email-masked]>
Date: Sun, 5 Jan 2020 13:50:36 +0100

On 05/01/2020 11:16, Surafel Temesgen wrote:
>
>
> On Fri, Jan 3, 2020 at 4:22 PM Vik Fearing
> 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.

> 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

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

---

From: legrand legrand <[email-masked]>
Date: Sun, 5 Jan 2020 08:01:44 -0700 (MST)

Vik Fearing-4 wrote
>> &lt;

> vik.fearing@

>  &lt;mailto:

> vik.fearing@

> &gt;> wrote:
> [...]
> [...] 

Hello,

I though that standard syntax was "AS OF SYSTEM TIME"
as discussed here
https://www.postgresql.org/message-id/flat/A254CDC3-D308-4822-8928-8CC584E0CC71%40elusive.cx#06c5dbffd5cfb9a20cdeec7a54dc657f
, also explaining how to parse such a syntax .

Regards
PAscal

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

---

From: Vik Fearing <[email-masked]>
Date: Sun, 5 Jan 2020 16:22:56 +0100

> , 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

---

From: legrand legrand <[email-masked]>
Date: Sun, 5 Jan 2020 12:26:06 -0700 (MST)

Vik Fearing-4 wrote
>     FROM tablename FOR SYSTEM_TIME FROM '...' TO '...'

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

Regards
PAscal

---

From: Kyotaro Horiguchi <[email-masked]>
Date: Tue, 07 Jan 2020 19:32:29 +0900 (JST)

Hello.

Isn't this patch somehow broken?

At Mon, 28 Oct 2019 16:36:09 +0100, Vik Fearing <[email-masked]> wrote in 
> Yes, I understand that.  I'm saying you should also add the row start

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.

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

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.

"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

---

From: Vik Fearing <[email-masked]>
Date: Wed, 8 Jan 2020 22:23:56 +0100

> 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

---

From: David Steele <[email-masked]>
Date: Tue, 3 Mar 2020 13:33:46 -0500

Hi Surafel,

> 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
[email-masked]

---

From: Vik Fearing <[email-masked]>
Date: Tue, 3 Mar 2020 19:45:14 +0100

> rebased and updated patch pretty soon.  My recommendation is that we

I strongly second that motion.
-- 
Vik Fearing

---

From: Surafel Temesgen <[email-masked]>
Date: Tue, 10 Mar 2020 15:58:41 +0300

Hi,
Thank you very much looking at it
On Tue, Jan 7, 2020 at 1:33 PM Kyotaro Horiguchi <[email-masked]>
wrote:

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

  ALTER TABLE t ADD COLUMN start timestamp(6) GENERATED ALWAYS AS ROW
> START, ADD COLUMN end timestamp(6) GENERATED ALWAYS AS ROW END;
end is a keyword

> 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

> 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

>  CREATE TABLE t (s timestamp(6) GENERATED ALWAYS AS ROW START, e
> timestamp(6) GENERATED ALWAYS AS ROW END, a int) WITH SYSTEM VERSIONING;
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);

>  SELECT * FROM t FOR SYSTEM_TIME BETWEEN '2020-01-07 0:00:00' AND
fixed

> quite ineffecient if there are many columns. We can store the column
but its only for system versioned table.

> remove rows no longer useful.
fixed

>  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

> wrong. "Unknown" location is -1.

done

> table. So it seems wrong to me to use constraint mechamism for this
we use constraint mechanism for similar thing like default value and
generated column

> of tuple descriptor. The temporal-period columns are not system

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
index e486196477..080187f12c 100644
 
@@ -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> ]

+   <varlistentry>
 
+   <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>

@@ -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)

 
 		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;
 
@@ -1565,6 +1572,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -3653,8 +3668,10 @@ AlterTableGetLockLevel(List *cmds)
+			case AT_PerodColumn:
 
@@ -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,

+	/*
+	 * 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);
 }
 
+/*
+			val = DirectFunctionCall3(timestamptz_in,
@@ -454,6 +579,13 @@ ExecInsert(ModifyTableState *mtstate,
 			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 (!table_tuple_fetch_row_version(resultRelationDesc, tupleid, estate->es_snapshot,
index eaab97f753..cc52800e29 100644
@@ -3358,6 +3358,7 @@ CopyCreateStmtFields(const CreateStmt *from, CreateStmt *newnode)
 
 static CreateStmt *
@@ -4767,6 +4768,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
 
 /*
@@ -5658,6 +5683,12 @@ copyObjectImpl(const void *from)
 
 			/*
 			 * 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
@@ -3752,6 +3774,12 @@ equal(const void *a, const void *b)
 
 		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
index b44efd6314..8847679f74 100644
index 3650e8329d..ce404ce4c6 100644
@@ -28,6 +28,7 @@
+			 * Check and add filter clause to filter out historical data .
+			 * Check and filter out historical data.
index d82fc5ab8b..91939cc1b6 100644
@@ -34,12 +34,14 @@
@@ -52,6 +54,7 @@
 #include "utils/rel.h"
 #include "utils/snapmgr.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)
+			else if (IsA((Node *) linitial(query->jointree->fromlist), JoinExpr))
+				JoinExpr   *j = (JoinExpr *) query->jointree->fromlist;
+				j->quals = make_and_qual(j->quals, wClause);
+	if (IsA(node, Var))
index 6676412842..be04dad8b8 100644
@@ -45,6 +45,7 @@
 #include "utils/rel.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);
 
@@ -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;
index 7e384f956c..768c67306f 100644
 
+/* 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;
 }
 
@@ -374,12 +389,14 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -433,7 +450,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -500,7 +517,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -541,7 +558,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -672,7 +689,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -687,7 +704,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	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

 
@@ -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 ')'
+				{
@@ -3521,12 +3613,12 @@ ColConstraintElem:
 
@@ -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
+				{
@@ -3945,9 +4062,34 @@ table_access_method_clause:

 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:
 
 opt_for_locking_clause:
 
@@ -11903,10 +12049,10 @@ for_locking_item:
 		;
 
 for_locking_strength:
 
 locked_rels_list:
@@ -11945,7 +12091,7 @@ values_clause:
  *****************************************************************************/
 
 from_clause:
 
@@ -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 *
index 36a3efff87..ed2dd152c6 100644
@@ -98,6 +99,7 @@ static WindowClause *findWindowClause(List *wclist, const char *name);

 /*
@@ -1139,6 +1141,35 @@ transformFromClauseItem(ParseState *pstate, Node *n,
@@ -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>
+
@@ -96,6 +99,11 @@ typedef struct
 
@@ -119,6 +127,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
@@ -249,6 +259,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)

@@ -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)

+	/*
+	 * 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"
 
@@ -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;
 
index a12c8d011b..0900655663 100644
@@ -15739,6 +15739,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
index 109245fea7..98080a8e98 100644
@@ -2058,11 +2058,15 @@ describeOneTableDetails(const char *schemaname,

 
 /*
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;

 
 #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,
 
 	/*
index 2039b42449..53c068233f 100644
 
 	/*
 	 * 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;
 
@@ -2079,6 +2084,7 @@ typedef struct 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;
+	BETWEEN_T,
+	BETWEEN_ASYMMETRIC,
index c29a7091ec..d5125db2a7 100644
@@ -73,5 +73,8 @@ extern double get_function_rows(PlannerInfo *root, Oid funcid, Node *node);

 #endif							/* PLANCAT_H */
index b1184c2d15..824200f0b4 100644
@@ -300,6 +300,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
@@ -393,6 +394,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
@@ -441,6 +443,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
 
 	/* 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;

---

From: Surafel Temesgen <[email-masked]>
Date: Tue, 10 Mar 2020 16:00:26 +0300

Hi,

> rebased and updated patch pretty soon.  My recommendation is that we
I agree with moving to  PG14 . Its hard to make it to PG13.

regards
Surafel

---

From: David Steele <[email-masked]>
Date: Tue, 10 Mar 2020 10:07:02 -0400

On 3/10/20 9:00 AM, Surafel Temesgen wrote:
> On Tue, Mar 3, 2020 at 9:33 PM David Steele <[email-masked] 
> <mailto:[email-masked]>> wrote:
> 

The target version is now PG14.

Regards,
-- 
-David
[email-masked]

---

From: Eli Marmor <[email-masked]>
Date: Tue, 31 Mar 2020 22:12:25 +0300

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

---

From: Surafel Temesgen <[email-masked]>
Date: Fri, 17 Jul 2020 17:18:20 +0300

On Tue, Mar 31, 2020 at 10:12 PM Eli Marmor <[email-masked]> 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
> To save me several hours, can you advise me what is the best way to
> install it?

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
index b2eb7097a9..2644067b00 100644

+   <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> ]
 
@@ -874,6 +878,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
@@ -966,6 +992,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
@@ -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>

@@ -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)

@@ -862,6 +865,7 @@ BuildDescForRelation(List *schema)
 
index 44da71c4cb..8f114e60f5 100644
@@ -3174,6 +3174,11 @@ CopyFrom(CopyState cstate)
 
index 27b596cb59..cc4b1a7ad9 100644
@@ -421,11 +426,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
@@ -1560,6 +1566,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -1568,6 +1575,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -3708,8 +3723,10 @@ AlterTableGetLockLevel(List *cmds)
 
@@ -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)
 	 */

 	/* 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;
 
@@ -5270,6 +5330,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
@@ -5340,6 +5407,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}

@@ -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;
 	}
 
@@ -6510,6 +6590,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
@@ -6889,6 +6976,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 
@@ -6989,6 +7083,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 
@@ -7380,6 +7481,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 
@@ -7585,11 +7693,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
@@ -7632,6 +7741,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,

@@ -7692,11 +7811,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,

@@ -7717,9 +7840,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
@@ -11177,6 +11300,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
@@ -11921,10 +12050,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 
@@ -12258,6 +12390,13 @@ ATExecAlterColumnGenericOptions(Relation rel,

@@ -15733,7 +15872,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu

+	/*
index 20a4c474cc..152d5900e6 100644
@@ -362,6 +362,128 @@ ExecComputeStoredGenerated(EState *estate, TupleTableSlot *slot, CmdType cmdtype
 
+/*
+ * Set row start time column for a tuple.
+ */
+void
+ * Set row end time column for a tuple.
@@ -461,6 +583,13 @@ ExecInsert(ModifyTableState *mtstate,
 
+		/*
+		 * 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 *
 
 /*
@@ -5706,6 +5731,12 @@ copyObjectImpl(const void *from)
 
 			/*
 			 * 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
@@ -3758,6 +3780,12 @@ equal(const void *a, const void *b)
 
 		default:
index 49de285f01..9dc114467c 100644
@@ -814,3 +814,130 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
+	n->location = -1;
index e2f177515d..9e63a118b1 100644
@@ -2626,6 +2626,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 
 static void
index b406d41e91..7b8e2adba4 100644
index b02fcb9bfe..d4d36f8999 100644
index 25545029d7..dd1a268df4 100644
 
 /* GUC parameter */

 /*
@@ -2345,3 +2350,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
+			n->location = -1;
index c159fb2957..e3468e8ee7 100644

+	/*
 
@@ -1317,6 +1332,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
+	/*
 
@@ -2280,6 +2300,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
 
 	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;
 
@@ -153,7 +167,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
@@ -178,7 +191,6 @@ static void insertSelectOptions(SelectStmt *stmt,
@@ -251,6 +263,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	struct SelectLimit	*selectlimit;
 
 %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
 
@@ -442,7 +459,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -508,7 +525,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -549,7 +566,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
@@ -680,7 +697,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -695,7 +712,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	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

 
@@ -727,7 +744,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -742,6 +759,10 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -783,6 +804,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -2102,6 +2128,14 @@ alter_table_cmd:
@@ -2129,7 +2163,15 @@ alter_table_cmd:
@@ -2268,7 +2310,19 @@ alter_table_cmd:
@@ -2277,8 +2331,20 @@ alter_table_cmd:
@@ -2287,6 +2353,15 @@ alter_table_cmd:
@@ -3186,12 +3261,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3205,12 +3281,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3224,13 +3301,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3244,13 +3322,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3264,13 +3343,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3284,13 +3364,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3367,6 +3448,7 @@ TableElement:
 
 TypedTableElement:
@@ -3463,6 +3545,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
@@ -3545,12 +3637,12 @@ ColConstraintElem:
 
@@ -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
+				{
@@ -3965,9 +4082,34 @@ table_access_method_clause:

 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:
 
 opt_for_locking_clause:
 
@@ -11749,10 +11895,10 @@ for_locking_item:
 		;
 
 for_locking_strength:
 
 locked_rels_list:
@@ -11791,7 +11937,7 @@ values_clause:
  *****************************************************************************/
 
 from_clause:
 
@@ -11803,12 +11949,16 @@ from_list:
 /*
@@ -11816,6 +11966,19 @@ table_ref:	relation_expr opt_alias_clause
@@ -11914,7 +12077,54 @@ table_ref:	relation_expr opt_alias_clause

 /*
  * 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 *
@@ -16015,29 +16218,6 @@ doNegateFloat(Value *v)
 
-static Node *
index 6fff13479e..d2bb40c04b 100644
+static void changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);

 /*
+		changeTempToWhereClause(pstate, tc, rte);
@@ -3699,3 +3730,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
+ *		make where clause from temporal clause specification.
+changeTempToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
+		fClause = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", (Node *) e, tc->from, 0);
index 25abc544fc..03ab9e1ba3 100644

+#include <string.h>
+

 
@@ -292,6 +308,40 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
 		}
 	}
 
+	/*
+					 errmsg("period start time column not specified")));
+					 errmsg("period end time column not specified")));
@@ -303,6 +353,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)

+	/*
+	 * implicitly to make history and current data co-exist.
@@ -726,6 +796,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
@@ -1300,6 +1426,35 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 
+/*
+ * 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 */
@@ -3123,6 +3278,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/*
@@ -3157,6 +3316,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 			case AT_AddConstraint:
@@ -3166,6 +3333,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
@@ -3333,6 +3517,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
@@ -3364,7 +3563,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
index 9b0c376c8c..558c27c172 100644
 
@@ -1240,6 +1243,12 @@ ProcessUtilitySlow(ParseState *pstate,
 
 					/*
 					 * 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);
 
+
+					/*
+							 * versioning in ALTER TABLE ADD system versioning statment
+							 * create alter table add column cmd and append to the ende
+							 * of alter table commands.
+							 * create alter table drop column cmd and append to the ende
+							 * of alter table commands.
@@ -1260,6 +1348,11 @@ ProcessUtilitySlow(ParseState *pstate,
 
index a2453cf1f4..b1801025c9 100644
@@ -515,6 +515,7 @@ RelationBuildTupleDesc(Relation relation)
 
 	/*
@@ -569,6 +570,9 @@ RelationBuildTupleDesc(Relation relation)
 
index 857c7c2278..2989fadc35 100644
@@ -15686,6 +15686,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
index 3b870c3b17..7c615e2ca8 100644

 
 /*

 

 
 #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,
 
 	/*
index 151bcdb7ef..105e6017ea 100644
@@ -1004,6 +1004,7 @@ typedef struct RangeTblEntry
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1768,7 +1769,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
@@ -1847,7 +1848,11 @@ typedef enum 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;

 #endif							/* PLANCAT_H */
index 08f22ce211..9f3a77bd67 100644
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
@@ -399,6 +400,7 @@ PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
@@ -447,6 +449,7 @@ PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)

 

index 9594856c88..3cfa98e7f6 100644
 
 /*
index 0000000000..9cff30b4a3
+ERROR:  period start time parameter must be the same as  the name of row start time column
+ERROR:  period end time  parameter must be the same as the name of row end time column
+ERROR:  period start time parameter must be the same as  the name of row start time column
index 026ea880cd..14dde559ea 100644
-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
 
index 979d926119..0d51a057fd 100644
@@ -125,6 +125,7 @@ test: drop_operator

---

From: Rémi Lapeyre <[email-masked]>
Date: Sat, 18 Jul 2020 18:05:00 +0200

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

---

From: Surafel Temesgen <[email-masked]>
Date: Tue, 21 Jul 2020 17:32:44 +0300

Hey Rémi,
Thank you for looking at it

On Sat, Jul 18, 2020 at 7:05 PM Rémi Lapeyre <[email-masked]>
wrote:

> Hi, thanks for working on this. I had planned to work on it and I’m
> looking forward to this natively in Postgres.
>
> plancat.c:2368:18: warning: variable 'name' is used uninitialized whenever
> 'for' loop exits because its condition is false [-Wsometimes-uninitialized]
> plancat.c:2396:18: warning: variable 'name' is used uninitialized whenever
> 'for' loop exits because its condition is false [-Wsometimes-uninitialized]

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

+   <varlistentry>
 
+   <varlistentry>

+   <varlistentry>
 
+   <varlistentry>
 
+   <varlistentry>

 
+     <varlistentry>

 

 

+			/*

 	/*

 

 

 

 

 

 

 
+	/*
 
+/*
 
+		/*
+		 * Set row start time
+		 */
 
+		/*
+		 * Set row start time
+		 */
 
 static CreateStmt *
 
+static RowTime *
 
 /*
 
 			/*
 			 * MISCELLANEOUS NODES
 
 	return true;
 }
 
+static bool
 
 		default:
 
 static void
index 25545029d7..eea6265e17 100644
 
 /* GUC parameter */

 /*
+	char	   *name = NULL;
+	char	   *name = NULL;

+	/*

+	/*

+	/*
 
 	return qry;
index dbb47d4982..055f52c2cc 100644

 

 

 

@@ -742,6 +759,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -783,6 +803,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -2102,6 +2127,14 @@ alter_table_cmd:
@@ -2129,7 +2162,15 @@ alter_table_cmd:
@@ -2268,7 +2309,19 @@ alter_table_cmd:
@@ -2277,8 +2330,20 @@ alter_table_cmd:
@@ -2287,6 +2352,15 @@ alter_table_cmd:
@@ -3186,12 +3260,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3205,12 +3280,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3224,13 +3300,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3244,13 +3321,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3264,13 +3342,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3284,13 +3363,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3367,6 +3447,7 @@ TableElement:
 
 TypedTableElement:
@@ -3463,6 +3544,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
@@ -3545,12 +3636,12 @@ ColConstraintElem:
 
@@ -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
+				{
@@ -3965,9 +4081,34 @@ table_access_method_clause:

 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:
 /*
@@ -11816,6 +11961,19 @@ table_ref:	relation_expr opt_alias_clause
@@ -11914,7 +12072,54 @@ table_ref:	relation_expr opt_alias_clause

 /*
  * 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 *
@@ -16015,29 +16213,6 @@ doNegateFloat(Value *v)
 
-static Node *

 /*
 
 	return node;
 }
+
+/*

+#include <string.h>
+

 
 
+	/*

+	/*

+/*
 
 	/* Set up pstate */
 
 	/*
 
 			case AT_AddConstraint:

 
 
 					/*
 
+
+					/*

 	/*

 
 
 /*

 

 
 #endif							/* MAKEFUNC_H */
 
 	/*
 
 	/*

 
 /* ----------

+typedef struct RowTime
+{
+	NodeTag		type;

 #endif							/* PLANCAT_H */

 

 /*

---

From: Michael Paquier <[email-masked]>
Date: Tue, 29 Sep 2020 14:44:27 +0900

> Thank you for looking at it

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

---

From: Surafel Temesgen <[email-masked]>
Date: Tue, 29 Sep 2020 12:54:52 +0300

Hi Michael,

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

Attached is a rebased one.

regards
Surafel
index f034e75ec0..ec8d30a24a 100644

+   <varlistentry>
 
+   <varlistentry>
index 087cad184c..33efc8dcf4 100644

+   <varlistentry>
 
+   <varlistentry>
 
+   <varlistentry>

 
+     <varlistentry>

 
index 2047557e52..8d8511094a 100644
@@ -3222,6 +3222,11 @@ CopyFrom(CopyState cstate)
 
index e0ac4e05e5..b90ab8bcde 100644
@@ -423,11 +428,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,
@@ -1574,6 +1580,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -1582,6 +1589,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -3764,8 +3779,10 @@ AlterTableGetLockLevel(List *cmds)
 
@@ -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);
 
+			/*
@@ -4505,16 +4554,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
@@ -4733,6 +4782,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
@@ -4789,7 +4839,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
@@ -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)
 	 */

 	/* 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;
 
@@ -5357,6 +5417,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
@@ -5427,6 +5494,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}

@@ -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;
 	}
 
@@ -6597,6 +6677,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
@@ -7040,6 +7127,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 
@@ -7140,6 +7234,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 
@@ -7531,6 +7632,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 
@@ -7736,11 +7844,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
@@ -7783,6 +7892,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,

@@ -7843,11 +7962,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,

@@ -7868,9 +7991,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
@@ -11328,6 +11451,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
@@ -12072,10 +12201,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 
@@ -12409,6 +12541,13 @@ ATExecAlterColumnGenericOptions(Relation rel,

@@ -15884,7 +16023,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu

+	/*
index 9812089161..7c735381ff 100644
 
+/*
 
+		/*
+		 * Set row start time
+		 */
 
+		/*
+		 * Set row start time
+		 */
index 0409a40b82..23038300b5 100644
 
 static CreateStmt *
@@ -4811,6 +4812,30 @@ _copyForeignKeyCacheInfo(const ForeignKeyCacheInfo *from)
 	return newnode;
 }
 
+static RowTime *
 
 /*
@@ -5705,6 +5730,12 @@ copyObjectImpl(const void *from)
 
 			/*
 			 * MISCELLANEOUS NODES
index e2d1b987bf..fa950f7c9c 100644
 
 	return true;
 }
@@ -2935,6 +2936,27 @@ _equalPartitionCmd(const PartitionCmd *a, const PartitionCmd *b)
 	return true;
 }
 
+static bool
@@ -3757,6 +3779,12 @@ equal(const void *a, const void *b)
 
 		default:
index f0386480ab..83690534ae 100644
@@ -2627,6 +2627,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 
 static void
index f331f82a6c..4a49362cff 100644
index fcce81926b..f337f67d98 100644
@@ -914,6 +915,15 @@ SS_process_ctes(PlannerInfo *root)
@@ -960,6 +970,15 @@ SS_process_ctes(PlannerInfo *root)
index f9d0d67aa7..31dc168a5d 100644
 
 /* GUC parameter */

 /*
@@ -2342,3 +2347,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)

+	/*

+	/*

+	/*
 
 	return qry;
index 17653ef3a7..b773facf69 100644

 
@@ -551,7 +568,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <keyword> bare_label_keyword
 
@@ -682,7 +699,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -697,7 +714,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	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

 
@@ -729,7 +746,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 /* 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:
@@ -3542,12 +3633,12 @@ ColConstraintElem:
 
@@ -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
+				{
@@ -3962,9 +4078,34 @@ table_access_method_clause:

 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:
 
 opt_for_locking_clause:
@@ -11804,12 +11945,16 @@ from_list:
 /*
@@ -11817,6 +11962,19 @@ table_ref:	relation_expr opt_alias_clause
@@ -11915,7 +12073,54 @@ table_ref:	relation_expr opt_alias_clause

 /*
  * 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 *
@@ -16445,29 +16646,6 @@ doNegateFloat(Value *v)
 
-static Node *
index edcaf276c0..7654147b15 100644

 /*
 
 	return node;
 }
+
+/*
index 0dc03dd984..a228a886e2 100644

+#include <string.h>
+
@@ -95,6 +98,11 @@ typedef struct
 
@@ -118,6 +126,8 @@ static void transformTableConstraint(CreateStmtContext *cxt,
@@ -250,6 +260,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)

@@ -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)
 		}
 	}
 
+	/*
@@ -304,6 +354,26 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)

+	/*
@@ -727,6 +797,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
@@ -1406,6 +1532,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
@@ -3146,7 +3301,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
@@ -3173,7 +3328,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
@@ -3209,6 +3364,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/*
@@ -3243,6 +3402,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 			case AT_AddConstraint:
@@ -3252,6 +3419,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
@@ -3419,6 +3603,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
@@ -3450,7 +3649,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
index 9a35147b26..f36d86e0dd 100644
 
@@ -1275,6 +1278,12 @@ ProcessUtilitySlow(ParseState *pstate,
 
 					/*
 					 * 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);
 
+
+					/*
@@ -1295,6 +1383,11 @@ ProcessUtilitySlow(ParseState *pstate,
 
index 9061af81a3..bb212d19a5 100644
 
 	/*
 
index 76320468ba..27cdc4eb4f 100644
index 58de433fd3..fcc6ab6a23 100644
@@ -2062,11 +2062,15 @@ describeOneTableDetails(const char *schemaname,

 
 /*

 

 
 #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,
 
 	/*
index 60c2f45466..bbaec08c9a 100644
@@ -1005,6 +1005,7 @@ typedef struct RangeTblEntry
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
@@ -1769,7 +1770,7 @@ typedef enum DropBehavior
 } DropBehavior;
 
@@ -1849,7 +1850,11 @@ typedef enum AlterTableType
 
@@ -2084,6 +2089,7 @@ typedef struct 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;

 #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 */
 
index bc3d66ed88..71e46f4f9c 100644

 
 /*

---

From: Georgios Kokolatos <[email-masked]>
Date: Tue, 10 Nov 2020 15:18:22 +0000

Hi,

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

Cheers,
//Georgios

---

From: Surafel Temesgen <[email-masked]>
Date: Thu, 19 Nov 2020 21:03:50 +0300

index c25ef5abd6..4a38ebbc69 100644

+   <varlistentry>
 
+   <varlistentry>
index bc59a2d77d..ca98f6bedc 100644
 
@@ -875,6 +879,28 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
@@ -967,6 +993,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
@@ -1221,6 +1257,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
     </listitem>
    </varlistentry>
 
+   <varlistentry>
index 472b7cae81..b2126b1f26 100644

 
+     <varlistentry>

 
 		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);
 
+					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,
index 46f1637e77..5e4d6c402c 100644
@@ -58,6 +58,7 @@
@@ -75,6 +76,7 @@
@@ -1575,6 +1581,7 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -1583,6 +1590,14 @@ ExecuteTruncate(TruncateStmt *stmt)
 
@@ -3768,8 +3783,10 @@ AlterTableGetLockLevel(List *cmds)
 
@@ -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);
 
+			/*
@@ -4521,16 +4570,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
@@ -4754,6 +4803,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			Assert(rel->rd_rel->relkind == RELKIND_INDEX);
 			ATExecAlterCollationRefreshVersion(rel, cmd->object);
@@ -4810,7 +4860,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
@@ -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)
 	 */

 	/* 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;
 
@@ -5378,6 +5436,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
@@ -5448,6 +5513,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}
 
+				ExecSetRowStartTime(estate, insertslot, resultRelInfo);
 
@@ -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;
 	}
 
@@ -6618,6 +6696,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
@@ -7061,6 +7146,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 
@@ -7161,6 +7253,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 
@@ -7568,6 +7667,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 
@@ -7773,11 +7879,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
@@ -7820,6 +7927,16 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,

@@ -7880,11 +7997,15 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,

@@ -7905,9 +8026,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
@@ -11368,6 +11489,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
@@ -12112,10 +12239,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 
@@ -12449,6 +12579,13 @@ ATExecAlterColumnGenericOptions(Relation rel,

@@ -15924,7 +16061,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu

+	/*
index 29e07b7228..ad4dfdb1f4 100644
@@ -360,6 +360,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 
+/*
+ExecSetRowStartTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
+ExecSetRowEndTime(EState *estate, TupleTableSlot *slot, ResultRelInfo *resultRelInfo)
@@ -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 *
 
 /*
@@ -5693,6 +5718,12 @@ copyObjectImpl(const void *from)
 
 			/*
 			 * 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
@@ -3748,6 +3770,12 @@ equal(const void *a, const void *b)
 
 		default:
index ee033ae779..5b885e469e 100644
@@ -815,3 +815,130 @@ makeVacuumRelation(RangeVar *relation, Oid oid, List *va_cols)
index 4504b1503b..fb6bf9e8ea 100644
@@ -2624,6 +2624,7 @@ _outCreateStmtInfo(StringInfo str, const CreateStmt *node)
 
 static void
index 986d7a52e3..787f9153b7 100644
index 52c01eb86b..8128040706 100644
@@ -35,12 +35,14 @@
@@ -53,6 +55,7 @@
 
 /* GUC parameter */
@@ -80,6 +83,8 @@ static void set_baserel_partition_key_exprs(Relation relation,

 /*
@@ -2353,3 +2358,192 @@ set_baserel_partition_constraint(Relation relation, RelOptInfo *rel)
index 084e00f73d..94f5c6bf43 100644

+	/*

+	/*
 
@@ -2265,6 +2285,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
 
 	return qry;
index 2cb377d034..1cb00ba643 100644

 
-				OptWith distinct_clause opt_definition func_args func_args_list
+				distinct_clause opt_definition func_args func_args_list

 

 

@@ -2098,6 +2123,14 @@ alter_table_cmd:
@@ -2125,7 +2158,15 @@ alter_table_cmd:
@@ -2264,7 +2305,19 @@ alter_table_cmd:
@@ -2273,8 +2326,20 @@ alter_table_cmd:
@@ -2283,6 +2348,15 @@ alter_table_cmd:
@@ -3190,12 +3264,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3209,12 +3284,13 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3228,13 +3304,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3248,13 +3325,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3268,13 +3346,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3288,13 +3367,14 @@ CreateStmt:	CREATE OptTemp TABLE qualified_name '(' OptTableElementList ')'
@@ -3371,6 +3451,7 @@ TableElement:
 
 TypedTableElement:
@@ -3467,6 +3548,16 @@ ColConstraint:
 				}
 		;
 
+optSystemTimeColumn:
@@ -3549,12 +3640,12 @@ ColConstraintElem:
 
@@ -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
+				{
@@ -3969,9 +4085,34 @@ table_access_method_clause:

 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:
 
 opt_for_locking_clause:
@@ -11833,12 +11974,16 @@ from_list:
 /*
@@ -11846,6 +11991,19 @@ table_ref:	relation_expr opt_alias_clause
@@ -11944,7 +12102,54 @@ table_ref:	relation_expr opt_alias_clause

 /*
  * 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 *
@@ -16571,29 +16772,6 @@ doNegateFloat(Value *v)
 
-static Node *
index ea4a1f5aeb..169d10638f 100644

 /*
@@ -3690,3 +3721,73 @@ transformFrameOffset(ParseState *pstate, int frameOptions,
 
 	return node;
 }
+
+/*
index 254c0f65c2..d67420d652 100644

+#include <string.h>
+

 
 
+	/*

+	/*
@@ -730,6 +800,62 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
 				saw_generated = true;
 				break;
 
@@ -1409,6 +1535,35 @@ expandTableLikeClause(RangeVar *heapRel, TableLikeClause *table_like_clause)
 	return result;
 }
 
+/*
@@ -3149,7 +3304,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
@@ -3176,7 +3331,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/* Set up pstate */
@@ -3212,6 +3367,10 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 	/*
@@ -3246,6 +3405,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
 			case AT_AddConstraint:
@@ -3255,6 +3422,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
@@ -3422,6 +3606,21 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 		}
 	}
 
@@ -3453,7 +3652,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 
index f398027fa6..1dba4bf610 100644

 					/*
 
+
+					/*
 
index 66393becfb..1495c357ce 100644
@@ -516,6 +516,7 @@ RelationBuildTupleDesc(Relation relation)
 
 	/*
@@ -570,6 +571,9 @@ RelationBuildTupleDesc(Relation relation)
 
index c68db75b97..91d8e92272 100644
@@ -15803,6 +15803,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
index 07d640021c..f20cb7d779 100644

 
 /*
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

 
 #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);

 #endif							/* MAKEFUNC_H */
 
 	/*
index a2dcdef3fa..6ecbe8d57c 100644
@@ -1010,6 +1010,7 @@ typedef struct RangeTblEntry
 
 	/*
 	 * 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;
 
@@ -2080,6 +2084,7 @@ typedef struct 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;

 #endif							/* PLANCAT_H */

 

 /*
index 0000000000..972d42f211
@@ -0,0 +1,416 @@
index ae89ed7f0b..9fabd58bc2 100644
 
index 525bdc804f..725e76cd26 100644
@@ -126,6 +126,7 @@ test: drop_operator

---

From: Ryan Lambert <[email-masked]>
Date: Fri, 18 Dec 2020 12:28:40 -0700

> Attached is a rebased one.

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

---

From: Surafel Temesgen <[email-masked]>
Date: Mon, 21 Dec 2020 21:01:23 +0300

Hi Ryan,

> 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

---

From: Masahiko Sawada <[email-masked]>
Date: Mon, 4 Jan 2021 23:23:41 +0900

Hi Surafel,

On Tue, Dec 22, 2020 at 3:01 AM Surafel Temesgen <[email-masked]> wrote:
>
> Hi Ryan,
>
> On Fri, Dec 18, 2020 at 10:28 PM Ryan Lambert <[email-masked]> wrote:
>>
>> On Thu, Nov 19, 2020 at 11:04 AM Surafel Temesgen <[email-masked]> wrote:
>>

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/

---

From: Simon Riggs <[email-masked]>
Date: Thu, 7 Jan 2021 17:59:39 +0000

On Mon, Jan 4, 2021 at 2:24 PM Masahiko Sawada <[email-masked]> 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 <[email-masked]> wrote:
> timestamps is different each time of inserts. I think what Surafel is

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!)

> necessary for application-time periods (which your patch doesn't address

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/

---

From: Simon Riggs <[email-masked]>
Date: Fri, 8 Jan 2021 07:13:40 +0000

> Surafel, please say whether you are working on this or not. If you

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

---

From: Simon Riggs <[email-masked]>
Date: Fri, 8 Jan 2021 07:34:43 +0000

> and passes current make check.

Full version attached

---

From: Simon Riggs <[email-masked]>
Date: Fri, 8 Jan 2021 12:33:59 +0000

> > and passes current make check.

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.

---

From: Andrew Dunstan <[email-masked]>
Date: Fri, 8 Jan 2021 08:38:42 -0500

> 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

---

From: Ryan Lambert <[email-masked]>
Date: Fri, 8 Jan 2021 09:50:03 -0700

> 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]: *** [parser/gram.h] Error 2
make[1]: 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

> 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].

> consider a recommendation on what to do with this.

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/

---

From: Simon Riggs <[email-masked]>
Date: Fri, 8 Jan 2021 18:38:29 +0000

On Fri, Jan 8, 2021 at 4:50 PM Ryan Lambert <[email-masked]> wrote:
>
> On Fri, Jan 8, 2021 at 5:34 AM Simon Riggs <[email-masked]> wrote:
>>

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

No changes on the points previously raised.

---

From: Ryan Lambert <[email-masked]>
Date: Fri, 8 Jan 2021 14:19:16 -0700

> 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.

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

---

From: Simon Riggs <[email-masked]>
Date: Sat, 9 Jan 2021 10:39:15 +0000

On Fri, Jan 8, 2021 at 9:19 PM Ryan Lambert <[email-masked]> wrote:

> 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.

---

From: Simon Riggs <[email-masked]>
Date: Mon, 11 Jan 2021 14:02:18 +0000

> 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.

* 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

---

From: Ryan Lambert <[email-masked]>
Date: Tue, 12 Jan 2021 16:14:13 -0700

> an earlier patch merge. That now works as advertised.

It is working as expected now, Thank you!

> 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.

> 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

---

From: Surafel Temesgen <[email-masked]>
Date: Thu, 14 Jan 2021 20:03:16 +0300

Hi Andrew,

> That seems like a significant limitation. Can we fix it instead of

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

---

From: Surafel Temesgen <[email-masked]>
Date: Thu, 14 Jan 2021 20:42:21 +0300

Hi Simon,
Thank you for all the work you does

> 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
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

> * UPDATE foo SET start_timestamp = DEFAULT should fail but currently
okay i will fix it

regards
Surafel

---

From: Surafel Temesgen <[email-masked]>
Date: Thu, 14 Jan 2021 20:46:41 +0300

Hi Ryan

On Fri, Jan 8, 2021 at 7:50 PM Ryan Lambert <[email-masked]> 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
I will change it to hidden by default if there are no objection

regards
Surafel

---

From: Simon Riggs <[email-masked]>
Date: Thu, 14 Jan 2021 21:22:26 +0000

On Thu, Jan 14, 2021 at 5:46 PM Surafel Temesgen <[email-masked]> 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

---

From: Simon Riggs <[email-masked]>
Date: Thu, 14 Jan 2021 21:27:24 +0000

> Thank you for all the work you does

No problem.

> On Mon, Jan 11, 2021 at 5:02 PM Simon Riggs <[email-masked]> wrote:
>>
>>
>>
> 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.

---

From: Ryan Lambert <[email-masked]>
Date: Thu, 14 Jan 2021 16:01:18 -0700

> same row, so it makes sense to see the validity times

+1

Ryan Lambert

---

From: Surafel Temesgen <[email-masked]>
Date: Fri, 15 Jan 2021 19:46:16 +0300

> We need to add an isolation test to show this, or to prove it doesn't

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

---

From: Simon Riggs <[email-masked]>
Date: Fri, 15 Jan 2021 16:50:24 +0000

On Fri, Jan 15, 2021 at 4:46 PM Surafel Temesgen <[email-masked]> wrote:
>
>
>
> On Fri, Jan 15, 2021 at 12:27 AM Simon Riggs <[email-masked]> wrote:
>>
>>

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

---

From: Surafel Temesgen <[email-masked]>
Date: Fri, 15 Jan 2021 19:56:42 +0300

> 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

---

From: Simon Riggs <[email-masked]>
Date: Fri, 15 Jan 2021 17:01:57 +0000

On Fri, Jan 15, 2021 at 4:56 PM Surafel Temesgen <[email-masked]> wrote:
>
>
>
> On Fri, Jan 15, 2021 at 12:22 AM Simon Riggs <[email-masked]> wrote:
>>

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 * ?

---

From: legrand legrand <[email-masked]>
Date: Fri, 15 Jan 2021 12:26:05 -0700 (MST)

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

---

From: Vik Fearing <[email-masked]>
Date: Sat, 16 Jan 2021 15:03:49 +0100

> 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

---

From: Vik Fearing <[email-masked]>
Date: Sat, 16 Jan 2021 15:08:48 +0100

> 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

---

From: Surafel Temesgen <[email-masked]>
Date: Sat, 16 Jan 2021 21:39:38 +0300

> There are no existing applications, so for PostgreSQL, it wouldn't be an
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

---

From: Vik Fearing <[email-masked]>
Date: Sat, 16 Jan 2021 20:12:30 +0100

> 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

---

From: Surafel Temesgen <[email-masked]>
Date: Sun, 17 Jan 2021 19:46:35 +0300

> 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

---

From: Vik Fearing <[email-masked]>
Date: Sun, 17 Jan 2021 23:42:59 +0100

> 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

---

From: Surafel Temesgen <[email-masked]>
Date: Mon, 18 Jan 2021 21:56:13 +0300

> these columns which is even worse.  Please read the standard that you
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.

> (not necessarily the code) this week.
Please do

regards
Surafel

---

From: Vik Fearing <[email-masked]>
Date: Tue, 26 Jan 2021 12:33:06 +0100

> * 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

---

From: Simon Riggs <[email-masked]>
Date: Tue, 26 Jan 2021 12:16:48 +0000

> 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.

---

From: Vik Fearing <[email-masked]>
Date: Tue, 26 Jan 2021 13:51:13 +0100

> 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

---

From: Simon Riggs <[email-masked]>
Date: Tue, 26 Jan 2021 13:26:06 +0000

> 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.

---

From: Surafel Temesgen <[email-masked]>
Date: Tue, 26 Jan 2021 19:39:18 +0300

> fail?  It should not fail.

Attached is rebased patch that include isolation test

regards
Surafel
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>
 
@@ -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>
+      not empty then history records are also removed.
index 569f4c9da7..598f019be2 100644

+   <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>

@@ -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 (constr1->has_system_versioning != constr2->has_system_versioning)
 
 		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);
 
+					resultRelInfo->ri_RelationDesc->rd_att->constr->has_system_versioning)
index 8687e9a97c..ed9ae63d7a 100644
@@ -57,6 +57,7 @@
@@ -74,6 +75,7 @@
@@ -168,6 +170,9 @@ typedef struct AlteredTableInfo
@@ -422,11 +427,12 @@ static ObjectAddress ATExecSetStorage(Relation rel, const char *colName,

+		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)
+				{
@@ -4522,16 +4570,16 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
@@ -4755,6 +4803,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
@@ -4811,7 +4860,7 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
@@ -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)
 	 */

 	/* 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;
 
@@ -5379,6 +5436,13 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
@@ -5449,6 +5513,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 				insertslot = oldslot;
 			}

@@ -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;
 	}
 
@@ -6619,6 +6696,13 @@ ATExecDropNotNull(Relation rel, const char *colName, LOCKMODE lockmode)
 
@@ -7062,6 +7146,13 @@ ATExecAddIdentity(Relation rel, const char *colName,
 
@@ -7162,6 +7253,13 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod
 
@@ -7569,6 +7667,13 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 
@@ -7774,11 +7879,12 @@ ATPrepDropColumn(List **wqueue, Relation rel, bool recurse, bool recursing,
@@ -7821,6 +7927,32 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,

+#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;

@@ -7906,9 +8042,9 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
@@ -11369,6 +11505,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 
@@ -12113,10 +12255,13 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
 
@@ -12450,6 +12595,13 @@ ATExecAlterColumnGenericOptions(Relation rel,

@@ -15929,7 +16081,8 @@ ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNu
index f2642dba6c..93c8c2dec2 100644

+	/*
index 5d90337498..946221ae83 100644
@@ -367,6 +367,126 @@ ExecComputeStoredGenerated(ResultRelInfo *resultRelInfo,
 
+/*
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
+	Assert(tupdesc->constr && tupdesc->constr->has_system_versioning);
@@ -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 *
 
 /*
@@ -5714,6 +5739,12 @@ copyObjectImpl(const void *from)
 
 			/*
 			 * 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
@@ -3767,6 +3789,12 @@ equal(const void *a, const void *b)
 
 		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
index 4e6497ff32..8f53e89a36 100644
index 54ef61bfb3..18cdd27b3f 100644
+			 * Check and add filter clause to remove historical row versions.
+			 * Check and add filter clause to remove historical row versions.
index da322b453e..800fa9afae 100644
 
 /* 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)
+ * if this is a system versioned relation and the where clause did not
+ * already contain a filter condition involving system time column.
+		if (!check_system_versioning_table(rte) ||
+			check_system_versioning_column(query->jointree->quals, rte))
+			 * Create a condition that filters history data and attach it to
+check_system_versioning_column_walker(Node *node, RangeTblEntry *rte)
+	return expression_tree_walker(node, check_system_versioning_column_walker,
+check_system_versioning_column(Node *node, RangeTblEntry *rte)
+	return check_system_versioning_column_walker(node, rte);
+check_system_versioning_table(RangeTblEntry *rte)
+	result = tupdesc->constr && tupdesc->constr->has_system_versioning;
index 6548389225..c9f2b7acaf 100644
@@ -47,6 +47,7 @@

 /* 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);
 
+	/*
@@ -1230,6 +1236,15 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
@@ -1327,6 +1342,11 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 
+	/*
 
@@ -2275,6 +2295,11 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 
+	/*
 
 	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;
 
+	bool	systemVersioning;
@@ -152,7 +166,6 @@ static RawStmt *makeRawStmt(Node *stmt, int stmt_location);
@@ -177,7 +190,6 @@ static void insertSelectOptions(SelectStmt *stmt,
@@ -250,6 +262,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

 %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
 
@@ -676,7 +693,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);

@@ -691,7 +708,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 	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

 
@@ -723,7 +740,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 /*
  * 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:
@@ -3591,12 +3682,12 @@ ColConstraintElem:
 
@@ -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
+				{
@@ -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:
 
 opt_for_locking_clause:
@@ -11850,12 +11991,16 @@ from_list:
 /*
@@ -11863,6 +12008,19 @@ table_ref:	relation_expr opt_alias_clause
@@ -11961,7 +12119,54 @@ table_ref:	relation_expr opt_alias_clause

 /*
  * 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 *
@@ -16617,23 +16818,6 @@ doNegateFloat(Value *v)
 
-static Node *
-	if (IsA(lexpr, BoolExpr))
-		BoolExpr *blexpr = (BoolExpr *) lexpr;
index 672245ded7..dabcab94ed 100644
+static void changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte);

 /*
+		rte->has_system_versioning = (tupdesc->constr && tupdesc->constr->has_system_versioning);
+		if (!rte->has_system_versioning)
+					 errmsg("temporal clause can only specified for a table with system versioning")));
+		changeTemporalToWhereClause(pstate, tc, rte);
 
 	return node;
 }
+
+/*
+ * changeTemporalToWhereClause
+changeTemporalToWhereClause(ParseState *pstate, TemporalClause * tc, RangeTblEntry *rte)
index b31f3afa03..7967fa7a8a 100644

+#include <string.h>
+
+	bool		hasSystemVersioning;	/* true if table is system versioned */
 
@@ -252,6 +262,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
+	cxt.hasSystemVersioning = false;
+

@@ -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)

+	if (cxt.hasSystemVersioning)
+	{
+		ListCell   *lc;
+
@@ -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;
 }
 
+/*
+				 errmsg("period start time must reference the row start time column")));
+				 errmsg("period end time must reference the row end time column")));
@@ -3173,7 +3327,7 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
@@ -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;
 
 	/*
@@ -3271,6 +3428,14 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
+			case AT_PeriodColumn:
 
 			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"
 
@@ -59,7 +60,9 @@
@@ -1236,6 +1239,10 @@ ProcessUtilitySlow(ParseState *pstate,
+					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;
 
index 7ef510cd01..8312a36261 100644
+	constr->has_system_versioning = false;
 
 	/*
+			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;
 
 /*
index 059dec3647..d57521009e 100644

 
index 83e2965531..be9790a6be 100644
 
 #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 */
index caed683ba9..941fcec266 100644
 
 	/*
 	 * 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;
 
@@ -1873,7 +1874,10 @@ typedef enum AlterTableType
+	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;
index 8d1d6c1b42..dd2d6bce2e 100644

 #endif							/* PLANCAT_H */
index 8c554e1f69..c10366ed26 100644
index dfc214b06f..41a708b952 100644
 
index bfa4a6b0f2..3a6251f800 100644

index 841062b4b3..4184ea14f4 100644
+	bool hasSystemVersioning;	/* true if table has system versioning */
 
 /*
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;

---

From: Li Japin <[email-masked]>
Date: Thu, 25 Feb 2021 10:28:28 +0000

On Jan 27, 2021, at 12:39 AM, Surafel Temesgen <[email-masked]<mailto:[email-masked]>> wrote:

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

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>

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.

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.

---

From: Ibrar Ahmed <[email-masked]>
Date: Mon, 8 Mar 2021 22:33:24 +0500

On Thu, Feb 25, 2021 at 3:28 PM Li Japin <[email-masked]> wrote:

>
> On Jan 27, 2021, at 12:39 AM, Surafel Temesgen <[email-masked]>
> wrote:
>
>
>
> 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

---

From: Surafel Temesgen <[email-masked]>
Date: Wed, 10 Mar 2021 08:49:16 -0800

hi Ibrar,
thank you for rebasing

>> are similar, IMO we can pass a flag to get StartTime/EndTime column name,
For me your option is better.  i will change to it in my next
patch if no objection

regards
Surafel

---

From: Vik Fearing <[email-masked]>
Date: Wed, 10 Mar 2021 18:02:53 +0100

> For me your option is better.  i will change to it in my next

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

---

From: Surafel Temesgen <[email-masked]>
Date: Thu, 11 Mar 2021 06:14:50 -0800

> review.  I am still working on it and it is coming soon, I promise.
okay take your time

regards
Surafel

---

From: vignesh C <[email-masked]>
Date: Wed, 14 Jul 2021 17:18:46 +0530

On Mon, Mar 8, 2021 at 11:04 PM Ibrar Ahmed <[email-masked]> wrote:
>> On Jan 27, 2021, at 12:39 AM, Surafel Temesgen <[email-masked]> wrote:
>>
>>
>>
> The patch (system-versioning-temporal-table_2021_v13.patch) does not apply successfully.
>
> 1 out of 1 hunk FAILED -- saving rejects to file src/test/regress/parallel_schedule.rej
> 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

---

From: Simon Riggs <[email-masked]>
Date: Tue, 10 Aug 2021 13:20:14 +0100

> 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.

---

From: Daniel Westermann <[email-masked]>
Date: Tue, 24 Aug 2021 13:18:34 +0000

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

---

From: Jaime Casanova <[email-masked]>
Date: Wed, 1 Sep 2021 11:05:18 -0500

Hi,

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

--

---

From: Jaime Casanova <[email-masked]>
Date: Fri, 10 Sep 2021 13:30:23 -0500

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

---

From: Simon Riggs <[email-masked]>
Date: Sun, 12 Sep 2021 17:02:31 +0100

> 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

---

From: Corey Huinker <[email-masked]>
Date: Mon, 13 Sep 2021 02:45:04 -0400

> Please expect that on Sep 15.
I've been interested in this patch, especially with how it will
interoperate with the work on application periods in
https://www.postgresql.org/message-id/CA+renyUApHgSZF9-nd-a0+OPGharLQLO=[email-masked]
. 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] 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], 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

---

From: Corey Huinker <[email-masked]>
Date: Sat, 18 Sep 2021 20:15:52 -0400

>
>
>
> 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
>
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.

---

From: Simon Riggs <[email-masked]>
Date: Sun, 19 Sep 2021 19:32:20 +0100

On Sun, 19 Sept 2021 at 01:16, Corey Huinker <[email-masked]> 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
>>
>> 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.
>

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.

---

From: Hannu Krosing <[email-masked]>
Date: Sun, 19 Sep 2021 21:12:37 +0200

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.

---

From: Corey Huinker <[email-masked]>
Date: Mon, 20 Sep 2021 00:57:11 -0400

> 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.

> 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.

---

From: Corey Huinker <[email-masked]>
Date: Mon, 20 Sep 2021 01:09:46 -0400

> structure changes we just make a new side table with now-currents

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?

+1

+1

---

From: Hannu Krosing <[email-masked]>
Date: Mon, 20 Sep 2021 11:49:25 +0200

On Mon, Sep 20, 2021 at 7:09 AM Corey Huinker <[email-masked]> wrote:
> 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

---

From: Daniel Gustafsson <[email-masked]>
Date: Mon, 15 Nov 2021 10:47:22 +0100

> On 19 Sep 2021, at 20:32, Simon Riggs <[email-masked]> wrote:

> (2) as Corey suggests, at least for now, and we can always add (3)

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/

---

From: Vik Fearing <[email-masked]>
Date: Mon, 15 Nov 2021 10:50:48 +0100

> 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

---

From: Simon Riggs <[email-masked]>
Date: Mon, 15 Nov 2021 10:50:00 +0000

> 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.

---

From: Daniel Gustafsson <[email-masked]>
Date: Tue, 16 Nov 2021 13:31:51 +0100

> On 15 Nov 2021, at 11:50, Simon Riggs <[email-masked]> wrote:

> I suggest we Return with Feedback.

Fair enough, done that way.