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