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