From 533dbf10e67b61a5940935889d17a1d32b0972a0 Mon Sep 17 00:00:00 2001 From: "Paul A. Jungwirth" Date: Sat, 9 Jan 2021 21:59:43 -0800 Subject: [PATCH v12 5/5] Add PERIODs - Added parsing for SQL:2011 syntax to define an application-time PERIOD on a table (in both CREATE TABLE and ALTER TABLE). Make sure we create the PERIOD after columns are known (since PERIODs can refer to them) but before constraints are handled (since PERIODs can appear in them). - Added ALTER TABLE DROP support for PERIODs. - Created postgres.pg_period table. - Created information_schema.periods view. - Added pg_dump support. - Added tests and documentation. - Automatically define a constraint for each PERIOD requiring the start column to be less than the end column. - When creating a PERIOD, choose an appropriate range type we can use to implement PERIOD-related operations. You can choose one explicitly if there is ambiguity (due to multiple range types created over the same base type). --- doc/src/sgml/catalogs.sgml | 112 +++ doc/src/sgml/ddl.sgml | 58 ++ doc/src/sgml/information_schema.sgml | 63 ++ doc/src/sgml/ref/alter_table.sgml | 25 + doc/src/sgml/ref/comment.sgml | 2 + doc/src/sgml/ref/create_table.sgml | 40 ++ src/backend/catalog/Makefile | 3 +- src/backend/catalog/aclchk.c | 2 + src/backend/catalog/dependency.c | 9 + src/backend/catalog/heap.c | 75 ++ src/backend/catalog/information_schema.sql | 23 +- src/backend/catalog/objectaddress.c | 73 ++ src/backend/catalog/pg_period.c | 105 +++ src/backend/catalog/sql_features.txt | 2 +- src/backend/commands/alter.c | 1 + src/backend/commands/comment.c | 10 + src/backend/commands/dropcmds.c | 1 + src/backend/commands/event_trigger.c | 4 + src/backend/commands/seclabel.c | 1 + src/backend/commands/tablecmds.c | 764 ++++++++++++++++++++- src/backend/commands/view.c | 4 +- src/backend/nodes/nodeFuncs.c | 3 + src/backend/parser/gram.y | 43 +- src/backend/parser/parse_utilcmd.c | 136 ++++ src/backend/utils/cache/lsyscache.c | 113 +++ src/backend/utils/cache/syscache.c | 21 + src/bin/pg_dump/pg_backup_archiver.c | 1 + src/bin/pg_dump/pg_dump.c | 173 ++++- src/bin/pg_dump/pg_dump.h | 14 + src/bin/pg_dump/pg_dump_sort.c | 7 + src/bin/psql/describe.c | 34 + src/include/catalog/dependency.h | 1 + src/include/catalog/heap.h | 4 + src/include/catalog/pg_index.h | 2 +- src/include/catalog/pg_period.h | 53 ++ src/include/catalog/pg_range.h | 1 + src/include/commands/tablecmds.h | 4 +- src/include/nodes/parsenodes.h | 35 +- src/include/parser/parse_utilcmd.h | 1 + src/include/utils/lsyscache.h | 4 + src/include/utils/syscache.h | 3 + src/test/regress/expected/periods.out | 152 ++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/periods.sql | 102 +++ 44 files changed, 2245 insertions(+), 41 deletions(-) create mode 100644 src/backend/catalog/pg_period.c create mode 100644 src/include/catalog/pg_period.h create mode 100644 src/test/regress/expected/periods.out create mode 100644 src/test/regress/sql/periods.sql diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 852cb30ae1..007bec90fc 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -230,6 +230,11 @@ information about partition key of tables + + pg_period + periods + + pg_policy row-security policies @@ -5700,6 +5705,113 @@ SCRAM-SHA-256$<iteration count>:&l are simple references. + + + + + + + + + <structname>pg_period</structname> + + + pg_period + + + + The catalog pg_period stores + information about system and application time periods. + + + + Periods are described in . + + + + <structname>pg_period</structname> Columns + + + + + + Column Type + + + Description + + + + + + + + oid oid + + + Row identifier + + + + + + pername text + + + Period name + + + + + + perrelid oid + (references pg_class.oid) + + + The table this period belongs to + + + + + + perstart int2 + (references pg_attribute.attnum) + + + The number of the start column + + + + + + perend int2 + (references pg_attribute.attnum) + + + The number of the end column + + + + + + perrngtype oid + (references pg_type.oid) + + + The OID of the range type associated with this period + + + + + + perconstraint oid + (references pg_constraint.oid) + + + The OID of the period's CHECK constraint + + +
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index e32f8253d0..6f52c2bacf 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -1238,6 +1238,64 @@ CREATE TABLE circles (
+ + Periods + + + Periods are definitions on a table that associate a period name with a start + column and an end column. Both columns must be of exactly the same type + (including collation), have a btree operator class, and the start column + value must be strictly less than the end column value. + + + + There are two types of periods: application and system. System periods are + distinguished by their name which must be SYSTEM_TIME. Any + other name is an application period. + + + + Application Periods + + + period + application + + + + Application periods are defined on a table using the following syntax: + + + +CREATE TABLE billing_addresses ( + customer_id integer, + address_id integer, + valid_from date, + valid_to date, + PERIOD FOR validity (valid_from, valid_to) +); + + + + Application periods can be used to define temporal primary and foreign keys. + Any table with a temporal primary key is a temporal table and supports temporal update and delete commands. + + + + + System Periods + + + period + system + + + + Periods for SYSTEM_TIME are currently not implemented. + + + + System Columns diff --git a/doc/src/sgml/information_schema.sgml b/doc/src/sgml/information_schema.sgml index 350c75bc31..ee9faa8079 100644 --- a/doc/src/sgml/information_schema.sgml +++ b/doc/src/sgml/information_schema.sgml @@ -4171,6 +4171,69 @@ ORDER BY c.ordinal_position; + + <literal>periods</literal> + + + The view parameters contains information about the + periods of all tables in the current database. The start and end column + names are only shown if the current user has access to them (by way of being + the owner or having some privilege). + + + + <literal>periods</literal> Columns + + + + + Name + Data Type + Description + + + + + + table_catalog + sql_identifier + Name of the database containing the period (always the current database) + + + + table_schema + sql_identifier + Name of the schema containing the period + + + + table_name + sql_identifier + Name of the table containing the period + + + + period_name + sql_identifier + Name of the period + + + + start_column_name + sql_identifier + Name of the start column for the period + + + + end_column_name + sql_identifier + Name of the end column for the period + + + +
+
+ <literal>referential_constraints</literal> diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index d4d93eeb7c..487f09f88a 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -60,6 +60,8 @@ ALTER TABLE [ IF EXISTS ] name ALTER CONSTRAINT constraint_name [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] VALIDATE CONSTRAINT constraint_name DROP CONSTRAINT [ IF EXISTS ] constraint_name [ RESTRICT | CASCADE ] + ADD PERIOD FOR period_name ( start_column, end_column ) [ WITH ( period_option = value [, ... ] ) ] + DROP PERIOD FOR period_name [ RESTRICT | CASCADE ] DISABLE TRIGGER [ trigger_name | ALL | USER ] ENABLE TRIGGER [ trigger_name | ALL | USER ] ENABLE REPLICA TRIGGER trigger_name @@ -570,6 +572,29 @@ WITH ( MODULUS numeric_literal, REM + + ADD PERIOD FOR + + + This form adds a new period to a table using the same syntax as + . + + + + + + DROP PERIOD FOR + + + This form drops the specified period on a table. The start and end + columns will not be dropped by this command but the + CHECK constraint will be. You will need to say + CASCADE if anything outside the table depends on the + column. + + + + DISABLE/ENABLE [ REPLICA | ALWAYS ] TRIGGER diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index 5b43c56b13..49c2df9944 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -44,6 +44,7 @@ COMMENT ON OPERATOR operator_name (left_type, right_type) | OPERATOR CLASS object_name USING index_method | OPERATOR FAMILY object_name USING index_method | + PERIOD relation_name.period_name | POLICY policy_name ON table_name | [ PROCEDURAL ] LANGUAGE object_name | PROCEDURE procedure_name [ ( [ [ argmode ] [ argname ] argtype [, ...] ] ) ] | @@ -341,6 +342,7 @@ COMMENT ON OPERATOR ^ (text, text) IS 'Performs intersection of two texts'; COMMENT ON OPERATOR - (NONE, integer) IS 'Unary minus'; COMMENT ON OPERATOR CLASS int4ops USING btree IS '4 byte integer operators for btrees'; COMMENT ON OPERATOR FAMILY integer_ops USING btree IS 'all integer operators for btrees'; +COMMENT ON PERIOD my_table.my_column IS 'Sales promotion validity'; COMMENT ON POLICY my_policy ON mytable IS 'Filter rows by users'; COMMENT ON PROCEDURE my_proc (integer, integer) IS 'Runs a report'; COMMENT ON PUBLICATION alltables IS 'Publishes all operations on all tables'; diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index aa0d4cda19..312d6e7be6 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -23,6 +23,7 @@ PostgreSQL documentation CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name ( [ { column_name data_type [ STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT } ] [ COMPRESSION compression_method ] [ COLLATE collation ] [ column_constraint [ ... ] ] + | period_definition | table_constraint | LIKE source_table [ like_option ... ] } [, ... ] @@ -37,6 +38,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name OF type_name [ ( { column_name [ WITH OPTIONS ] [ column_constraint [ ... ] ] + | period_definition | table_constraint } [, ... ] ) ] @@ -49,6 +51,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXISTS ] table_name PARTITION OF parent_table [ ( { column_name [ WITH OPTIONS ] [ column_constraint [ ... ] ] + | period_definition | table_constraint } [, ... ] ) ] { FOR VALUES partition_bound_spec | DEFAULT } @@ -73,6 +76,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI [ ON DELETE referential_action ] [ ON UPDATE referential_action ] } [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] +and period_definition is: + +PERIOD FOR { period_name | SYSTEM_TIME } ( column_name, column_name ) +[ WITH ( period_option = value [, ... ] ) ] + and table_constraint is: [ CONSTRAINT constraint_name ] @@ -143,6 +151,14 @@ WITH ( MODULUS numeric_literal, REM name as any existing data type in the same schema. + + Periods my be defined on tables, specifying that two existing columns + represent start and end values for the period. Periods may have any name + that doesn't conflict with a column name, but the name + SYSTEM_TIME is special, used for versioning tables. + System periods are not yet implemented. See for more details. + + The optional constraint clauses specify constraints (tests) that new or updated rows must satisfy for an insert or update operation @@ -803,6 +819,30 @@ WITH ( MODULUS numeric_literal, REM + + PERIOD FOR period_name ( column_name, column_name ) [ WITH ( period_option = value [, ... ] ) ] + + + A period definition gives semantic meaning to two existing columns of + the table. It defines a "start column" and an "end column" where the + start value is strictly less than the end value. A + CHECK constraint is automatically created to enforce + this. You can specify the name of that constraint with the + check_constraint_name period_option. + + + + Both columns must have exactly the same type and must have a range type + defined from their base type. If there are several range types for that + base type, you must specify which one you want by using the + rangetype period_option. + Any base type is allowed, as long as it has a range type, although it is + expected that most periods will use temporal types like timestamptz + or date. + + + + CONSTRAINT constraint_name diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index a60107bf94..c9e37a4d71 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -39,6 +39,7 @@ OBJS = \ pg_namespace.o \ pg_operator.o \ pg_parameter_acl.o \ + pg_period.o \ pg_proc.o \ pg_publication.o \ pg_range.o \ @@ -70,7 +71,7 @@ CATALOG_HEADERS := \ pg_foreign_table.h pg_policy.h pg_replication_origin.h \ pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \ pg_collation.h pg_parameter_acl.h pg_partitioned_table.h \ - pg_range.h pg_transform.h \ + pg_period.h pg_range.h pg_transform.h \ pg_sequence.h pg_publication.h pg_publication_namespace.h \ pg_publication_rel.h pg_subscription.h pg_subscription_rel.h diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index bc2ad773c9..1672ed342b 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -2795,6 +2795,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_DEFAULT: case OBJECT_DEFACL: case OBJECT_DOMCONSTRAINT: + case OBJECT_PERIOD: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: case OBJECT_ROLE: @@ -2936,6 +2937,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_DEFACL: case OBJECT_DOMCONSTRAINT: case OBJECT_PARAMETER_ACL: + case OBJECT_PERIOD: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: case OBJECT_ROLE: diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index f8a136ba0a..232f77c96e 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -48,6 +48,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_parameter_acl.h" +#include "catalog/pg_period.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" #include "catalog/pg_publication.h" @@ -154,6 +155,7 @@ static const Oid object_classes[] = { CastRelationId, /* OCLASS_CAST */ CollationRelationId, /* OCLASS_COLLATION */ ConstraintRelationId, /* OCLASS_CONSTRAINT */ + PeriodRelationId, /* OCLASS_PERIOD */ ConversionRelationId, /* OCLASS_CONVERSION */ AttrDefaultRelationId, /* OCLASS_DEFAULT */ LanguageRelationId, /* OCLASS_LANGUAGE */ @@ -1443,6 +1445,10 @@ doDeletion(const ObjectAddress *object, int flags) RemoveConstraintById(object->objectId); break; + case OCLASS_PERIOD: + RemovePeriodById(object->objectId); + break; + case OCLASS_DEFAULT: RemoveAttrDefaultById(object->objectId); break; @@ -2862,6 +2868,9 @@ getObjectClass(const ObjectAddress *object) case ConstraintRelationId: return OCLASS_CONSTRAINT; + case PeriodRelationId: + return OCLASS_PERIOD; + case ConversionRelationId: return OCLASS_CONVERSION; diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index b67fb8400c..022a2ede4c 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -49,6 +49,7 @@ #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_partitioned_table.h" +#include "catalog/pg_period.h" #include "catalog/pg_statistic.h" #include "catalog/pg_subscription_rel.h" #include "catalog/pg_tablespace.h" @@ -2052,6 +2053,80 @@ SetAttrMissing(Oid relid, char *attname, char *value) table_close(tablerel, AccessExclusiveLock); } +/* + * Store a period of relation rel. + * + * Returns the OID of the new pg_period tuple. + */ +Oid +StorePeriod(Relation rel, const char *periodname, AttrNumber startnum, + AttrNumber endnum, AttrNumber rangenum, Oid conoid) +{ + Assert(rangenum != InvalidAttrNumber); + Datum values[Natts_pg_period]; + bool nulls[Natts_pg_period]; + Relation pg_period; + HeapTuple tuple; + Oid oid; + NameData pername; + ObjectAddress myself, referenced; + + namestrcpy(&pername, periodname); + + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + + pg_period = table_open(PeriodRelationId, RowExclusiveLock); + + oid = GetNewOidWithIndex(pg_period, AttrDefaultOidIndexId, Anum_pg_period_oid); + values[Anum_pg_period_oid - 1] = ObjectIdGetDatum(oid); + values[Anum_pg_period_pername - 1] = NameGetDatum(&pername); + values[Anum_pg_period_perrelid - 1] = RelationGetRelid(rel); + values[Anum_pg_period_perstart - 1] = startnum; + values[Anum_pg_period_perend - 1] = endnum; + values[Anum_pg_period_perrange - 1] = rangenum; + values[Anum_pg_period_perconstraint - 1] = conoid; + + tuple = heap_form_tuple(RelationGetDescr(pg_period), values, nulls); + CatalogTupleInsert(pg_period, tuple); + + ObjectAddressSet(myself, PeriodRelationId, oid); + + /* Drop the period when the table is dropped. */ + ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel)); + recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO); + + /* Forbid dropping the columns of the period. */ + ObjectAddressSubSet(referenced, RelationRelationId, RelationGetRelid(rel), startnum); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + ObjectAddressSubSet(referenced, RelationRelationId, RelationGetRelid(rel), endnum); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* + * The range column is an implementation detail, + * but we can't use DEPENDENCY_INTERNAL + * because dropping the table will check for dependencies on all subobjects too + * (in findDependentObjects). + * But if we make an AUTO dependency one way we will auto-drop the column + * when we drop the PERIOD, + * and a NORMAL dependency the other way we will forbid dropping the column directly. + */ + ObjectAddressSubSet(referenced, RelationRelationId, RelationGetRelid(rel), rangenum); + recordDependencyOn(&referenced, &myself, DEPENDENCY_AUTO); + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* + * The constraint is an implementation detail, so we mark it as such. + * (Note that myself and referenced are reversed for this one.) + */ + ObjectAddressSet(referenced, ConstraintRelationId, conoid); + recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL); + + table_close(pg_period, RowExclusiveLock); + + return oid; +} + /* * Store a check-constraint expression for the given relation. * diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql index 8bcd42467a..4afd207ade 100644 --- a/src/backend/catalog/information_schema.sql +++ b/src/backend/catalog/information_schema.sql @@ -1200,7 +1200,28 @@ GRANT SELECT ON parameters TO PUBLIC; * PERIODS view */ --- feature not supported +CREATE VIEW periods AS + SELECT current_database()::information_schema.sql_identifier AS table_catalog, + nc.nspname::information_schema.sql_identifier AS table_schema, + c.relname::information_schema.sql_identifier AS table_name, + p.pername::information_schema.sql_identifier AS period_name, + CASE WHEN pg_has_role(c.relowner, 'USAGE') + OR has_column_privilege(sa.attrelid, sa.attnum, 'SELECT, INSERT, UPDATE, REFERENCES') + THEN sa.attname::information_schema.sql_identifier + END AS start_column_name, + CASE WHEN pg_has_role(c.relowner, 'USAGE') + OR has_column_privilege(ea.attrelid, ea.attnum, 'SELECT, INSERT, UPDATE, REFERENCES') + THEN ea.attname::information_schema.sql_identifier + END AS end_column_name + FROM pg_period AS p + JOIN pg_class AS c ON c.oid = p.perrelid + JOIN pg_namespace AS nc ON nc.oid = c.relnamespace + JOIN pg_attribute AS sa ON (sa.attrelid, sa.attnum) = (p.perrelid, p.perstart) + JOIN pg_attribute AS ea ON (ea.attrelid, ea.attnum) = (p.perrelid, p.perend) + WHERE NOT pg_is_other_temp_schema(nc.oid) + AND c.relkind IN ('r', 'v'); + +GRANT SELECT ON periods TO PUBLIC; /* diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 95fefc7565..a979233e6b 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -47,6 +47,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_opfamily.h" #include "catalog/pg_parameter_acl.h" +#include "catalog/pg_period.h" #include "catalog/pg_policy.h" #include "catalog/pg_proc.h" #include "catalog/pg_publication.h" @@ -730,6 +731,10 @@ static const struct object_type_map { "domain constraint", OBJECT_DOMCONSTRAINT }, + /* OCLASS_PERIOD */ + { + "period", OBJECT_PERIOD + }, /* OCLASS_CONVERSION */ { "conversion", OBJECT_CONVERSION @@ -1008,6 +1013,7 @@ get_object_address(ObjectType objtype, Node *object, case OBJECT_TRIGGER: case OBJECT_TABCONSTRAINT: case OBJECT_POLICY: + case OBJECT_PERIOD: address = get_object_address_relobject(objtype, castNode(List, object), &relation, missing_ok); break; @@ -1506,6 +1512,13 @@ get_object_address_relobject(ObjectType objtype, List *object, InvalidOid; address.objectSubId = 0; break; + case OBJECT_PERIOD: + address.classId = PeriodRelationId; + address.objectId = relation ? + get_relation_period_oid(reloid, depname, missing_ok) : + InvalidOid; + address.objectSubId = 0; + break; default: elog(ERROR, "unrecognized object type: %d", (int) objtype); } @@ -2323,6 +2336,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_RULE: case OBJECT_TRIGGER: case OBJECT_TABCONSTRAINT: + case OBJECT_PERIOD: case OBJECT_OPCLASS: case OBJECT_OPFAMILY: objnode = (Node *) name; @@ -2433,6 +2447,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, case OBJECT_TRIGGER: case OBJECT_POLICY: case OBJECT_TABCONSTRAINT: + case OBJECT_PERIOD: if (!object_ownercheck(RelationRelationId, RelationGetRelid(relation), roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, objtype, RelationGetRelationName(relation)); @@ -3081,6 +3096,38 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) break; } + case OCLASS_PERIOD: + { + HeapTuple perTup; + Form_pg_period per; + + perTup = SearchSysCache1(PERIODOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(perTup)) + elog(ERROR, "cache lookup failed for period %u", + object->objectId); + per = (Form_pg_period) GETSTRUCT(perTup); + + if (OidIsValid(per->perrelid)) + { + StringInfoData rel; + + initStringInfo(&rel); + getRelationDescription(&rel, per->perrelid, false); + appendStringInfo(&buffer, _("period %s on %s"), + NameStr(per->pername), rel.data); + pfree(rel.data); + } + else + { + appendStringInfo(&buffer, _("period %s"), + NameStr(per->pername)); + } + + ReleaseSysCache(perTup); + break; + } + case OCLASS_CONVERSION: { HeapTuple conTup; @@ -4445,6 +4492,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok) missing_ok); break; + case OCLASS_PERIOD: + appendStringInfoString(&buffer, "period"); + break; + case OCLASS_CONVERSION: appendStringInfoString(&buffer, "conversion"); break; @@ -4952,6 +5003,28 @@ getObjectIdentityParts(const ObjectAddress *object, break; } + case OCLASS_PERIOD: + { + HeapTuple perTup; + Form_pg_period per; + + perTup = SearchSysCache1(PERIODOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(perTup)) + elog(ERROR, "cache lookup failed for period %u", + object->objectId); + per = (Form_pg_period) GETSTRUCT(perTup); + + appendStringInfo(&buffer, "%s on ", + quote_identifier(NameStr(per->pername))); + getRelationIdentity(&buffer, per->perrelid, objname, false); + if (objname) + *objname = lappend(*objname, pstrdup(NameStr(per->pername))); + + ReleaseSysCache(perTup); + break; + } + case OCLASS_CONVERSION: { HeapTuple conTup; diff --git a/src/backend/catalog/pg_period.c b/src/backend/catalog/pg_period.c new file mode 100644 index 0000000000..8b7968311b --- /dev/null +++ b/src/backend/catalog/pg_period.c @@ -0,0 +1,105 @@ +/*------------------------------------------------------------------------- + * + * pg_period.c + * routines to support manipulation of the pg_period relation + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/catalog/pg_period.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "catalog/indexing.h" +#include "catalog/pg_period.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + + +/* + * Delete a single period record. + */ +void +RemovePeriodById(Oid periodId) +{ + Relation pg_period; + HeapTuple tup; + + pg_period = table_open(PeriodRelationId, RowExclusiveLock); + + tup = SearchSysCache1(PERIODOID, ObjectIdGetDatum(periodId)); + if (!HeapTupleIsValid(tup)) /* should not happen */ + elog(ERROR, "cache lookup failed for period %u", periodId); + + /* Fry the period itself */ + CatalogTupleDelete(pg_period, &tup->t_self); + + /* Clean up */ + ReleaseSysCache(tup); + table_close(pg_period, RowExclusiveLock); +} + +/* + * get_relation_period_oid + * Find a period on the specified relation with the specified name. + * Returns period's OID. + */ +Oid +get_relation_period_oid(Oid relid, const char *pername, bool missing_ok) +{ + Relation pg_period; + HeapTuple tuple; + SysScanDesc scan; + ScanKeyData skey[1]; + Oid perOid = InvalidOid; + + /* + * Fetch the period tuple from pg_period. Periods should have unique + * names, but if we find a duplicate then error out. + */ + pg_period = table_open(PeriodRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_period_perrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relid)); + + scan = systable_beginscan(pg_period, PeriodRelidNameIndexId, true, + NULL, 1, skey); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_period period = (Form_pg_period) GETSTRUCT(tuple); + + if (strcmp(NameStr(period->pername), pername) == 0) + { + if (OidIsValid(perOid)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("table \"%s\" has multiple periods named \"%s\"", + get_rel_name(relid), pername))); + perOid = period->oid; + } + } + + systable_endscan(scan); + + /* If no such period exists, complain */ + if (!OidIsValid(perOid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("period \"%s\" for table \"%s\" does not exist", + pername, get_rel_name(relid)))); + + table_close(pg_period, AccessShareLock); + + return perOid; +} diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index b33065d7bf..775090a6ec 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -449,7 +449,7 @@ T176 Sequence generator support NO supported except for NEXT VALUE FOR T177 Sequence generator support: simple restart option YES T178 Identity columns: simple restart option YES T180 System-versioned tables NO -T181 Application-time period tables NO +T181 Application-time period tables YES T191 Referential action RESTRICT YES T200 Trigger DDL NO similar but not fully compatible T201 Comparable data types for referential constraints YES diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index e95dc31bde..2b378acd2a 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -668,6 +668,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, case OCLASS_CAST: case OCLASS_CONSTRAINT: + case OCLASS_PERIOD: case OCLASS_DEFAULT: case OCLASS_LANGUAGE: case OCLASS_LARGEOBJECT: diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c index 26c4d59202..452af1d413 100644 --- a/src/backend/commands/comment.c +++ b/src/backend/commands/comment.c @@ -102,6 +102,16 @@ CommentObject(CommentStmt *stmt) RelationGetRelationName(relation)), errdetail_relkind_not_supported(relation->rd_rel->relkind))); break; + + case OBJECT_PERIOD: + /* Periods can only go on tables */ + if (relation->rd_rel->relkind != RELKIND_RELATION) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a table", + RelationGetRelationName(relation)))); + break; + default: break; } diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c index 469a6c2ee9..1b9754e998 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -509,6 +509,7 @@ does_not_exist_skipping(ObjectType objtype, Node *object) case OBJECT_DOMCONSTRAINT: case OBJECT_LARGEOBJECT: case OBJECT_PARAMETER_ACL: + case OBJECT_PERIOD: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: case OBJECT_TABCONSTRAINT: diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index d4b00d1a82..19e6dfd37b 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -970,6 +970,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_OPCLASS: case OBJECT_OPERATOR: case OBJECT_OPFAMILY: + case OBJECT_PERIOD: case OBJECT_POLICY: case OBJECT_PROCEDURE: case OBJECT_PUBLICATION: @@ -1028,6 +1029,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_CAST: case OCLASS_COLLATION: case OCLASS_CONSTRAINT: + case OCLASS_PERIOD: case OCLASS_CONVERSION: case OCLASS_DEFAULT: case OCLASS_LANGUAGE: @@ -2069,6 +2071,7 @@ stringify_grant_objtype(ObjectType objtype) case OBJECT_OPCLASS: case OBJECT_OPERATOR: case OBJECT_OPFAMILY: + case OBJECT_PERIOD: case OBJECT_POLICY: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_NAMESPACE: @@ -2153,6 +2156,7 @@ stringify_adefprivs_objtype(ObjectType objtype) case OBJECT_OPERATOR: case OBJECT_OPFAMILY: case OBJECT_PARAMETER_ACL: + case OBJECT_PERIOD: case OBJECT_POLICY: case OBJECT_PUBLICATION: case OBJECT_PUBLICATION_NAMESPACE: diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index 7ff16e3276..3ddc99581a 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -79,6 +79,7 @@ SecLabelSupportsObjectType(ObjectType objtype) case OBJECT_OPERATOR: case OBJECT_OPFAMILY: case OBJECT_PARAMETER_ACL: + case OBJECT_PERIOD: case OBJECT_POLICY: case OBJECT_PUBLICATION_NAMESPACE: case OBJECT_PUBLICATION_REL: diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 409e470fbd..bce1551bfc 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -43,6 +43,7 @@ #include "catalog/pg_largeobject.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" +#include "catalog/pg_period.h" #include "catalog/pg_statistic_ext.h" #include "catalog/pg_tablespace.h" #include "catalog/pg_trigger.h" @@ -149,13 +150,18 @@ static List *on_commits = NIL; #define AT_PASS_OLD_CONSTR 3 /* re-add existing constraints */ /* We could support a RENAME COLUMN pass here, but not currently used */ #define AT_PASS_ADD_COL 4 /* ADD COLUMN */ -#define AT_PASS_ADD_CONSTR 5 /* ADD constraints (initial examination) */ -#define AT_PASS_COL_ATTRS 6 /* set column attributes, eg NOT NULL */ -#define AT_PASS_ADD_INDEXCONSTR 7 /* ADD index-based constraints */ -#define AT_PASS_ADD_INDEX 8 /* ADD indexes */ -#define AT_PASS_ADD_OTHERCONSTR 9 /* ADD other constraints, defaults */ -#define AT_PASS_MISC 10 /* other stuff */ -#define AT_NUM_PASSES 11 +/* + * We must add PERIODs after columns, in case they reference a newly-added column, + * and before constraints, in case a newly-added PK/FK references them. + */ +#define AT_PASS_ADD_PERIOD 5 /* ADD PERIOD */ +#define AT_PASS_ADD_CONSTR 6 /* ADD constraints (initial examination) */ +#define AT_PASS_COL_ATTRS 7 /* set column attributes, eg NOT NULL */ +#define AT_PASS_ADD_INDEXCONSTR 8 /* ADD index-based constraints */ +#define AT_PASS_ADD_INDEX 9 /* ADD indexes */ +#define AT_PASS_ADD_OTHERCONSTR 10 /* ADD other constraints, defaults */ +#define AT_PASS_MISC 11 /* other stuff */ +#define AT_NUM_PASSES 12 typedef struct AlteredTableInfo { @@ -353,6 +359,7 @@ static void RangeVarCallbackForTruncate(const RangeVar *relation, Oid relId, Oid oldRelId, void *arg); static List *MergeAttributes(List *schema, List *supers, char relpersistence, bool is_partition, List **supconstr); +static List *MergePeriods(char *relname, List *periods, List *tableElts, List *supers); static bool MergeCheckConstraint(List *constraints, char *name, Node *expr); static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel); static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); @@ -433,6 +440,8 @@ static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, AlterTableUtilityContext *context); static bool check_for_column_name_collision(Relation rel, const char *colname, bool if_not_exists); +static bool check_for_period_name_collision(Relation rel, const char *pername, + bool if_not_exists); static void add_column_datatype_dependency(Oid relid, int32 attnum, Oid typid); static void add_column_collation_dependency(Oid relid, int32 attnum, Oid collid); static void ATPrepDropNotNull(Relation rel, bool recurse, bool recursing); @@ -452,6 +461,12 @@ static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName, Node *newDefault, LOCKMODE lockmode); static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum, Node *newDefault); +static ObjectAddress ATExecAddPeriod(Relation rel, PeriodDef *period, + AlterTableUtilityContext *context); +static void ATExecDropPeriod(Relation rel, const char *periodName, + DropBehavior behavior, + bool recurse, bool recursing, + bool missing_ok); static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode); static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName, @@ -651,6 +666,10 @@ static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); static char GetAttributeCompression(Oid atttypid, char *compression); static char GetAttributeStorage(Oid atttypid, const char *storagemode); +static void AddRelationNewPeriod(Relation rel, PeriodDef *period); +static void ValidatePeriod(Relation rel, PeriodDef *period); +static Constraint *make_constraint_for_period(Relation rel, PeriodDef *period); +static ColumnDef *make_range_column_for_period(PeriodDef *period); /* ---------------------------------------------------------------- @@ -877,6 +896,26 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, stmt->partbound != NULL, &old_constraints); + /* + * Using the column list (including inherited columns), + * find the start/end columns for each period. + * PERIODs should be inherited too (but aren't yet). + */ + stmt->periods = MergePeriods(relname, stmt->periods, stmt->tableElts, inheritOids); + + /* + * For each PERIOD we need to create a GENERATED column, + * so add those to tableElts. + * These columns are not inherited, + * so we don't worry about conflicts in tableElts. + */ + foreach(listptr, stmt->periods) + { + PeriodDef *period = (PeriodDef *) lfirst(listptr); + ColumnDef *col = make_range_column_for_period(period); + stmt->tableElts = lappend(stmt->tableElts, col); + } + /* * Create a tuple descriptor from the relation schema. Note that this * deals with column names, types, and NOT NULL constraints, but not @@ -1263,6 +1302,21 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, AddRelationNewConstraints(rel, NIL, stmt->constraints, true, true, false, queryString); + /* + * Create periods for the table. This must come after we create columns + * and before we create index constraints. It will automatically create + * a CHECK constraint for the period. + */ + foreach(listptr, stmt->periods) + { + PeriodDef *period = (PeriodDef *) lfirst(listptr); + + /* Don't update the count of check constraints twice */ + CommandCounterIncrement(); + + AddRelationNewPeriod(rel, period); + } + ObjectAddressSet(address, RelationRelationId, relationId); /* @@ -1274,6 +1328,264 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, return address; } +/* + * make_constraint_for_period + * + * Builds a CHECK Constraint to ensure start < end. + * Returns the CHECK Constraint. + * Also fills in period->constraintname if needed. + */ +static Constraint * +make_constraint_for_period(Relation rel, PeriodDef *period) +{ + ColumnRef *scol, *ecol; + Constraint *constr; + TypeCacheEntry *type; + + if (period->constraintname == NULL) + period->constraintname = ChooseConstraintName(RelationGetRelationName(rel), + period->periodname, + "check", + RelationGetNamespace(rel), + NIL); + scol = makeNode(ColumnRef); + scol->fields = list_make1(makeString(pstrdup(period->startcolname))); + scol->location = 0; + + ecol = makeNode(ColumnRef); + ecol->fields = list_make1(makeString(pstrdup(period->endcolname))); + ecol->location = 0; + + type = lookup_type_cache(period->coltypid, TYPECACHE_LT_OPR); + if (type->lt_opr == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("column \"%s\" cannot be used in a PERIOD because its type %s has no less than operator", + period->startcolname, format_type_be(period->coltypid)))); + + constr = makeNode(Constraint); + constr->contype = CONSTR_CHECK; + constr->conname = period->constraintname; + constr->deferrable = false; + constr->initdeferred = false; + constr->location = -1; + constr->is_no_inherit = false; + constr->raw_expr = (Node *) makeSimpleA_Expr(AEXPR_OP, + get_opname(type->lt_opr), + (Node *) scol, + (Node *) ecol, + 0); + constr->cooked_expr = NULL; + constr->skip_validation = false; + constr->initially_valid = true; + + return constr; +} + +/* + * make_range_column_for_period + * + * Builds a GENERATED ALWAYS range column based on the PERIOD + * start/end columns. Returns the ColumnDef. + */ +ColumnDef * +make_range_column_for_period(PeriodDef *period) +{ + char *range_type_namespace; + char *range_type_name; + ColumnDef *col = makeNode(ColumnDef); + ColumnRef *startvar, *endvar; + Expr *rangeConstructor; + + if (!get_typname_and_namespace(period->rngtypid, &range_type_name, &range_type_namespace)) + elog(ERROR, "missing range type %d", period->rngtypid); + + startvar = makeNode(ColumnRef); + startvar->fields = list_make1(makeString(pstrdup(period->startcolname))); + endvar = makeNode(ColumnRef); + endvar->fields = list_make1(makeString(pstrdup(period->endcolname))); + rangeConstructor = (Expr *) makeFuncCall( + list_make2(makeString(range_type_namespace), makeString(range_type_name)), + list_make2(startvar, endvar), + COERCE_EXPLICIT_CALL, + period->location); + + col->colname = pstrdup(period->periodname); + col->typeName = makeTypeName(range_type_name); + col->compression = NULL; + col->inhcount = 0; + col->is_local = true; + col->is_not_null = true; + col->is_from_type = false; + col->storage = 0; + col->storage_name = NULL; + col->raw_default = (Node *) rangeConstructor; + col->cooked_default = NULL; + col->identity = 0; + col->generated = ATTRIBUTE_GENERATED_STORED; + col->collClause = NULL; + col->collOid = InvalidOid; + col->fdwoptions = NIL; + col->location = period->location; + + return col; +} + +/* + * ValidatePeriod + * + * Look up the attributes used by the PERIOD, + * make sure they exist, are not system columns, + * and have the same type and collation. + * + * You must have a RowExclusiveLock on pg_attribute + * before calling this function. + * + * Add our findings to these PeriodDef fields: + * + * coltypid - the type of PERIOD columns. + * startattnum - the attnum of the start column. + * endattnum - the attnum of the end column. + */ +static void +ValidatePeriod(Relation rel, PeriodDef *period) +{ + HeapTuple starttuple; + HeapTuple endtuple; + Form_pg_attribute atttuple; + Oid attcollation; + + /* Find the start column */ + starttuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), period->startcolname); + if (!HeapTupleIsValid(starttuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + period->startcolname, RelationGetRelationName(rel)))); + atttuple = (Form_pg_attribute) GETSTRUCT(starttuple); + period->coltypid = atttuple->atttypid; + attcollation = atttuple->attcollation; + period->startattnum = atttuple->attnum; + + /* Make sure it's not a system column */ + if (period->startattnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use system column \"%s\" in period", + period->startcolname))); + + /* Find the end column */ + endtuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), period->endcolname); + if (!HeapTupleIsValid(endtuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + period->endcolname, RelationGetRelationName(rel)))); + atttuple = (Form_pg_attribute) GETSTRUCT(endtuple); + period->endattnum = atttuple->attnum; + + /* Make sure it's not a system column */ + if (period->endattnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot use system column \"%s\" in period", + period->endcolname))); + + /* Both columns must be of same type */ + if (period->coltypid != atttuple->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("start and end columns of period must be of same type"))); + + /* Both columns must have the same collation */ + if (attcollation != atttuple->attcollation) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("start and end columns of period must have same collation"))); + + heap_freetuple(starttuple); + heap_freetuple(endtuple); +} + +/* + * choose_rangetype_for_period + * + * Find a suitable range type for operations involving this period. + * Use the rangetype option if provided, otherwise try to find a + * non-ambiguous existing type. + */ +// TODO: This could be static now +Oid +choose_rangetype_for_period(PeriodDef *period) +{ + Oid rngtypid; + + if (period->rangetypename != NULL) + { + /* Make sure it exists */ + rngtypid = TypenameGetTypidExtended(period->rangetypename, false); + if (rngtypid == InvalidOid) + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("Range type %s not found", period->rangetypename))); + + /* Make sure it is a range type */ + if (!type_is_range(rngtypid)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("Type %s is not a range type", period->rangetypename))); + + /* Make sure it matches the column type */ + if (get_range_subtype(rngtypid) != period->coltypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("Range type %s does not match column type %s", + period->rangetypename, + format_type_be(period->coltypid)))); + } + else + { + rngtypid = get_subtype_range(period->coltypid); + if (rngtypid == InvalidOid) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no range type for %s found for period %s", + format_type_be(period->coltypid), + period->periodname), + errhint("You can define a custom range type with CREATE TYPE"))); + + } + + return rngtypid; +} + +static void +AddRelationNewPeriod(Relation rel, PeriodDef *period) +{ + Relation attrelation; + Oid conoid; + Constraint *constr; + List *newconstrs; + + attrelation = table_open(AttributeRelationId, RowExclusiveLock); + + /* Find the GENERATED range column */ + + period->rngattnum = get_attnum(RelationGetRelid(rel), period->periodname); + if (period->rngattnum == InvalidAttrNumber) + elog(ERROR, "missing attribute %s", period->periodname); + + /* The parser has already found period->coltypid */ + + constr = make_constraint_for_period(rel, period); + newconstrs = AddRelationNewConstraints(rel, NIL, list_make1(constr), false, true, true, NULL); + conoid = ((CookedConstraint *) linitial(newconstrs))->conoid; + + /* Save it */ + StorePeriod(rel, period->periodname, period->startattnum, period->endattnum, period->rngattnum, conoid); + + table_close(attrelation, RowExclusiveLock); +} + /* * Emit the right error or warning message for a "DROP" command issued on a * non-existent relation @@ -3175,6 +3487,168 @@ MergeAttributes(List *schema, List *supers, char relpersistence, } +/*---------- + * MergePeriods + * Returns new period list given initial periods and superclasses. + * + * For now we don't support inheritence with PERIODs, + * but we might make it work eventually. + * + * We can omit lots of checks here and assume MergeAttributes already did them, + * for example that child & parents are not a mix of permanent and temp. + */ +static List * +MergePeriods(char *relname, List *periods, List *tableElts, List *supers) +{ + ListCell *entry; + + /* If we have a PERIOD then supers must be empty. */ + + if (list_length(periods) > 0 && list_length(supers) > 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Inheriting is not supported when a table has a PERIOD"))); + + /* If any parent table has a PERIOD, then fail. */ + + foreach(entry, supers) + { + Oid parent = lfirst_oid(entry); + Relation relation; + Relation pg_period; + SysScanDesc scan; + ScanKeyData skey[1]; + HeapTuple tuple; + + /* caller already got lock */ + relation = table_open(parent, NoLock); + pg_period = table_open(PeriodRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_period_perrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(parent)); + + scan = systable_beginscan(pg_period, PeriodRelidNameIndexId, true, + NULL, 1, skey); + + if (HeapTupleIsValid(tuple = systable_getnext(scan))) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Inheriting from a table with a PERIOD is not supported"))); + + systable_endscan(scan); + table_close(pg_period, AccessShareLock); + table_close(relation, NoLock); + } + + /* + * Find the start & end columns and get their attno and type. + * In the same pass, make sure the period doesn't conflict with any column names. + * Also make sure the same period name isn't used more than once. + */ + foreach (entry, periods) + { + PeriodDef *period = lfirst(entry); + ListCell *entry2; + int i = 1; + Oid startcoltypid = InvalidOid; + Oid endcoltypid = InvalidOid; + Oid startcolcollation = InvalidOid; + Oid endcolcollation = InvalidOid; + + period->startattnum = InvalidAttrNumber; + period->endattnum = InvalidAttrNumber; + + if (SystemAttributeByName(period->periodname) != NULL) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("period name \"%s\" conflicts with a system column name", + period->periodname))); + + foreach (entry2, periods) + { + PeriodDef *period2 = lfirst(entry2); + + if (period != period2 && strcmp(period->periodname, period2->periodname) == 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("period name \"%s\" specified more than once", + period->periodname))); + } + + foreach (entry2, tableElts) + { + ColumnDef *col = lfirst(entry2); + int32 atttypmod; + AclResult aclresult; + + if (strcmp(period->periodname, col->colname) == 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("period name \"%s\" conflicts with a column name", + period->periodname))); + + if (strcmp(period->startcolname, col->colname) == 0) + { + period->startattnum = i; + + typenameTypeIdAndMod(NULL, col->typeName, &startcoltypid, &atttypmod); + + aclresult = object_aclcheck(TypeRelationId, startcoltypid, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, startcoltypid); + + startcolcollation = GetColumnDefCollation(NULL, col, startcoltypid); + } + + if (strcmp(period->endcolname, col->colname) == 0) + { + period->endattnum = i; + + typenameTypeIdAndMod(NULL, col->typeName, &endcoltypid, &atttypmod); + + aclresult = object_aclcheck(TypeRelationId, endcoltypid, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, endcoltypid); + + endcolcollation = GetColumnDefCollation(NULL, col, endcoltypid); + } + + i++; + } + + /* Did we find the columns? */ + if (period->startattnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + period->startcolname, relname))); + if (period->endattnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + period->endcolname, relname))); + + /* Both columns must be of same type */ + if (startcoltypid != endcoltypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("start and end columns of period must be of same type"))); + + /* Both columns must have the same collation */ + if (startcolcollation != endcolcollation) + ereport(ERROR, + (errcode(ERRCODE_COLLATION_MISMATCH), + errmsg("start and end columns of period must have same collation"))); + + period->coltypid = startcoltypid; + period->rngtypid = choose_rangetype_for_period(period); + } + + return periods; +} + /* * MergeCheckConstraint * Try to merge an inherited CHECK constraint with previous ones @@ -4217,12 +4691,12 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, * existing query plans. On the assumption it's not used for such, we * don't have to reject pending AFTER triggers, either. * - * Also, since we don't have an AlterTableUtilityContext, this cannot be + * Also, if you don't pass an AlterTableUtilityContext, this cannot be * used for any subcommand types that require parse transformation or * could generate subcommands that have to be passed to ProcessUtility. */ void -AlterTableInternal(Oid relid, List *cmds, bool recurse) +AlterTableInternal(Oid relid, List *cmds, bool recurse, AlterTableUtilityContext *context) { Relation rel; LOCKMODE lockmode = AlterTableGetLockLevel(cmds); @@ -4231,7 +4705,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse) EventTriggerAlterTableRelid(relid); - ATController(NULL, rel, cmds, recurse, lockmode, NULL); + ATController(NULL, rel, cmds, recurse, lockmode, context); } /* @@ -4324,6 +4798,8 @@ AlterTableGetLockLevel(List *cmds) case AT_EnableReplicaRule: /* may change SELECT rules */ case AT_EnableRule: /* may change SELECT rules */ case AT_DisableRule: /* may change SELECT rules */ + case AT_AddPeriod: /* shares namespace with columns, adds constraint */ + case AT_DropPeriod: cmd_lockmode = AccessExclusiveLock; break; @@ -4647,6 +5123,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* This command never recurses */ pass = AT_PASS_ADD_OTHERCONSTR; break; + case AT_AddPeriod: /* ALTER TABLE ... ADD PERIOD FOR name (start, end) */ + ATSimplePermissions(cmd->subtype, rel, ATT_TABLE); + pass = AT_PASS_ADD_PERIOD; + break; + case AT_DropPeriod: /* ALTER TABLE ... DROP PERIOD FOR name */ + ATSimplePermissions(cmd->subtype, rel, ATT_TABLE); + pass = AT_PASS_DROP; + break; case AT_AddIdentity: ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); /* This command never recurses */ @@ -5042,6 +5526,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, case AT_CookedColumnDefault: /* add a pre-cooked default */ address = ATExecCookedColumnDefault(rel, cmd->num, cmd->def); break; + case AT_AddPeriod: + address = ATExecAddPeriod(rel, (PeriodDef *) cmd->def, context); + break; + case AT_DropPeriod: + ATExecDropPeriod(rel, cmd->name, cmd->behavior, + false, false, + cmd->missing_ok); + break; case AT_AddIdentity: cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, cur_pass, context); @@ -6182,6 +6674,8 @@ alter_table_type_to_string(AlterTableType cmdtype) case AT_AddColumn: case AT_AddColumnToView: return "ADD COLUMN"; + case AT_AddPeriod: + return "ADD PERIOD"; case AT_ColumnDefault: case AT_CookedColumnDefault: return "ALTER COLUMN ... SET DEFAULT"; @@ -6205,6 +6699,8 @@ alter_table_type_to_string(AlterTableType cmdtype) return "ALTER COLUMN ... SET COMPRESSION"; case AT_DropColumn: return "DROP COLUMN"; + case AT_DropPeriod: + return "DROP PERIOD"; case AT_AddIndex: case AT_ReAddIndex: return NULL; /* not real grammar */ @@ -7211,14 +7707,29 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* * If a new or renamed column will collide with the name of an existing * column and if_not_exists is false then error out, else do nothing. + * + * See also check_for_period_name_collision. */ static bool check_for_column_name_collision(Relation rel, const char *colname, bool if_not_exists) { - HeapTuple attTuple; + HeapTuple attTuple, perTuple; int attnum; + /* If the name exists as a period, we're done. */ + perTuple = SearchSysCache2(PERIODNAME, + ObjectIdGetDatum(RelationGetRelid(rel)), + PointerGetDatum(colname)); + if (HeapTupleIsValid(perTuple)) + { + ReleaseSysCache(perTuple); + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("column name \"%s\" conflicts with a period name", + colname))); + } + /* * this test is deliberately not attisdropped-aware, since if one tries to * add a column matching a dropped column name, it's gonna fail anyway. @@ -7262,6 +7773,78 @@ check_for_column_name_collision(Relation rel, const char *colname, return true; } +/* + * If a new period name will collide with the name of an existing column or + * period [and if_not_exists is false] then error out, else do nothing. + * + * See also check_for_column_name_collision. + */ +static bool +check_for_period_name_collision(Relation rel, const char *pername, + bool if_not_exists) +{ + HeapTuple attTuple, perTuple; + int attnum; + + /* TODO: implement IF [NOT] EXISTS for periods */ + Assert(!if_not_exists); + + /* If there is already a period with this name, then we're done. */ + perTuple = SearchSysCache2(PERIODNAME, + ObjectIdGetDatum(RelationGetRelid(rel)), + PointerGetDatum(pername)); + if (HeapTupleIsValid(perTuple)) + { + if (if_not_exists) + { + ReleaseSysCache(perTuple); + + ereport(NOTICE, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("period \"%s\" of relation \"%s\" already exists, skipping", + pername, RelationGetRelationName(rel)))); + return false; + } + + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("period \"%s\" of relation \"%s\" already exists", + pername, RelationGetRelationName(rel)))); + } + + /* + * this test is deliberately not attisdropped-aware, since if one tries to + * add a column matching a dropped column name, it's gonna fail anyway. + * + * XXX: Does this hold for periods? + */ + attTuple = SearchSysCache2(ATTNAME, + ObjectIdGetDatum(RelationGetRelid(rel)), + PointerGetDatum(pername)); + if (HeapTupleIsValid(attTuple)) + { + attnum = ((Form_pg_attribute) GETSTRUCT(attTuple))->attnum; + ReleaseSysCache(attTuple); + + /* + * We throw a different error message for conflicts with system column + * names, since they are normally not shown and the user might otherwise + * be confused about the reason for the conflict. + */ + if (attnum <= 0) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("period name \"%s\" conflicts with a system column name", + pername))); + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_COLUMN), + errmsg("period name \"%s\" conflicts with a column name", + pername))); + } + + return true; +} + /* * Install a column's dependency on its datatype. */ @@ -7790,6 +8373,154 @@ ATExecCookedColumnDefault(Relation rel, AttrNumber attnum, return address; } +/* + * ALTER TABLE ADD PERIOD + * + * Return the address of the period. + */ +static ObjectAddress +ATExecAddPeriod(Relation rel, PeriodDef *period, AlterTableUtilityContext *context) +{ + Relation attrelation; + ObjectAddress address = InvalidObjectAddress; + Constraint *constr; + Oid conoid, periodoid; + List *cmds = NIL; + AlterTableCmd *cmd; + + /* + * PERIOD FOR SYSTEM_TIME is not yet implemented, but make sure no one uses + * the name. + */ + if (strcmp(period->periodname, "system_time") == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PERIOD FOR SYSTEM_TIME is not supported"))); + + /* The period name must not already exist */ + (void) check_for_period_name_collision(rel, period->periodname, false); + + /* Parse options */ + transformPeriodOptions(period); + + attrelation = table_open(AttributeRelationId, RowExclusiveLock); + ValidatePeriod(rel, period); + + period->rngtypid = choose_rangetype_for_period(period); + + /* Make the CHECK constraint */ + constr = make_constraint_for_period(rel, period); + cmd = makeNode(AlterTableCmd); + cmd->subtype = AT_AddConstraint; + cmd->def = (Node *) constr; + cmds = lappend(cmds, cmd); + AlterTableInternal(RelationGetRelid(rel), cmds, true, context); + conoid = get_relation_constraint_oid(RelationGetRelid(rel), period->constraintname, false); + + /* Make the range column */ + ColumnDef *rangecol = make_range_column_for_period(period); + cmd = makeNode(AlterTableCmd); + cmd->subtype = AT_AddColumn; + cmd->def = (Node *) rangecol; + cmd->name = period->periodname; + cmd->recurse = false; /* no, let the PERIOD recurse instead */ + AlterTableInternal(RelationGetRelid(rel), list_make1(cmd), true, context); + period->rngattnum = get_attnum(RelationGetRelid(rel), period->periodname); + if (period->rngattnum == InvalidAttrNumber) + elog(ERROR, "missing attribute %s", period->periodname); + + /* Save the Period */ + periodoid = StorePeriod(rel, period->periodname, period->startattnum, period->endattnum, period->rngattnum, conoid); + + ObjectAddressSet(address, PeriodRelationId, periodoid); + + table_close(attrelation, RowExclusiveLock); + + return address; +} + +/* + * ALTER TABLE DROP PERIOD + * + * Like DROP COLUMN, we can't use the normal ALTER TABLE recursion mechanism. + */ +static void +ATExecDropPeriod(Relation rel, const char *periodName, + DropBehavior behavior, + bool recurse, bool recursing, + bool missing_ok) +{ + Relation pg_period; + Form_pg_period period; + SysScanDesc scan; + ScanKeyData key; + HeapTuple tuple; + bool found = false; + + /* At top level, permission check was done in ATPrepCmd, else do it */ + if (recursing) + ATSimplePermissions(AT_DropPeriod, rel, ATT_TABLE); + + pg_period = table_open(PeriodRelationId, RowExclusiveLock); + + /* + * Find and drop the target period + */ + ScanKeyInit(&key, + Anum_pg_period_perrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(RelationGetRelid(rel))); + scan = systable_beginscan(pg_period, PeriodRelidNameIndexId, + true, NULL, 1, &key); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + ObjectAddress perobj; + + period = (Form_pg_period) GETSTRUCT(tuple); + + if (strcmp(NameStr(period->pername), periodName) != 0) + continue; + + /* + * Perform the actual period deletion + */ + perobj.classId = PeriodRelationId; + perobj.objectId = period->oid; + perobj.objectSubId = 0; + + performDeletion(&perobj, behavior, 0); + + found = true; + + /* period found and dropped -- no need to keep looping */ + break; + } + + systable_endscan(scan); + + if (!found) + { + if (!missing_ok) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("period \"%s\" on relation \"%s\" does not exist", + periodName, RelationGetRelationName(rel)))); + } + else + { + ereport(NOTICE, + (errmsg("period \"%s\" on relation \"%s\" does not exist, skipping", + periodName, RelationGetRelationName(rel)))); + table_close(pg_period, RowExclusiveLock); + return; + } + } + + table_close(pg_period, RowExclusiveLock); +} + /* * ALTER TABLE ALTER COLUMN ADD IDENTITY * @@ -12939,6 +13670,15 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, RememberConstraintForRebuilding(foundObject.objectId, tab); break; + case OCLASS_PERIOD: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot alter type of a column used by a period"), + errdetail("%s depends on column \"%s\"", + getObjectDescription(&foundObject, false), + colName))); + break; + case OCLASS_REWRITE: /* XXX someday see if we can cope with revising views */ ereport(ERROR, @@ -14999,7 +15739,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt) EventTriggerAlterTableStart((Node *) stmt); /* OID is set by AlterTableInternal */ - AlterTableInternal(lfirst_oid(l), cmds, false); + AlterTableInternal(lfirst_oid(l), cmds, false, NULL); EventTriggerAlterTableEnd(); } diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 9bd77546b9..be4637287f 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -169,7 +169,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, } /* EventTriggerAlterTableStart called by ProcessUtilitySlow */ - AlterTableInternal(viewOid, atcmds, true); + AlterTableInternal(viewOid, atcmds, true, NULL); /* Make the new view columns visible */ CommandCounterIncrement(); @@ -201,7 +201,7 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, atcmds = list_make1(atcmd); /* EventTriggerAlterTableStart called by ProcessUtilitySlow */ - AlterTableInternal(viewOid, atcmds, true); + AlterTableInternal(viewOid, atcmds, true, NULL); /* * There is very little to do here to update the view's dependencies. diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index fd3349832d..aa1965e913 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -1641,6 +1641,9 @@ exprLocation(const Node *expr) case T_Constraint: loc = ((const Constraint *) expr)->location; break; + case T_PeriodDef: + loc = ((const PeriodDef *) expr)->location; + break; case T_FunctionParameter: /* just use typename's location */ loc = exprLocation((Node *) ((const FunctionParameter *) expr)->argType); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index c3ab65896f..7ca9682ff4 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -595,7 +595,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 TablePeriod %type TableLikeOptionList TableLikeOption %type column_compression opt_column_compression column_storage opt_column_storage %type ColQualList @@ -2603,6 +2603,24 @@ alter_table_cmd: n->def = (Node *) $4; $$ = (Node *) n; } + /* ALTER TABLE ADD PERIOD FOR (, ) */ + | ADD_P TablePeriod + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AddPeriod; + n->def = $2; + $$ = (Node *)n; + } + /* ALTER TABLE DROP PERIOD FOR [RESTRICT|CASCADE] */ + | DROP PERIOD FOR name opt_drop_behavior + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_DropPeriod; + n->name = $4; + n->behavior = $5; + n->missing_ok = false; + $$ = (Node *)n; + } /* ALTER TABLE ADD CONSTRAINT ... */ | ADD_P TableConstraint { @@ -3714,8 +3732,10 @@ TableElement: columnDef { $$ = $1; } | TableLikeClause { $$ = $1; } | TableConstraint { $$ = $1; } + | TablePeriod { $$ = $1; } ; + TypedTableElement: columnOptions { $$ = $1; } | TableConstraint { $$ = $1; } @@ -4065,6 +4085,19 @@ TableLikeOption: ; +TablePeriod: + PERIOD FOR name '(' name ',' name ')' opt_definition + { + PeriodDef *n = makeNode(PeriodDef); + n->periodname = $3; + n->startcolname = $5; + n->endcolname = $7; + n->options = $9; + n->location = @1; + $$ = (Node *) n; + } + ; + /* ConstraintElem specifies constraint syntax which is not embedded into * a column definition. ColConstraintElem specifies the embedded form. * - thomas 1997-12-03 @@ -7083,6 +7116,14 @@ CommentStmt: n->comment = $9; $$ = (Node *) n; } + | COMMENT ON PERIOD any_name IS comment_text + { + CommentStmt *n = makeNode(CommentStmt); + n->objtype = OBJECT_PERIOD; + n->object = (Node *) $4; + n->comment = $6; + $$ = (Node *) n; + } | COMMENT ON LARGE_P OBJECT_P NumericOnly IS comment_text { CommentStmt *n = makeNode(CommentStmt); diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 04ca087973..c90bcd058f 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -80,6 +80,7 @@ typedef struct bool isforeign; /* true if CREATE/ALTER FOREIGN TABLE */ bool isalter; /* true if altering existing table */ List *columns; /* ColumnDef items */ + List *periods; /* PeriodDef items */ List *ckconstraints; /* CHECK constraints */ List *fkconstraints; /* FOREIGN KEY constraints */ List *ixconstraints; /* index-creating constraints */ @@ -110,6 +111,8 @@ typedef struct static void transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column); +static void transformTablePeriod(CreateStmtContext *cxt, + PeriodDef *period); static void transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint); static void transformTableLikeClause(CreateStmtContext *cxt, @@ -241,6 +244,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) cxt.inhRelations = stmt->inhRelations; cxt.isalter = false; cxt.columns = NIL; + cxt.periods = NIL; cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; @@ -280,6 +284,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) transformColumnDefinition(&cxt, (ColumnDef *) element); break; + case T_PeriodDef: + transformTablePeriod(&cxt, (PeriodDef *) element); + break; + case T_Constraint: transformTableConstraint(&cxt, (Constraint *) element); break; @@ -347,6 +355,7 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString) * Output results. */ stmt->tableElts = cxt.columns; + stmt->periods = cxt.periods; stmt->constraints = cxt.ckconstraints; result = lappend(cxt.blist, stmt); @@ -866,6 +875,113 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column) } } +void +transformPeriodOptions(PeriodDef *period) +{ + ListCell *option; + DefElem *dconstraintname = NULL; + DefElem *drangetypename = NULL; + + foreach(option, period->options) + { + DefElem *defel = (DefElem *) lfirst(option); + + if (strcmp(defel->defname, "check_constraint_name") == 0) + { + if (dconstraintname) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + dconstraintname = defel; + } + else if (strcmp(defel->defname, "rangetype") == 0) + { + if (drangetypename) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + drangetypename = defel; + } + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("option \"%s\" not recognized", defel->defname))); + } + + if (dconstraintname != NULL) + period->constraintname = defGetString(dconstraintname); + else + period->constraintname = NULL; + + if (drangetypename != NULL) + period->rangetypename = defGetString(drangetypename); + else + period->rangetypename = NULL; +} + +/* + * transformTablePeriod + * transform a PeriodDef node within CREATE TABLE or ALTER TABLE + * TODO: Does ALTER TABLE really call us?? It doesn't seem like it does. Maybe it should. Did it in Vik's original patch? + */ +static void +transformTablePeriod(CreateStmtContext *cxt, PeriodDef *period) +{ + // Oid coltypid; + // ColumnDef *col; + // ListCell *columns; + + if (strcmp(period->periodname, "system_time") == 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PERIOD FOR SYSTEM_TIME is not supported"), + parser_errposition(cxt->pstate, + period->location))); + + if (strcmp(period->startcolname, period->endcolname) == 0) + ereport(ERROR, (errmsg("column \"%s\" can't be the start and end column for period \"%s\"", + period->startcolname, period->periodname))); + + /* + * Determine the column info and range type so that transformIndexConstraints + * knows how to create PRIMARY KEY/UNIQUE constraints using this PERIOD. + */ + transformPeriodOptions(period); + + /* + * Find a suitable range type for operations involving this period. + * Use the rangetype option if provided, otherwise try to find a + * non-ambiguous existing type. + */ + +#ifdef asdsdfa + /* First find out the type of the period's columns */ + // TODO: I'm doing this in DefineRelation now, + // which seems like a better place since it knows about inherited columns. + period->coltypid = InvalidOid; + period->rngtypid = InvalidOid; + foreach (columns, cxt->columns) + { + col = (ColumnDef *) lfirst(columns); + if (strcmp(col->colname, period->startcolname) == 0) + { + coltypid = typenameTypeId(cxt->pstate, col->typeName); + break; + } + } + if (coltypid == InvalidOid) + ereport(ERROR, (errmsg("column \"%s\" of relation \"%s\" does not exist", + period->startcolname, cxt->relation->relname))); + + period->coltypid = coltypid; + + /* Now make sure it matches rangetypename or we can find a matching range */ + period->rngtypid = choose_rangetype_for_period(period); +#endif + + cxt->periods = lappend(cxt->periods, period); +} + /* * transformTableConstraint * transform a Constraint node within CREATE TABLE or ALTER TABLE @@ -1539,6 +1655,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, Oid keycoltype; Datum datum; bool isnull; + PeriodDef *period; if (constraintOid) *constraintOid = InvalidOid; @@ -1596,6 +1713,11 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, index->if_not_exists = false; index->reset_default_tblspc = false; + /* Copy the period */ + period = makeNode(PeriodDef); + period->oid = idxrec->indperiod; + index->period = period; + /* * We don't try to preserve the name of the source index; instead, just * let DefineIndex() choose a reasonable name. (If we tried to preserve @@ -2986,6 +3108,10 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) } } + /* take care of the period */ + if (stmt->period) + stmt->period->oid = get_period_oid(relid, stmt->period->periodname, false); + /* * Check that only the base rel is mentioned. (This should be dead code * now that add_missing_from is history.) @@ -3443,6 +3569,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.inhRelations = NIL; cxt.isalter = true; cxt.columns = NIL; + cxt.periods = NIL; cxt.ckconstraints = NIL; cxt.fkconstraints = NIL; cxt.ixconstraints = NIL; @@ -3504,6 +3631,15 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, (int) nodeTag(cmd->def)); break; + case AT_AddPeriod: + { + newcmds = lappend(newcmds, cmd); + // Why not call transformTablePeriod here? + // Ah because it looks at cxt->columns + // and in an ALTER statement the columns might already exist (or not). + break; + } + case AT_AlterColumnType: { ColumnDef *def = castNode(ColumnDef, cmd->def); diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 353e9f2cad..9ebfea971e 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -30,6 +30,7 @@ #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" #include "catalog/pg_operator.h" +#include "catalog/pg_period.h" #include "catalog/pg_proc.h" #include "catalog/pg_range.h" #include "catalog/pg_statistic.h" @@ -1021,6 +1022,68 @@ get_attoptions(Oid relid, int16 attnum) return result; } +/* ---------- PG_PERIOD CACHE ---------- */ + +/* + * get_periodname - given its OID, look up a period + * + * If missing_ok is false, throw an error if the period is not found. + * If true, just return InvalidOid. + */ +char * +get_periodname(Oid periodid, bool missing_ok) +{ + HeapTuple tp; + + tp = SearchSysCache1(PERIODOID, + ObjectIdGetDatum(periodid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_period period_tup = (Form_pg_period) GETSTRUCT(tp); + char *result; + + result = pstrdup(NameStr(period_tup->pername)); + ReleaseSysCache(tp); + return result; + } + + if (!missing_ok) + elog(ERROR, "cache lookup failed for period %d", + periodid); + return NULL; +} + +/* + * get_period_oid - gets its relation and name, look up a period + * + * If missing_ok is false, throw an error if the cast is not found. If + * true, just return InvalidOid. + */ +Oid +get_period_oid(Oid relid, const char *periodname, bool missing_ok) +{ + HeapTuple tp; + + tp = SearchSysCache2(PERIODNAME, + ObjectIdGetDatum(relid), + PointerGetDatum(periodname)); + + if (HeapTupleIsValid(tp)) + { + Form_pg_period period_tup = (Form_pg_period) GETSTRUCT(tp); + Oid result; + + result = period_tup->oid; + ReleaseSysCache(tp); + return result; + } + + if (!missing_ok) + elog(ERROR, "cache lookup failed for period %s", + periodname); + return InvalidOid; +} + /* ---------- PG_CAST CACHE ---------- */ /* @@ -2172,6 +2235,32 @@ get_typname(Oid typid) return NULL; } +/* + * get_typname_and_namespace + * + * Returns the name and namespace of a given type + * + * Returns true if one found, or false if not. + */ +bool +get_typname_and_namespace(Oid typid, char **typname, char **typnamespace) +{ + HeapTuple tp; + + tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid)); + if (HeapTupleIsValid(tp)) + { + Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp); + + *typname = pstrdup(NameStr(typtup->typname)); + *typnamespace = get_namespace_name(typtup->typnamespace); + ReleaseSysCache(tp); + return *typnamespace; + } + else + return false; +} + /* * get_typlen * @@ -3482,6 +3571,30 @@ get_multirange_range(Oid multirangeOid) return InvalidOid; } +Oid +get_subtype_range(Oid subtypeOid) +{ + CatCList *catlist; + Oid result = InvalidOid; + + catlist = SearchSysCacheList1(RANGESUBTYPE, ObjectIdGetDatum(subtypeOid)); + + if (catlist->n_members == 1) + { + HeapTuple tuple = &catlist->members[0]->tuple; + Form_pg_range rngtup = (Form_pg_range) GETSTRUCT(tuple); + result = rngtup->rngtypid; + ReleaseCatCacheList(catlist); + } + else if (catlist->n_members > 1) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_DATATYPE), + errmsg("ambiguous range for type %s", + format_type_be(subtypeOid)))); + + return result; +} + /* ---------- PG_INDEX CACHE ---------- */ /* diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 4e4a34bde8..2a3503a289 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -49,6 +49,7 @@ #include "catalog/pg_opfamily.h" #include "catalog/pg_parameter_acl.h" #include "catalog/pg_partitioned_table.h" +#include "catalog/pg_period.h" #include "catalog/pg_proc.h" #include "catalog/pg_publication.h" #include "catalog/pg_publication_namespace.h" @@ -422,6 +423,19 @@ static const struct cachedesc cacheinfo[] = { KEY(Anum_pg_partitioned_table_partrelid), 32 }, + [PERIODNAME] = { + PeriodRelationId, + PeriodRelidNameIndexId, + KEY(Anum_pg_period_perrelid, + Anum_pg_period_pername), + 32 + }, + [PERIODOID] = { + PeriodRelationId, + PeriodObjectIndexId, + KEY(Anum_pg_period_oid), + 32 + }, [PROCNAMEARGSNSP] = { ProcedureRelationId, ProcedureNameArgsNspIndexId, @@ -480,6 +494,13 @@ static const struct cachedesc cacheinfo[] = { KEY(Anum_pg_range_rngmultitypid), 4 }, + [RANGESUBTYPE] = { + RangeRelationId, + RangeSubTypidTypidIndexId, + KEY(Anum_pg_range_rngsubtype, + Anum_pg_range_rngtypid), + 4 + }, [RANGETYPE] = { RangeRelationId, RangeTypidIndexId, diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 39ebcfec32..191eba1990 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3517,6 +3517,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te) strcmp(type, "DATABASE PROPERTIES") == 0 || strcmp(type, "DEFAULT") == 0 || strcmp(type, "FK CONSTRAINT") == 0 || + strcmp(type, "PERIOD") == 0 || strcmp(type, "INDEX") == 0 || strcmp(type, "RULE") == 0 || strcmp(type, "TRIGGER") == 0 || diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index a95e1d3696..5b81a7e5a0 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6325,6 +6325,7 @@ getTables(Archive *fout, int *numTables) int i_reltype; int i_relowner; int i_relchecks; + int i_nperiod; int i_relhasindex; int i_relhasrules; int i_relpages; @@ -6402,6 +6403,14 @@ getTables(Archive *fout, int *numTables) appendPQExpBufferStr(query, "c.relhasoids, "); + /* In PG16 upwards we have PERIODs. */ + if (fout->remoteVersion >= 160000) + appendPQExpBufferStr(query, + "(SELECT count(*) FROM pg_period WHERE perrelid = c.oid) AS nperiods, "); + else + appendPQExpBufferStr(query, + "0 AS nperiods, "); + if (fout->remoteVersion >= 90300) appendPQExpBufferStr(query, "c.relispopulated, "); @@ -6539,6 +6548,7 @@ getTables(Archive *fout, int *numTables) i_reltype = PQfnumber(res, "reltype"); i_relowner = PQfnumber(res, "relowner"); i_relchecks = PQfnumber(res, "relchecks"); + i_nperiod = PQfnumber(res, "nperiods"); i_relhasindex = PQfnumber(res, "relhasindex"); i_relhasrules = PQfnumber(res, "relhasrules"); i_relpages = PQfnumber(res, "relpages"); @@ -6622,6 +6632,7 @@ getTables(Archive *fout, int *numTables) } tblinfo[i].reltablespace = pg_strdup(PQgetvalue(res, i, i_reltablespace)); tblinfo[i].hasoids = (strcmp(PQgetvalue(res, i, i_relhasoids), "t") == 0); + tblinfo[i].nperiod = atoi(PQgetvalue(res, i, i_nperiod)); tblinfo[i].hastriggers = (strcmp(PQgetvalue(res, i, i_relhastriggers), "t") == 0); tblinfo[i].relpersistence = *(PQgetvalue(res, i, i_relpersistence)); tblinfo[i].relispopulated = (strcmp(PQgetvalue(res, i, i_relispopulated), "t") == 0); @@ -8351,7 +8362,7 @@ getTransforms(Archive *fout, int *numTransforms) /* * getTableAttrs - * for each interesting table, read info about its attributes - * (names, types, default values, CHECK constraints, etc) + * (names, types, default values, CHECK constraints, PERIODs, etc) * * modifies tblinfo */ @@ -8401,6 +8412,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) for (int i = 0; i < numTables; i++) { TableInfo *tbinfo = &tblinfo[i]; + int ndumpablechecks; /* number of CHECK constraints that do + not belong to a period */ /* Don't bother to collect info for sequences */ if (tbinfo->relkind == RELKIND_SEQUENCE) @@ -8415,7 +8428,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) appendPQExpBufferChar(tbloids, ','); appendPQExpBuffer(tbloids, "%u", tbinfo->dobj.catId.oid); - if (tbinfo->ncheck > 0) + ndumpablechecks = tbinfo->ncheck - tbinfo->nperiod; + if (ndumpablechecks > 0) { /* Also make a list of the ones with check constraints */ if (checkoids->len > 1) /* do we have more than the '{'? */ @@ -8774,15 +8788,36 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) pg_log_info("finding table check constraints"); resetPQExpBuffer(q); - appendPQExpBuffer(q, - "SELECT c.tableoid, c.oid, conrelid, conname, " - "pg_catalog.pg_get_constraintdef(c.oid) AS consrc, " - "conislocal, convalidated " - "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" - "JOIN pg_catalog.pg_constraint c ON (src.tbloid = c.conrelid)\n" - "WHERE contype = 'c' " - "ORDER BY c.conrelid, c.conname", - checkoids->data); + if (fout->remoteVersion >= 160000) + { + /* + * PERIODs were added in v17 and we don't dump CHECK + * constraints for them. + */ + appendPQExpBuffer(q, + "SELECT c.tableoid, c.oid, conrelid, conname, " + "pg_catalog.pg_get_constraintdef(c.oid) AS consrc, " + "conislocal, convalidated " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_constraint c ON (src.tbloid = c.conrelid)\n" + "WHERE contype = 'c' " + " AND NOT EXISTS (SELECT FROM pg_period " + " WHERE (perrelid, perconstraint) = (conrelid, c.oid)) " + "ORDER BY c.conrelid, c.conname", + checkoids->data); + } + else + { + appendPQExpBuffer(q, + "SELECT c.tableoid, c.oid, conrelid, conname, " + "pg_catalog.pg_get_constraintdef(c.oid) AS consrc, " + "conislocal, convalidated " + "FROM unnest('%s'::pg_catalog.oid[]) AS src(tbloid)\n" + "JOIN pg_catalog.pg_constraint c ON (src.tbloid = c.conrelid)\n" + "WHERE contype = 'c' " + "ORDER BY c.conrelid, c.conname", + checkoids->data); + } res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK); @@ -8804,6 +8839,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) Oid conrelid = atooid(PQgetvalue(res, j, i_conrelid)); TableInfo *tbinfo = NULL; int numcons; + int ndumpablechecks; /* Count rows for this table */ for (numcons = 1; numcons < numConstrs - j; numcons++) @@ -8823,12 +8859,13 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) if (curtblindx >= numTables) pg_fatal("unrecognized table OID %u", conrelid); - if (numcons != tbinfo->ncheck) + ndumpablechecks = tbinfo->ncheck - tbinfo->nperiod; + if (numcons != ndumpablechecks) { pg_log_error(ngettext("expected %d check constraint on table \"%s\" but found %d", "expected %d check constraints on table \"%s\" but found %d", - tbinfo->ncheck), - tbinfo->ncheck, tbinfo->dobj.name, numcons); + ndumpablechecks), + ndumpablechecks, tbinfo->dobj.name, numcons); pg_log_error_hint("The system catalogs might be corrupted."); exit_nicely(1); } @@ -8887,6 +8924,79 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables) PQclear(res); } + for (int i = 0; i < numTables; i++) + { + TableInfo *tbinfo = &tblinfo[i]; + + /* + * Get info about PERIOD definitions + */ + if (tbinfo->nperiod > 0) + { + PeriodInfo *periods; + int numPeriods; + int j; + + /* We shouldn't have any periods before v17 */ + Assert(fout->remoteVersion >= 160000); + + pg_log_info("finding periods for table \"%s.%s\"", + tbinfo->dobj.namespace->dobj.name, + tbinfo->dobj.name); + + resetPQExpBuffer(q); + appendPQExpBuffer(q, + "SELECT p.tableoid, p.oid, p.pername, " + " sa.attname AS perstart, ea.attname AS perend, " + " r.typname AS rngtype, " + " c.conname AS conname " + "FROM pg_catalog.pg_period AS p " + "JOIN pg_catalog.pg_attribute AS sa ON (sa.attrelid, sa.attnum) = (p.perrelid, p.perstart) " + "JOIN pg_catalog.pg_attribute AS ea ON (ea.attrelid, ea.attnum) = (p.perrelid, p.perend) " + "JOIN pg_catalog.pg_type AS r ON r.oid = p.perrngtype " + "JOIN pg_catalog.pg_constraint AS c ON c.oid = p.perconstraint " + "WHERE p.perrelid = '%u'::pg_catalog.oid " + "ORDER BY p.pername", + tbinfo->dobj.catId.oid); + + res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK); + + /* + * If we didn't get the number of rows we thought we were going to, + * then those JOINs didn't work. + */ + numPeriods = PQntuples(res); + if (numPeriods != tbinfo->nperiod) + { + pg_log_info(ngettext("expected %d period on table \"%s\" but found %d", + "expected %d periods on table \"%s\" but found %d", + tbinfo->nperiod), + tbinfo->nperiod, tbinfo->dobj.name, numPeriods); + pg_log_info("(The system catalogs might be corrupted.)"); + exit_nicely(1); + } + + periods = (PeriodInfo *) pg_malloc(numPeriods * sizeof(PeriodInfo)); + tbinfo->periods = periods; + + for (j = 0; j < numPeriods; j++) + { + periods[j].dobj.objType = DO_PERIOD; + periods[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, 0)); + periods[j].dobj.catId.oid = atooid(PQgetvalue(res, j, 1)); + AssignDumpId(&periods[j].dobj); + periods[j].dobj.name = pg_strdup(PQgetvalue(res, j, 2)); + periods[j].dobj.namespace = tbinfo->dobj.namespace; + periods[j].pertable = tbinfo; + periods[j].perstart = pg_strdup(PQgetvalue(res, j, 3)); + periods[j].perend = pg_strdup(PQgetvalue(res, j, 4)); + periods[j].rngtype = pg_strdup(PQgetvalue(res, j, 5)); + periods[j].conname = pg_strdup(PQgetvalue(res, j, 6)); + } + PQclear(res); + } + } + destroyPQExpBuffer(q); destroyPQExpBuffer(tbloids); destroyPQExpBuffer(checkoids); @@ -10157,6 +10267,8 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj) case DO_FK_CONSTRAINT: dumpConstraint(fout, (const ConstraintInfo *) dobj); break; + case DO_PERIOD: + break; case DO_PROCLANG: dumpProcLang(fout, (const ProcLangInfo *) dobj); break; @@ -15645,6 +15757,32 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) } } + /* + * Add non-inherited PERIOD definitions, if any. + */ + for (j = 0; j < tbinfo->nperiod; j++) + { + PeriodInfo *period = &(tbinfo->periods[j]); + + char *name = pg_strdup(fmtId(period->dobj.name)); + char *start = pg_strdup(fmtId(period->perstart)); + char *end = pg_strdup(fmtId(period->perend)); + char *rngtype = pg_strdup(fmtId(period->rngtype)); + char *conname = pg_strdup(fmtId(period->conname)); + + if (actual_atts == 0) + appendPQExpBufferStr(q, " (\n "); + else + appendPQExpBufferStr(q, ",\n "); + + appendPQExpBuffer(q, "PERIOD FOR %s (%s, %s) " + "WITH (rangetype = %s, check_constraint_name = %s)", + name, start, end, + rngtype, conname); + + actual_atts++; + } + /* * Add non-inherited CHECK constraints, if any. * @@ -15653,7 +15791,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) * PARTITION that we'll emit later expects the constraint to be * there. (No need to fix conislocal: ATTACH PARTITION does that) */ - for (j = 0; j < tbinfo->ncheck; j++) + for (j = 0; j < tbinfo->ncheck - tbinfo->nperiod; j++) { ConstraintInfo *constr = &(tbinfo->checkexprs[j]); @@ -15852,7 +15990,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) * For partitions, they were already dumped, and conislocal * doesn't need fixing. */ - for (k = 0; k < tbinfo->ncheck; k++) + for (k = 0; k < tbinfo->ncheck - tbinfo->nperiod; k++) { ConstraintInfo *constr = &(tbinfo->checkexprs[k]); @@ -16143,7 +16281,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) dumpTableSecLabel(fout, tbinfo, reltypename); /* Dump comments on inlined table constraints */ - for (j = 0; j < tbinfo->ncheck; j++) + for (j = 0; j < tbinfo->ncheck - tbinfo->nperiod; j++) { ConstraintInfo *constr = &(tbinfo->checkexprs[j]); @@ -18231,6 +18369,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs, case DO_TRIGGER: case DO_EVENT_TRIGGER: case DO_DEFAULT_ACL: + case DO_PERIOD: case DO_POLICY: case DO_PUBLICATION: case DO_PUBLICATION_REL: diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 2afb5d5294..3ffea100da 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -59,6 +59,7 @@ typedef enum DO_TRIGGER, DO_CONSTRAINT, DO_FK_CONSTRAINT, /* see note for ConstraintInfo */ + DO_PERIOD, DO_PROCLANG, DO_CAST, DO_TABLE_DATA, @@ -299,12 +300,14 @@ typedef struct _tableInfo bool rowsec; /* is row security enabled? */ bool forcerowsec; /* is row security forced? */ bool hasoids; /* does it have OIDs? */ + bool hasperiods; /* does it have any periods? */ uint32 frozenxid; /* table's relfrozenxid */ uint32 minmxid; /* table's relminmxid */ Oid toast_oid; /* toast table's OID, or 0 if none */ uint32 toast_frozenxid; /* toast table's relfrozenxid, if any */ uint32 toast_minmxid; /* toast table's relminmxid */ int ncheck; /* # of CHECK expressions */ + int nperiod; /* # of PERIOD definitions */ Oid reltype; /* OID of table's composite type, if any */ Oid reloftype; /* underlying type for typed table */ Oid foreign_server; /* foreign server oid, if applicable */ @@ -349,6 +352,7 @@ typedef struct _tableInfo bool *inhNotNull; /* true if NOT NULL is inherited */ struct _attrDefInfo **attrdefs; /* DEFAULT expressions */ struct _constraintInfo *checkexprs; /* CHECK constraints */ + struct _periodInfo *periods; /* PERIOD definitions */ bool needs_override; /* has GENERATED ALWAYS AS IDENTITY */ char *amname; /* relation access method */ @@ -487,6 +491,16 @@ typedef struct _constraintInfo bool withoutoverlaps; /* true if the last elem is WITHOUT OVERLAPS */ } ConstraintInfo; +typedef struct _periodInfo +{ + DumpableObject dobj; + TableInfo *pertable; + char *perstart; /* the name of the start column */ + char *perend; /* the name of the end column */ + char *rngtype; /* the name of the range type */ + char *conname; /* the name of the CHECK constraint */ +} PeriodInfo; + typedef struct _procLangInfo { DumpableObject dobj; diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 523a19c155..55f2b68d0f 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -84,6 +84,7 @@ enum dbObjectTypePriorities PRIO_CONSTRAINT, PRIO_INDEX, PRIO_INDEX_ATTACH, + PRIO_PERIOD, PRIO_STATSEXT, PRIO_RULE, PRIO_TRIGGER, @@ -118,6 +119,7 @@ static const int dbObjectTypePriority[] = PRIO_ATTRDEF, /* DO_ATTRDEF */ PRIO_INDEX, /* DO_INDEX */ PRIO_INDEX_ATTACH, /* DO_INDEX_ATTACH */ + PRIO_PERIOD, /* DO_PERIOD */ PRIO_STATSEXT, /* DO_STATSEXT */ PRIO_RULE, /* DO_RULE */ PRIO_TRIGGER, /* DO_TRIGGER */ @@ -1438,6 +1440,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) "FK CONSTRAINT %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); return; + case DO_PERIOD: + snprintf(buf, bufsize, + "PERIOD %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; case DO_PROCLANG: snprintf(buf, bufsize, "PROCEDURAL LANGUAGE %s (ID %d OID %u)", diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 2846489eb0..1c974efff5 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -2370,6 +2370,40 @@ describeOneTableDetails(const char *schemaname, PGresult *result = NULL; int tuples = 0; + /* print periods */ + if (pset.sversion >= 150000) + { + printfPQExpBuffer(&buf, + "SELECT quote_ident(p.pername), quote_ident(s.attname) AS startatt, quote_ident(e.attname) AS endatt\n" + "FROM pg_period AS p\n" + "JOIN pg_attribute AS s ON (s.attrelid, s.attnum) = (p.perrelid, p.perstart)\n" + "JOIN pg_attribute AS e ON (e.attrelid, e.attnum) = (p.perrelid, p.perend)\n" + "WHERE p.perrelid = '%s'\n" + "ORDER BY 1;", + oid); + result = PSQLexec(buf.data); + if (!result) + goto error_return; + else + tuples = PQntuples(result); + + if (tuples > 0) + { + printTableAddFooter(&cont, _("Periods:")); + for (i = 0; i < tuples; i++) + { + /* untranslated constraint name and def */ + printfPQExpBuffer(&buf, " %s (%s, %s)", + PQgetvalue(result, i, 0), + PQgetvalue(result, i, 1), + PQgetvalue(result, i, 2)); + + printTableAddFooter(&cont, buf.data); + } + } + PQclear(result); + } + /* print indexes */ if (tableinfo.hasindex) { diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index ffd5e9dc82..8f03bf3de1 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -93,6 +93,7 @@ typedef enum ObjectClass OCLASS_CAST, /* pg_cast */ OCLASS_COLLATION, /* pg_collation */ OCLASS_CONSTRAINT, /* pg_constraint */ + OCLASS_PERIOD, /* pg_period */ OCLASS_CONVERSION, /* pg_conversion */ OCLASS_DEFAULT, /* pg_attrdef */ OCLASS_LANGUAGE, /* pg_language */ diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index d01ab504b6..31ec0f8c34 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -117,6 +117,10 @@ extern List *AddRelationNewConstraints(Relation rel, extern void RelationClearMissing(Relation rel); extern void SetAttrMissing(Oid relid, char *attname, char *value); +extern Oid StorePeriod(Relation rel, const char *period, + AttrNumber startnum, AttrNumber endnum, + AttrNumber rangenum, Oid conoid); + extern Node *cookDefault(ParseState *pstate, Node *raw_default, Oid atttypid, diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h index b0592571da..6af677d814 100644 --- a/src/include/catalog/pg_index.h +++ b/src/include/catalog/pg_index.h @@ -44,11 +44,11 @@ CATALOG(pg_index,2610,IndexRelationId) BKI_SCHEMA_MACRO bool indisready; /* is this index ready for inserts? */ bool indislive; /* is this index alive at all? */ bool indisreplident; /* is this index the identity for replication? */ + Oid indperiod; /* the period it contains, if any */ /* variable-length fields start here, but we allow direct access to indkey */ int2vector indkey BKI_FORCE_NOT_NULL; /* column numbers of indexed cols, * or 0 */ - #ifdef CATALOG_VARLEN oidvector indcollation BKI_LOOKUP_OPT(pg_collation) BKI_FORCE_NOT_NULL; /* collation identifiers */ oidvector indclass BKI_LOOKUP(pg_opclass) BKI_FORCE_NOT_NULL; /* opclass identifiers */ diff --git a/src/include/catalog/pg_period.h b/src/include/catalog/pg_period.h new file mode 100644 index 0000000000..f4066cdb06 --- /dev/null +++ b/src/include/catalog/pg_period.h @@ -0,0 +1,53 @@ +/*------------------------------------------------------------------------- + * + * pg_period.h + * definition of the "period" system catalog (pg_period) + * + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * + * src/include/catalog/pg_period.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_PERIOD_H +#define PG_PERIOD_H + +#include "catalog/genbki.h" +#include "catalog/pg_period_d.h" + +/* ---------------- + * pg_period definition. cpp turns this into + * typedef struct FormData_pg_period + * ---------------- + */ +CATALOG(pg_period,8000,PeriodRelationId) +{ + Oid oid; /* OID of the period */ + NameData pername; /* name of period */ + Oid perrelid; /* OID of relation containing this period */ + int16 perstart; /* column for start value */ + int16 perend; /* column for end value */ + int16 perrange; /* column for range value */ + Oid perconstraint; /* OID of (start < end) constraint */ +} FormData_pg_period; + +/* ---------------- + * Form_pg_period corresponds to a pointer to a tuple with + * the format of pg_period relation. + * ---------------- + */ +typedef FormData_pg_period *Form_pg_period; + +DECLARE_UNIQUE_INDEX_PKEY(pg_period_oid_index, 8001, PeriodObjectIndexId, on pg_period using btree(oid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_period_perrelid_pername_index, 8002, PeriodRelidNameIndexId, on pg_period using btree(perrelid oid_ops, pername name_ops)); + +extern void RemovePeriodById(Oid periodId); + +extern Oid get_relation_period_oid(Oid relid, const char *pername, bool missing_ok); + +#endif /* PG_PERIOD_H */ diff --git a/src/include/catalog/pg_range.h b/src/include/catalog/pg_range.h index d03ef18851..f3031070f7 100644 --- a/src/include/catalog/pg_range.h +++ b/src/include/catalog/pg_range.h @@ -59,6 +59,7 @@ typedef FormData_pg_range *Form_pg_range; DECLARE_UNIQUE_INDEX_PKEY(pg_range_rngtypid_index, 3542, RangeTypidIndexId, on pg_range using btree(rngtypid oid_ops)); DECLARE_UNIQUE_INDEX(pg_range_rngmultitypid_index, 2228, RangeMultirangeTypidIndexId, on pg_range using btree(rngmultitypid oid_ops)); +DECLARE_UNIQUE_INDEX(pg_range_rngsubtype_rngtypid_index, 8003, RangeSubTypidTypidIndexId, on pg_range using btree(rngsubtype oid_ops, rngtypid oid_ops)); /* * prototypes for functions in pg_range.c diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 250d89ff88..419cf1e84f 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -38,7 +38,8 @@ extern LOCKMODE AlterTableGetLockLevel(List *cmds); extern void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode); -extern void AlterTableInternal(Oid relid, List *cmds, bool recurse); +extern void AlterTableInternal(Oid relid, List *cmds, bool recurse, + struct AlterTableUtilityContext *context); extern Oid AlterTableMoveAll(AlterTableMoveAllStmt *stmt); @@ -104,5 +105,6 @@ extern void RangeVarCallbackOwnsRelation(const RangeVar *relation, Oid relId, Oid oldRelId, void *arg); extern bool PartConstraintImpliedByRelConstraint(Relation scanrel, List *partConstraint); +extern Oid choose_rangetype_for_period(PeriodDef *period); #endif /* TABLECMDS_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 7b7e64c99f..6d107259b9 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2127,6 +2127,7 @@ typedef enum ObjectType OBJECT_OPERATOR, OBJECT_OPFAMILY, OBJECT_PARAMETER_ACL, + OBJECT_PERIOD, OBJECT_POLICY, OBJECT_PROCEDURE, OBJECT_PUBLICATION, @@ -2214,6 +2215,8 @@ typedef enum AlterTableType AT_ValidateConstraint, /* validate constraint */ AT_AddIndexConstraint, /* add constraint using existing index */ AT_DropConstraint, /* drop constraint */ + AT_AddPeriod, /* ADD PERIOD */ + AT_DropPeriod, /* DROP PERIOD */ AT_ReAddComment, /* internal to commands/tablecmds.c */ AT_AlterColumnType, /* alter column type */ AT_AlterColumnGenericOptions, /* alter column OPTIONS (...) */ @@ -2480,9 +2483,9 @@ typedef struct VariableShowStmt /* ---------------------- * Create Table Statement * - * NOTE: in the raw gram.y output, ColumnDef and Constraint nodes are - * intermixed in tableElts, and constraints is NIL. After parse analysis, - * tableElts contains just ColumnDefs, and constraints contains just + * NOTE: in the raw gram.y output, ColumnDef, PeriodDef, and Constraint nodes are + * intermixed in tableElts; periods and constraints are NIL. After parse analysis, + * tableElts contains just ColumnDefs, periods contains just PeriodDef nodes, and constraints contains just * Constraint nodes (in fact, only CONSTR_CHECK nodes, in the present * implementation). * ---------------------- @@ -2493,6 +2496,7 @@ typedef struct CreateStmt NodeTag type; RangeVar *relation; /* relation to create */ List *tableElts; /* column definitions (list of ColumnDef) */ + List *periods; /* periods (list of PeriodDef nodes) */ List *inhRelations; /* relations to inherit from (list of * RangeVar) */ PartitionBoundSpec *partbound; /* FOR VALUES clause */ @@ -2506,6 +2510,30 @@ typedef struct CreateStmt bool if_not_exists; /* just do nothing if it already exists? */ } CreateStmt; + +/* ---------- + * Definitions for periods in CreateStmt + * ---------- + */ + +typedef struct PeriodDef +{ + NodeTag type; + Oid oid; /* period oid, once it's transformed */ + char *periodname; /* period name */ + char *startcolname; /* name of start column */ + char *endcolname; /* name of end column */ + AttrNumber startattnum; /* attnum of the start column */ + AttrNumber endattnum; /* attnum of the end column */ + AttrNumber rngattnum; /* attnum of the GENERATED range column */ + List *options; /* options from WITH clause */ + char *constraintname; /* name of the CHECK constraint */ + char *rangetypename; /* name of the range type */ + Oid coltypid; /* the start/end col type */ + Oid rngtypid; /* the range type to use */ + int location; /* token location, or -1 if unknown */ +} PeriodDef; + /* ---------- * Definitions for constraints in CreateStmt * @@ -3212,6 +3240,7 @@ typedef struct IndexStmt List *indexParams; /* columns to index: a list of IndexElem */ List *indexIncludingParams; /* additional columns to index: a list * of IndexElem */ + PeriodDef *period; /* The period included in the index */ List *options; /* WITH clause options: a list of DefElem */ Node *whereClause; /* qualification (partial-index predicate) */ List *excludeOpNames; /* exclusion operator names, or NIL if none */ diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 2b40a0b5b1..feb6c26007 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -40,5 +40,6 @@ extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, const struct AttrMap *attmap, Oid *constraintOid); +extern void transformPeriodOptions(PeriodDef *period); #endif /* PARSE_UTILCMD_H */ diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index a2b6ced904..e8dcf17b7d 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -96,6 +96,8 @@ extern Oid get_atttype(Oid relid, AttrNumber attnum); extern void get_atttypetypmodcoll(Oid relid, AttrNumber attnum, Oid *typid, int32 *typmod, Oid *collid); extern Datum get_attoptions(Oid relid, int16 attnum); +extern char *get_periodname(Oid periodid, bool missing_ok); +extern Oid get_period_oid(Oid relid, const char *periodname, bool missing_ok); extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); extern char *get_collation_name(Oid colloid); extern bool get_collation_isdeterministic(Oid colloid); @@ -143,6 +145,7 @@ extern Oid get_transform_fromsql(Oid typid, Oid langid, List *trftypes); extern Oid get_transform_tosql(Oid typid, Oid langid, List *trftypes); extern bool get_typisdefined(Oid typid); extern char *get_typname(Oid typid); +extern bool get_typname_and_namespace(Oid typid, char **typname, char **typnamespace); extern int16 get_typlen(Oid typid); extern bool get_typbyval(Oid typid); extern void get_typlenbyval(Oid typid, int16 *typlen, bool *typbyval); @@ -195,6 +198,7 @@ extern Oid get_range_subtype(Oid rangeOid); extern Oid get_range_collation(Oid rangeOid); extern Oid get_range_multirange(Oid rangeOid); extern Oid get_multirange_range(Oid multirangeOid); +extern Oid get_subtype_range(Oid subtypeOid); extern Oid get_index_column_opclass(Oid index_oid, int attno); extern bool get_index_isreplident(Oid index_oid); extern bool get_index_isvalid(Oid index_oid); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index 67ea6e4945..33bab4061a 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -75,6 +75,8 @@ enum SysCacheIdentifier PARAMETERACLNAME, PARAMETERACLOID, PARTRELID, + PERIODNAME, + PERIODOID, PROCNAMEARGSNSP, PROCOID, PUBLICATIONNAME, @@ -84,6 +86,7 @@ enum SysCacheIdentifier PUBLICATIONREL, PUBLICATIONRELMAP, RANGEMULTIRANGE, + RANGESUBTYPE, RANGETYPE, RELNAMENSP, RELOID, diff --git a/src/test/regress/expected/periods.out b/src/test/regress/expected/periods.out new file mode 100644 index 0000000000..9d24e342ac --- /dev/null +++ b/src/test/regress/expected/periods.out @@ -0,0 +1,152 @@ +/* System periods are not implemented */ +create table pt (id integer, ds date, de date, period for system_time (ds, de)); +ERROR: PERIOD FOR SYSTEM_TIME is not supported +LINE 2: create table pt (id integer, ds date, de date, period for sy... + ^ +/* Periods must specify actual columns */ +create table pt (id integer, ds date, de date, period for p (bogus, de)); +ERROR: column "bogus" of relation "pt" does not exist +create table pt (id integer, ds date, de date, period for p (ds, bogus)); +ERROR: column "bogus" of relation "pt" does not exist +/* Data types must match exactly */ +create table pt (id integer, ds date, de timestamp, period for p (ds, de)); +ERROR: start and end columns of period must be of same type +create table pt (id integer, ds text collate "C", de text collate "POSIX", period for p (ds, de)); +ERROR: start and end columns of period must have same collation +/* Periods must have a default BTree operator class */ +create table pt (id integer, ds xml, de xml, period for p (ds, de)); +ERROR: no range type for xml found for period p +HINT: You can define a custom range type with CREATE TYPE +/* Period and column names are in the same namespace */ +create table pt (id integer, ds date, de date, period for ctid (ds, de)); +ERROR: period name "ctid" conflicts with a system column name +create table pt (id integer, ds date, de date, period for id (ds, de)); +ERROR: period name "id" conflicts with a column name +/* Period name can't be given more than once */ +create table pt (id integer, ds date, de date, period for p (ds, de), period for p (ds, de)); +ERROR: period name "p" specified more than once +/* Now make one that works */ +create table pt (id integer, ds date, de date, period for p (ds, de)); +/* + * CREATE TABLE currently adds an ALTER TABLE to add the periods, but let's do + * some explicit testing anyway + */ +alter table pt drop period for p; +alter table pt add period for system_time (ds, de); +ERROR: PERIOD FOR SYSTEM_TIME is not supported +alter table pt add period for p (ds, de); +/* Can't drop its columns */ +alter table pt drop column ds; +ERROR: cannot drop column ds of table pt because other objects depend on it +DETAIL: period p on table pt depends on column ds of table pt +HINT: Use DROP ... CASCADE to drop the dependent objects too. +alter table pt drop column de; +ERROR: cannot drop column de of table pt because other objects depend on it +DETAIL: period p on table pt depends on column de of table pt +HINT: Use DROP ... CASCADE to drop the dependent objects too. +/* Can't change the data types */ +alter table pt alter column ds type timestamp; +ERROR: cannot alter type of a column used by a period +DETAIL: period p on table pt depends on column "ds" +alter table pt alter column ds type timestamp; +ERROR: cannot alter type of a column used by a period +DETAIL: period p on table pt depends on column "ds" +/* column/period namespace conflicts */ +alter table pt add column p integer; +ERROR: column name "p" conflicts with a period name +alter table pt rename column id to p; +ERROR: column name "p" conflicts with a period name +/* adding columns and the period at the same time */ +create table pt2 (id integer); +alter table pt2 add column ds date, add column de date, add period for p (ds, de); +drop table pt2; +/* Ambiguous range types raise an error */ +create type mydaterange as range(subtype=date); +create table pt2 (id int, ds date, de date, period for p (ds, de)); +ERROR: ambiguous range for type date +/* You can give an explicit range type */ +create table pt2 (id int, ds date, de date, period for p (ds, de) with (rangetype = 'mydaterange')); +drop type mydaterange; +ERROR: cannot drop type mydaterange because other objects depend on it +DETAIL: period p on table pt2 depends on type mydaterange +HINT: Use DROP ... CASCADE to drop the dependent objects too. +drop type mydaterange cascade; +NOTICE: drop cascades to period p on table pt2 +drop table pt2; +create table pt2 (id int, ds date, de date, period for p (ds, de) with (rangetype = 'daterange')); +/* Range type is not found */ +create table pt3 (id int, ds date, de date, period for p (ds, de) with (rangetype = 'notarange')); +ERROR: Range type notarange not found +/* Range type is the wrong type */ +create table pt3 (id int, ds date, de date, period for p (ds, de) with (rangetype = 'tstzrange')); +ERROR: Range type tstzrange does not match column type date +drop table pt2; +/* CREATE TABLE (LIKE ...) */ +/* Periods are not copied by LIKE */ +create table pt2 (like pt); +\d pt2 + Table "public.pt2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + id | integer | | | + ds | date | | | + de | date | | | + +drop table pt2; +/* Can add a period referring to LIKE'd columns */ +create table not_p (id integer, ds date, de date); +create table pt2 (like not_p, period for p (ds, de)); +\d pt2 + Table "public.pt2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + id | integer | | | + ds | date | | | + de | date | | | +Periods: + p (ds, de) +Check constraints: + "pt2_p_check" CHECK (ds < de) + +drop table pt2; +/* Can add a period with the same name */ +create table pt2 (like pt, period for p (ds, de)); +\d pt2 + Table "public.pt2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + id | integer | | | + ds | date | | | + de | date | | | +Periods: + p (ds, de) +Check constraints: + "pt2_p_check" CHECK (ds < de) + +drop table pt2; +/* Can add a period with a different name */ +create table pt2 (like pt, period for p2 (ds, de)); +\d pt2 + Table "public.pt2" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + id | integer | | | + ds | date | | | + de | date | | | +Periods: + p2 (ds, de) +Check constraints: + "pt2_p2_check" CHECK (ds < de) + +drop table pt2; +/* Can't add a period whose name conflicts with a LIKE'd column */ +create table pt2 (like pt, period for id (ds, de)); +ERROR: period name "id" conflicts with a column name +/* CREATE TALBE INHERITS */ +/* Can't inherit from a table with a period */ +create table pt2 (name text) inherits (pt); +ERROR: Inheriting from a table with a PERIOD is not supported +/* Can't inherit with a period */ +create table pt2 (d2s date, d2e date, period for p (d2s, d2e)) inherits (not_p); +ERROR: Inheriting is not supported when a table has a PERIOD +drop table not_p; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index ce732bda3c..202c320ed8 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -48,7 +48,7 @@ test: create_index create_index_spgist create_view index_including index_includi # ---------- # Another group of parallel tests # ---------- -test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse for_portion_of +test: create_aggregate create_function_sql create_cast constraints triggers select inherit typed_table periods vacuum drop_if_exists updatable_views roleattributes create_am hash_func errors infinite_recurse for_portion_of # ---------- # sanity_check does a vacuum, affecting the sort order of SELECT * diff --git a/src/test/regress/sql/periods.sql b/src/test/regress/sql/periods.sql new file mode 100644 index 0000000000..5632706c39 --- /dev/null +++ b/src/test/regress/sql/periods.sql @@ -0,0 +1,102 @@ +/* System periods are not implemented */ +create table pt (id integer, ds date, de date, period for system_time (ds, de)); + +/* Periods must specify actual columns */ +create table pt (id integer, ds date, de date, period for p (bogus, de)); +create table pt (id integer, ds date, de date, period for p (ds, bogus)); + +/* Data types must match exactly */ +create table pt (id integer, ds date, de timestamp, period for p (ds, de)); +create table pt (id integer, ds text collate "C", de text collate "POSIX", period for p (ds, de)); + +/* Periods must have a default BTree operator class */ +create table pt (id integer, ds xml, de xml, period for p (ds, de)); + +/* Period and column names are in the same namespace */ +create table pt (id integer, ds date, de date, period for ctid (ds, de)); +create table pt (id integer, ds date, de date, period for id (ds, de)); + +/* Period name can't be given more than once */ +create table pt (id integer, ds date, de date, period for p (ds, de), period for p (ds, de)); + +/* Now make one that works */ +create table pt (id integer, ds date, de date, period for p (ds, de)); + +/* + * CREATE TABLE currently adds an ALTER TABLE to add the periods, but let's do + * some explicit testing anyway + */ +alter table pt drop period for p; +alter table pt add period for system_time (ds, de); +alter table pt add period for p (ds, de); + +/* Can't drop its columns */ +alter table pt drop column ds; +alter table pt drop column de; + +/* Can't change the data types */ +alter table pt alter column ds type timestamp; +alter table pt alter column ds type timestamp; + +/* column/period namespace conflicts */ +alter table pt add column p integer; +alter table pt rename column id to p; + +/* adding columns and the period at the same time */ +create table pt2 (id integer); +alter table pt2 add column ds date, add column de date, add period for p (ds, de); +drop table pt2; + +/* Ambiguous range types raise an error */ +create type mydaterange as range(subtype=date); +create table pt2 (id int, ds date, de date, period for p (ds, de)); + +/* You can give an explicit range type */ +create table pt2 (id int, ds date, de date, period for p (ds, de) with (rangetype = 'mydaterange')); +drop type mydaterange; +drop type mydaterange cascade; +drop table pt2; +create table pt2 (id int, ds date, de date, period for p (ds, de) with (rangetype = 'daterange')); + +/* Range type is not found */ +create table pt3 (id int, ds date, de date, period for p (ds, de) with (rangetype = 'notarange')); + +/* Range type is the wrong type */ +create table pt3 (id int, ds date, de date, period for p (ds, de) with (rangetype = 'tstzrange')); +drop table pt2; + +/* CREATE TABLE (LIKE ...) */ + +/* Periods are not copied by LIKE */ +create table pt2 (like pt); +\d pt2 +drop table pt2; + +/* Can add a period referring to LIKE'd columns */ +create table not_p (id integer, ds date, de date); +create table pt2 (like not_p, period for p (ds, de)); +\d pt2 +drop table pt2; + +/* Can add a period with the same name */ +create table pt2 (like pt, period for p (ds, de)); +\d pt2 +drop table pt2; + +/* Can add a period with a different name */ +create table pt2 (like pt, period for p2 (ds, de)); +\d pt2 +drop table pt2; + +/* Can't add a period whose name conflicts with a LIKE'd column */ +create table pt2 (like pt, period for id (ds, de)); + +/* CREATE TALBE INHERITS */ + +/* Can't inherit from a table with a period */ +create table pt2 (name text) inherits (pt); + +/* Can't inherit with a period */ +create table pt2 (d2s date, d2e date, period for p (d2s, d2e)) inherits (not_p); + +drop table not_p; -- 2.32.0 (Apple Git-132)