diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3ed9021c2f..025d6b5355 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -225,6 +225,11 @@
information about partition key of tables
+
+ pg_period
+ periods
+
+
pg_pltemplatetemplate data for procedural languages
@@ -4902,6 +4907,116 @@ SCRAM-SHA-256$<iteration count>:&l
+
+ pg_period
+
+
+ pg_period
+
+
+
+ The catalog pg_period stores
+ information about system and application time periods.
+
+
+
+ Periods are described in .
+
+
+
+ pg_period Columns
+
+
+
+
+ Name
+ Type
+ References
+ Description
+
+
+
+
+
+
+ pername
+ name
+
+ Period name
+
+
+
+ perrelid
+ oid
+ pg_class.oid
+ The OID of the pg_class entry for the table containing this period.
+
+
+
+ perstart
+ int2
+ pg_attribute.attnum
+
+ The attribute number of the start column.
+
+
+
+
+ perend
+ int2
+ pg_attribute.attnum
+
+ The attribute number of the end column.
+
+
+
+
+ peropclass
+ oid
+ pg_opclass.oid
+
+ This contains the OID of the operator class to use.
+
+
+
+
+ perconstraint
+ oid
+ pg_constraint.oid
+
+ This contains the OID of the CHECK constraint owned by the period to
+ ensure that (startcolumn
+ <
+ endcolumn).
+
+
+
+
+ perislocal
+ bool
+
+
+ This period is defined locally for the relation. Note that a period can
+ be locally defined and inherited simultaneously.
+
+
+
+
+ perinhcount
+ int4
+
+
+ The number of direct inheritance ancestors this period has. A period
+ with a nonzero number of ancestors cannot be dropped.
+
+
+
+
+
+
+
+
+
pg_pltemplate
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 2cd0b8ab9d..cdbe06196c 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -919,6 +919,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.
+
+
+
+ Currently, periods in PostgreSQL have no functionality; they can only be
+ defined for future use.
+
+
+
+ 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)
+);
+
+
+
+
+ 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 09ef2827f2..95a621370d 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -3348,6 +3348,69 @@ ORDER BY c.ordinal_position;
+
+ periods
+
+
+ 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).
+
+
+
+ periods 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
+
+
+
+
+
+
referential_constraints
diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index a37d0b756b..6c78fe221a 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -3431,7 +3431,7 @@
PERIOD
-
+ reservedreserved
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 1cce00eaf9..2a0f16e175 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -58,6 +58,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
@@ -481,6 +483,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 965c5a40ad..89a5406da9 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 [, ...] ] ) ] |
@@ -328,6 +329,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 ROLE my_role IS 'Administration group for finance tables';
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 2a1eac9592..b030866c12 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_namedata_type [ COLLATE collation ] [ column_constraint [ ... ] ]
+ | period_definition
| table_constraint
| LIKE source_table [ like_option ... ] }
[, ... ]
@@ -36,6 +37,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 }
[, ... ]
) ]
@@ -47,6 +49,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 }
@@ -69,6 +72,11 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
[ ON DELETE action ] [ ON UPDATE 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 ]
@@ -130,6 +138,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
@@ -639,6 +655,27 @@ 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. The columns are also marked as NOT NULL.
+
+
+
+ Both columns must have exactly the same type and must have a btree
+ operator class. The operator class can be specified with the
+ operator_classperiod_option. If not specified, the
+ default operator class for the type is used.
+
+
+
+
CONSTRAINT constraint_name
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 0865240f11..cb5e9f1d2b 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -17,7 +17,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
objectaccess.o objectaddress.o partition.o pg_aggregate.o pg_collation.o \
pg_constraint.o pg_conversion.o \
pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \
- pg_operator.o pg_proc.o pg_publication.o pg_range.o \
+ pg_operator.o pg_period.o pg_proc.o pg_publication.o pg_range.o \
pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \
storage.o toasting.o
@@ -44,7 +44,7 @@ CATALOG_HEADERS := \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
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_partitioned_table.h pg_range.h pg_transform.h \
+ pg_collation.h pg_partitioned_table.h pg_period.h pg_range.h pg_transform.h \
pg_sequence.h pg_publication.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 578e4c6592..1f70090367 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -3468,6 +3468,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PERIOD:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_RULE:
@@ -3607,6 +3608,7 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_DEFAULT:
case OBJECT_DEFACL:
case OBJECT_DOMCONSTRAINT:
+ case OBJECT_PERIOD:
case OBJECT_PUBLICATION_REL:
case OBJECT_ROLE:
case OBJECT_TRANSFORM:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 4f1d365357..26452e5b76 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -43,6 +43,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
+#include "catalog/pg_period.h"
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
@@ -139,6 +140,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 */
@@ -1163,6 +1165,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemoveConstraintById(object->objectId);
break;
+ case OCLASS_PERIOD:
+ RemovePeriodById(object->objectId);
+ break;
+
case OCLASS_CONVERSION:
RemoveConversionById(object->objectId);
break;
@@ -2442,6 +2448,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 39813de991..dd164efab4 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -50,6 +50,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"
@@ -2001,6 +2002,68 @@ RelationClearMissing(Relation rel)
heap_close(attr_rel, RowExclusiveLock);
}
+/*
+ * 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, Oid opclass, Oid conoid)
+{
+ 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));
+
+ 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_peropclass -1] = opclass;
+ values[Anum_pg_period_perconstraint - 1] = conoid;
+
+ pg_period = heap_open(PeriodRelationId, RowExclusiveLock);
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_period), values, nulls);
+ oid = 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);
+
+ /* Make sure we don't lose our operator class. */
+ ObjectAddressSet(referenced, OperatorClassRelationId, opclass);
+ 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_AUTO);
+
+ heap_close(pg_period, RowExclusiveLock);
+
+ return oid;
+}
+
/*
* Store a default expression for column attnum of relation rel.
*
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index f4e69f4a26..4eaeb302bf 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -1179,7 +1179,29 @@ 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 = ANY (ARRAY['r']);
+ 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 ad682673e6..b430236eb8 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -42,6 +42,7 @@
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_period.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_policy.h"
#include "catalog/pg_publication.h"
@@ -587,6 +588,10 @@ static const struct object_type_map
{
"domain constraint", OBJECT_DOMCONSTRAINT
},
+ /* OCLASS_PERIOD */
+ {
+ "period", OBJECT_PERIOD
+ },
/* OCLASS_CONVERSION */
{
"conversion", OBJECT_CONVERSION
@@ -844,6 +849,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;
@@ -1335,6 +1341,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 objtype: %d", (int) objtype);
}
@@ -2129,6 +2142,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;
@@ -2243,6 +2257,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
case OBJECT_TRIGGER:
case OBJECT_POLICY:
case OBJECT_TABCONSTRAINT:
+ case OBJECT_PERIOD:
if (!pg_class_ownercheck(RelationGetRelid(relation), roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, objtype,
RelationGetRelationName(relation));
@@ -2804,6 +2819,38 @@ getObjectDescription(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);
+
+ if (OidIsValid(per->perrelid))
+ {
+ StringInfoData rel;
+
+ initStringInfo(&rel);
+ getRelationDescription(&rel, per->perrelid);
+ 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;
@@ -3942,6 +3989,10 @@ getObjectTypeDescription(const ObjectAddress *object)
getConstraintTypeDescription(&buffer, object->objectId);
break;
+ case OCLASS_PERIOD:
+ appendStringInfoString(&buffer, "period");
+ break;
+
case OCLASS_CONVERSION:
appendStringInfoString(&buffer, "conversion");
break;
@@ -4355,6 +4406,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);
+ 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..e819ea0cff
--- /dev/null
+++ b/src/backend/catalog/pg_period.c
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_period.c
+ * routines to support manipulation of the pg_period relation
+ *
+ * Portions Copyright (c) 1996-2018, 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 = heap_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);
+ heap_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. There may be more than
+ * one match, because periods are not required to have unique names;
+ * if so, error out.
+ */
+ pg_period = heap_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 = HeapTupleGetOid(tuple);
+ }
+ }
+
+ 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))));
+
+ heap_close(pg_period, AccessShareLock);
+
+ return perOid;
+}
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index aeb262a5b0..61088d97ea 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -426,7 +426,7 @@ T176 Sequence generator support NO
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
T201 Comparable data types for referential constraints YES
T211 Basic trigger capability NO
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index eff325cc7d..ee64694fb7 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -608,6 +608,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 2f2e69b4a8..4e9c0fbf92 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -101,6 +101,16 @@ CommentObject(CommentStmt *stmt)
errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
RelationGetRelationName(relation))));
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/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..9c98a18495 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1126,6 +1126,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:
@@ -1181,6 +1182,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:
@@ -2264,6 +2266,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_REL:
@@ -2346,6 +2349,7 @@ stringify_adefprivs_objtype(ObjectType objtype)
case OBJECT_OPCLASS:
case OBJECT_OPERATOR:
case OBJECT_OPFAMILY:
+ case OBJECT_PERIOD:
case OBJECT_POLICY:
case OBJECT_PUBLICATION:
case OBJECT_PUBLICATION_REL:
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0e95037dcf..81398adfb8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -40,6 +40,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
+#include "catalog/pg_period.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
@@ -365,6 +366,8 @@ static ObjectAddress ATExecAddColumn(List **wqueue, AlteredTableInfo *tab,
bool if_not_exists, LOCKMODE lockmode);
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 ATPrepAddOids(List **wqueue, Relation rel, bool recurse,
@@ -376,6 +379,11 @@ static ObjectAddress ATExecSetNotNull(AlteredTableInfo *tab, Relation rel,
const char *colName, LOCKMODE lockmode);
static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName,
Node *newDefault, LOCKMODE lockmode);
+static ObjectAddress ATExecAddPeriod(Relation rel, Period *period, LOCKMODE lockmode);
+static void ATExecDropPeriod(Relation rel, const char *periodName,
+ DropBehavior behavior,
+ bool recurse, bool recursing,
+ bool missing_ok, LOCKMODE lockmode);
static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName,
Node *def, LOCKMODE lockmode);
static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName,
@@ -1854,6 +1862,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
*
* Note that we also need to check that we do not exceed this figure after
* including columns from inherited relations.
+ *
+ * TODO: What about periods?
*/
if (list_length(schema) > MaxHeapAttributeNumber)
ereport(ERROR,
@@ -1932,6 +1942,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
* Scan the parents left-to-right, and merge their attributes to form a
* list of inherited attributes (inhSchema). Also check to see if we need
* to inherit an OID column.
+ *
+ * TODO: probably need periods here, too.
*/
child_attno = 0;
foreach(entry, supers)
@@ -3382,6 +3394,20 @@ AlterTableGetLockLevel(List *cmds)
cmd_lockmode = AccessExclusiveLock;
break;
+ /*
+ * Adding a period may conflict with a column name so we need
+ * an exclusive lock to make sure columns aren't added
+ * concurrently.
+ *
+ * It also adds a CHECK constraint so we need to match that
+ * level, and dropping a period drops the constraint so that
+ * level needs to be matched, too.
+ */
+ case AT_AddPeriod:
+ case AT_DropPeriod:
+ cmd_lockmode = AccessExclusiveLock;
+ break;
+
/*
* These subcommands may require addition of toast tables. If
* we add a toast table to a table currently being scanned, we
@@ -3690,6 +3716,14 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = cmd->def ? AT_PASS_ADD_CONSTR : AT_PASS_DROP;
break;
+ case AT_AddPeriod: /* ALTER TABLE ... ADD PERIOD FOR name (start, end) */
+ ATSimplePermissions(rel, ATT_TABLE);
+ pass = AT_PASS_ADD_CONSTR;
+ break;
+ case AT_DropPeriod: /* ALTER TABLE ... DROP PERIOD FOR name */
+ ATSimplePermissions(rel, ATT_TABLE);
+ pass = AT_PASS_DROP;
+ break;
case AT_AddIdentity:
ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE);
pass = AT_PASS_ADD_CONSTR;
@@ -4032,6 +4066,14 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
case AT_ColumnDefault: /* ALTER COLUMN DEFAULT */
address = ATExecColumnDefault(rel, cmd->name, cmd->def, lockmode);
break;
+ case AT_AddPeriod:
+ address = ATExecAddPeriod(rel, (Period *) cmd->def, lockmode);
+ break;
+ case AT_DropPeriod:
+ ATExecDropPeriod(rel, cmd->name, cmd->behavior,
+ false, false,
+ cmd->missing_ok, lockmode);
+ break;
case AT_AddIdentity:
address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode);
break;
@@ -5731,14 +5773,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.
@@ -5782,6 +5839,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.
*/
@@ -6154,6 +6283,285 @@ ATExecColumnDefault(Relation rel, const char *colName,
return address;
}
+/*
+ * make_constraints_for_period
+ *
+ * Add constraints to make both columns NOT NULL and CHECK (start < end).
+ *
+ * Returns the CHECK constraint Oid.
+ */
+static Oid
+make_constraints_for_period(Relation rel, Period *period, char *constraintname)
+{
+ List *cmds = NIL;
+ AlterTableCmd *cmd;
+ ColumnRef *scol, *ecol;
+ Constraint *constr;
+ char *conname;
+
+ /* Start column must be NOT NULL */
+ cmd = makeNode(AlterTableCmd);
+ cmd->subtype = AT_SetNotNull;
+ cmd->name = period->startcolname;
+ cmds = lappend(cmds, cmd);
+
+ /* End column must be NOT NULL */
+ cmd = makeNode(AlterTableCmd);
+ cmd->subtype = AT_SetNotNull;
+ cmd->name = period->endcolname;
+ cmds = lappend(cmds, cmd);
+
+ /*
+ * Create the CHECK constraint
+ */
+ if (constraintname)
+ conname = constraintname;
+ else
+ conname = 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;
+
+ constr = makeNode(Constraint);
+ constr->contype = CONSTR_CHECK;
+ constr->conname = conname;
+ constr->deferrable = false;
+ constr->initdeferred = false;
+ constr->location = 0;
+ constr->is_no_inherit = false;
+ constr->raw_expr = (Node *) makeSimpleA_Expr(AEXPR_OP, "<", (Node *) scol, (Node *) ecol, 0);
+ constr->cooked_expr = NULL;
+ constr->skip_validation = false;
+ constr->initially_valid = true;
+
+ cmd = makeNode(AlterTableCmd);
+ cmd->subtype = AT_AddConstraint;
+ cmd->def = (Node *) constr;
+ cmds = lappend(cmds, cmd);
+
+ /* Do the deed. */
+ AlterTableInternal(RelationGetRelid(rel), cmds, true);
+
+ return get_relation_constraint_oid(RelationGetRelid(rel), conname, false);
+}
+
+/*
+ * ALTER TABLE ADD PERIOD
+ *
+ * Return the address of the what?
+ */
+static ObjectAddress
+ATExecAddPeriod(Relation rel, Period *period, LOCKMODE lockmode)
+{
+ Relation attrelation;
+ HeapTuple starttuple, endtuple;
+ Form_pg_attribute startatttuple, endatttuple;
+ AttrNumber startattnum, endattnum;
+ ListCell *option;
+ DefElem *dconstraintname = NULL;
+ DefElem *dopclass = NULL;
+ Oid conoid, opclass;
+
+ /*
+ * 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 */
+ 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, "operator_class") == 0)
+ {
+ if (dopclass)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ dopclass = defel;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("option \"%s\" not recognized", defel->defname)));
+ }
+
+ attrelation = heap_open(AttributeRelationId, RowExclusiveLock);
+
+ /* 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))));
+ startatttuple = (Form_pg_attribute) GETSTRUCT(starttuple);
+ startattnum = startatttuple->attnum;
+
+ /* Make sure it's not a system column */
+ if (startattnum <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot use system column \"%s\" in period",
+ period->startcolname)));
+
+ /*
+ * Get the btree operator class for it (ResolveOpClass will error out if
+ * necessary)
+ */
+ opclass = ResolveOpClass(dopclass ? defGetQualifiedName(dopclass) : NULL,
+ startatttuple->atttypid,
+ "btree", BTREE_AM_OID);
+
+ /* 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))));
+ endatttuple = (Form_pg_attribute) GETSTRUCT(endtuple);
+ endattnum = endatttuple->attnum;
+
+ /* Make sure it's not a system column */
+ if (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 (startatttuple->atttypid != endatttuple->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 (startatttuple->attcollation != endatttuple->attcollation)
+ ereport(ERROR,
+ (errcode(ERRCODE_COLLATION_MISMATCH),
+ errmsg("start and end columns of period must have same collation")));
+
+ heap_freetuple(starttuple);
+ heap_freetuple(endtuple);
+
+ conoid = make_constraints_for_period(rel, period,
+ dconstraintname ? defGetString(dconstraintname) : NULL);
+
+ /* Save it */
+ StorePeriod(rel, period->periodname, startattnum, endattnum, opclass, conoid);
+
+ heap_close(attrelation, RowExclusiveLock);
+
+ return InvalidObjectAddress;
+}
+
+/*
+ * 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, LOCKMODE lockmode)
+{
+ 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(rel, ATT_TABLE);
+
+ pg_period = heap_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 = HeapTupleGetOid(tuple);
+ 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))));
+ heap_close(pg_period, RowExclusiveLock);
+ return;
+ }
+ }
+
+ heap_close(pg_period, RowExclusiveLock);
+}
+
/*
* ALTER TABLE ALTER COLUMN ADD IDENTITY
*
@@ -9512,6 +9920,15 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
}
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),
+ colName)));
+ break;
+
case OCLASS_REWRITE:
/* XXX someday see if we can cope with revising views */
ereport(ERROR,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c045a7afe..fa5bc9cdd3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2911,6 +2911,20 @@ _copyConstraint(const Constraint *from)
return newnode;
}
+static Period *
+_copyPeriod(const Period *from)
+{
+ Period *newnode = makeNode(Period);
+
+ COPY_STRING_FIELD(periodname);
+ COPY_STRING_FIELD(startcolname);
+ COPY_STRING_FIELD(endcolname);
+ COPY_NODE_FIELD(options);
+ COPY_LOCATION_FIELD(location);
+
+ return newnode;
+}
+
static DefElem *
_copyDefElem(const DefElem *from)
{
@@ -5553,6 +5567,9 @@ copyObjectImpl(const void *from)
case T_Constraint:
retval = _copyConstraint(from);
break;
+ case T_Period:
+ retval = _copyPeriod(from);
+ break;
case T_DefElem:
retval = _copyDefElem(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 6a971d0141..13fc726cbb 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2602,6 +2602,18 @@ _equalConstraint(const Constraint *a, const Constraint *b)
return true;
}
+static bool
+_equalPeriod(const Period *a, const Period *b)
+{
+ COMPARE_STRING_FIELD(periodname);
+ COMPARE_STRING_FIELD(startcolname);
+ COMPARE_STRING_FIELD(endcolname);
+ COMPARE_NODE_FIELD(options);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
static bool
_equalDefElem(const DefElem *a, const DefElem *b)
{
@@ -3630,6 +3642,9 @@ equal(const void *a, const void *b)
case T_Constraint:
retval = _equalConstraint(a, b);
break;
+ case T_Period:
+ retval = _equalPeriod(a, b);
+ break;
case T_DefElem:
retval = _equalDefElem(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index a10014f755..a45d82cf40 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -1501,6 +1501,9 @@ exprLocation(const Node *expr)
case T_Constraint:
loc = ((const Constraint *) expr)->location;
break;
+ case T_Period:
+ loc = ((const Period *) expr)->location;
+ break;
case T_FunctionParameter:
/* just use typename's location */
loc = exprLocation((Node *) ((const FunctionParameter *) expr)->argType);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 1da9d7ed15..9aa4808015 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -3592,6 +3592,18 @@ _outConstraint(StringInfo str, const Constraint *node)
}
}
+static void
+_outPeriod(StringInfo str, const Period *node)
+{
+ WRITE_NODE_TYPE("PERIOD");
+
+ WRITE_STRING_FIELD(periodname);
+ WRITE_STRING_FIELD(startcolname);
+ WRITE_STRING_FIELD(endcolname);
+ WRITE_NODE_FIELD(options);
+ WRITE_LOCATION_FIELD(location);
+}
+
static void
_outForeignKeyCacheInfo(StringInfo str, const ForeignKeyCacheInfo *node)
{
@@ -4275,6 +4287,9 @@ outNode(StringInfo str, const void *obj)
case T_Constraint:
_outConstraint(str, obj);
break;
+ case T_Period:
+ _outPeriod(str, obj);
+ break;
case T_FuncCall:
_outFuncCall(str, obj);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 90dfac2cb1..430021a984 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -533,7 +533,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type unreserved_keyword type_func_name_keyword
%type col_name_keyword reserved_keyword
-%type TableConstraint TableLikeClause
+%type TableConstraint TableLikeClause TablePeriod
%type TableLikeOptionList TableLikeOption
%type ColQualList
%type ColConstraint ColConstraintElem ConstraintAttr
@@ -662,7 +662,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ORDER ORDINALITY OTHERS OUT_P OUTER_P
OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
- PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+ PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PERIOD PLACING PLANS POLICY
POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
@@ -2261,6 +2261,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
{
@@ -3373,8 +3391,10 @@ TableElement:
columnDef { $$ = $1; }
| TableLikeClause { $$ = $1; }
| TableConstraint { $$ = $1; }
+ | TablePeriod { $$ = $1; }
;
+
TypedTableElement:
columnOptions { $$ = $1; }
| TableConstraint { $$ = $1; }
@@ -3651,6 +3671,19 @@ TableLikeOption:
;
+TablePeriod:
+ PERIOD FOR name '(' name ',' name ')' opt_definition
+ {
+ Period *n = makeNode(Period);
+ 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
@@ -6422,6 +6455,7 @@ opt_restart_seqs:
* OPERATOR (leftoperand_typ, rightoperand_typ) |
* OPERATOR CLASS USING |
* OPERATOR FAMILY USING |
+ * PERIOD . |
* RULE ON |
* TRIGGER ON ]
* IS { 'text' | NULL }
@@ -6604,6 +6638,7 @@ comment_type_any_name:
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
+ | PERIOD { $$ = OBJECT_PERIOD; }
;
/* object types taking name */
@@ -15447,6 +15482,7 @@ reserved_keyword:
| ONLY
| OR
| ORDER
+ | PERIOD
| PLACING
| PRIMARY
| REFERENCES
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 17b54b20cc..43bbb3bbf7 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -114,6 +114,8 @@ typedef struct
static void transformColumnDefinition(CreateStmtContext *cxt,
ColumnDef *column);
+static void transformTablePeriod(CreateStmtContext *cxt,
+ Period *period);
static void transformTableConstraint(CreateStmtContext *cxt,
Constraint *constraint);
static void transformTableLikeClause(CreateStmtContext *cxt,
@@ -286,6 +288,10 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
transformColumnDefinition(&cxt, (ColumnDef *) element);
break;
+ case T_Period:
+ transformTablePeriod(&cxt, (Period *) element);
+ break;
+
case T_Constraint:
transformTableConstraint(&cxt, (Constraint *) element);
break;
@@ -806,6 +812,42 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
}
}
+/*
+ * transformTablePeriod
+ * transform a Period node within CREATE TABLE or ALTER TABLE
+ */
+static void
+transformTablePeriod(CreateStmtContext *cxt, Period *period)
+{
+ AlterTableStmt *alterstmt;
+ AlterTableCmd *altercmd;
+
+ 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)));
+
+ /*
+ * Instead of duplicating code, just create an ALTER TABLE statement to run
+ * after the table is created.
+ */
+ alterstmt = makeNode(AlterTableStmt);
+ alterstmt->relation = cxt->relation;
+ alterstmt->cmds = NIL;
+ alterstmt->relkind = OBJECT_TABLE;
+
+ altercmd = makeNode(AlterTableCmd);
+ altercmd->subtype = AT_AddPeriod;
+ altercmd->name = NULL;
+ altercmd->def = (Node *) period;
+
+ alterstmt->cmds = lappend(alterstmt->cmds, altercmd);
+
+ cxt->alist = lappend(cxt->alist, alterstmt);
+}
+
/*
* transformTableConstraint
* transform a Constraint node within CREATE TABLE or ALTER TABLE
@@ -3023,6 +3065,23 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
(int) nodeTag(cmd->def));
break;
+ case AT_AddPeriod:
+ {
+ /*
+ Period *period = castNode(Period, cmd->def);
+
+ 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)));
+
+ */
+ newcmds = lappend(newcmds, cmd);
+ break;
+ }
+
case AT_ProcessedConstraint:
/*
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index 2b381782a3..3067cce14a 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -49,6 +49,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.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_rel.h"
@@ -584,6 +585,27 @@ static const struct cachedesc cacheinfo[] = {
},
32
},
+ {PeriodRelationId, /* PERIODNAME */
+ PeriodRelidNameIndexId,
+ 2,
+ {
+ Anum_pg_period_perrelid,
+ Anum_pg_period_pername,
+ 0,
+ 0
+ },
+ 32
+ },
+ {PeriodRelationId, /* PERIODOID */
+ PeriodObjectIndexId,
+ 1,
+ {
+ ObjectIdAttributeNumber,
+ 0,
+ 0
+ },
+ 32
+ },
{ProcedureRelationId, /* PROCNAMEARGSNSP */
ProcedureNameArgsNspIndexId,
3,
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 83c976eaf7..edb1fa5085 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3686,6 +3686,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
strcmp(te->desc, "DATABASE PROPERTIES") == 0 ||
strcmp(te->desc, "DEFAULT") == 0 ||
strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ strcmp(te->desc, "PERIOD") == 0 ||
strcmp(te->desc, "INDEX") == 0 ||
strcmp(te->desc, "RULE") == 0 ||
strcmp(te->desc, "TRIGGER") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f8fbcdad5b..9824aa5342 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -5843,6 +5843,7 @@ getTables(Archive *fout, int *numTables)
int i_initrrelacl;
int i_rolname;
int i_relchecks;
+ int i_nperiod;
int i_relhastriggers;
int i_relhasindex;
int i_relhasrules;
@@ -5891,6 +5892,114 @@ getTables(Archive *fout, int *numTables)
* we cannot correctly identify inherited columns, owned sequences, etc.
*/
+ if (fout->remoteVersion >= 0 /* TODO */)
+ {
+ PQExpBuffer acl_subquery = createPQExpBuffer();
+ PQExpBuffer racl_subquery = createPQExpBuffer();
+ PQExpBuffer initacl_subquery = createPQExpBuffer();
+ PQExpBuffer initracl_subquery = createPQExpBuffer();
+
+ PQExpBuffer attacl_subquery = createPQExpBuffer();
+ PQExpBuffer attracl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitacl_subquery = createPQExpBuffer();
+ PQExpBuffer attinitracl_subquery = createPQExpBuffer();
+
+ /*
+ * Left join to pick up dependency info linking sequences to their
+ * owning column, if any (note this dependency is AUTO as of 8.2)
+ *
+ * Left join to detect if any privileges are still as-set-at-init, in
+ * which case we won't dump out ACL commands for those.
+ */
+
+ buildACLQueries(acl_subquery, racl_subquery, initacl_subquery,
+ initracl_subquery, "c.relacl", "c.relowner",
+ "CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE)
+ " THEN 's' ELSE 'r' END::\"char\"",
+ dopt->binary_upgrade);
+
+ buildACLQueries(attacl_subquery, attracl_subquery, attinitacl_subquery,
+ attinitracl_subquery, "at.attacl", "c.relowner", "'c'",
+ dopt->binary_upgrade);
+
+ appendPQExpBuffer(query,
+ "SELECT c.tableoid, c.oid, c.relname, "
+ "%s AS relacl, %s as rrelacl, "
+ "%s AS initrelacl, %s as initrrelacl, "
+ "c.relkind, c.relnamespace, "
+ "(%s c.relowner) AS rolname, "
+ "c.relchecks, c.relhastriggers, "
+ "c.relhasindex, c.relhasrules, c.relhasoids, "
+ "c.relrowsecurity, c.relforcerowsecurity, "
+ "c.relfrozenxid, c.relminmxid, tc.oid AS toid, "
+ "tc.relfrozenxid AS tfrozenxid, "
+ "tc.relminmxid AS tminmxid, "
+ "c.relpersistence, c.relispopulated, "
+ "c.relreplident, c.relpages, "
+ "(SELECT count(*) FROM pg_period WHERE perrelid = c.oid) AS nperiod, "
+ "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
+ "d.refobjid AS owning_tab, "
+ "d.refobjsubid AS owning_col, "
+ "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, "
+ "array_remove(array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
+ "CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
+ "WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption, "
+ "tc.reloptions AS toast_reloptions, "
+ "c.relkind = '%c' AND EXISTS (SELECT 1 FROM pg_depend WHERE classid = 'pg_class'::regclass AND objid = c.oid AND objsubid = 0 AND refclassid = 'pg_class'::regclass AND deptype = 'i') AS is_identity_sequence, "
+ "EXISTS (SELECT 1 FROM pg_attribute at LEFT JOIN pg_init_privs pip ON "
+ "(c.oid = pip.objoid "
+ "AND pip.classoid = 'pg_class'::regclass "
+ "AND pip.objsubid = at.attnum)"
+ "WHERE at.attrelid = c.oid AND ("
+ "%s IS NOT NULL "
+ "OR %s IS NOT NULL "
+ "OR %s IS NOT NULL "
+ "OR %s IS NOT NULL"
+ "))"
+ "AS changed_acl, "
+ "pg_get_partkeydef(c.oid) AS partkeydef, "
+ "c.relispartition AS ispartition, "
+ "pg_get_expr(c.relpartbound, c.oid) AS partbound "
+ "FROM pg_class c "
+ "LEFT JOIN pg_depend d ON "
+ "(c.relkind = '%c' AND "
+ "d.classid = c.tableoid AND d.objid = c.oid AND "
+ "d.objsubid = 0 AND "
+ "d.refclassid = c.tableoid AND d.deptype IN ('a', 'i')) "
+ "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) "
+ "LEFT JOIN pg_init_privs pip ON "
+ "(c.oid = pip.objoid "
+ "AND pip.classoid = 'pg_class'::regclass "
+ "AND pip.objsubid = 0) "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
+ "ORDER BY c.oid",
+ acl_subquery->data,
+ racl_subquery->data,
+ initacl_subquery->data,
+ initracl_subquery->data,
+ username_subquery,
+ RELKIND_SEQUENCE,
+ attacl_subquery->data,
+ attracl_subquery->data,
+ attinitacl_subquery->data,
+ attinitracl_subquery->data,
+ RELKIND_SEQUENCE,
+ RELKIND_RELATION, RELKIND_SEQUENCE,
+ RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
+ RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
+ RELKIND_PARTITIONED_TABLE);
+
+ destroyPQExpBuffer(acl_subquery);
+ destroyPQExpBuffer(racl_subquery);
+ destroyPQExpBuffer(initacl_subquery);
+ destroyPQExpBuffer(initracl_subquery);
+
+ destroyPQExpBuffer(attacl_subquery);
+ destroyPQExpBuffer(attracl_subquery);
+ destroyPQExpBuffer(attinitacl_subquery);
+ destroyPQExpBuffer(attinitracl_subquery);
+ }
+ else
if (fout->remoteVersion >= 90600)
{
char *partkeydef = "NULL";
@@ -6420,6 +6529,7 @@ getTables(Archive *fout, int *numTables)
i_relkind = PQfnumber(res, "relkind");
i_rolname = PQfnumber(res, "rolname");
i_relchecks = PQfnumber(res, "relchecks");
+ i_nperiod = PQfnumber(res, "nperiod");
i_relhastriggers = PQfnumber(res, "relhastriggers");
i_relhasindex = PQfnumber(res, "relhasindex");
i_relhasrules = PQfnumber(res, "relhasrules");
@@ -6499,6 +6609,7 @@ getTables(Archive *fout, int *numTables)
else
tblinfo[i].reloftype = pg_strdup(PQgetvalue(res, i, i_reloftype));
tblinfo[i].ncheck = atoi(PQgetvalue(res, i, i_relchecks));
+ tblinfo[i].nperiod = atoi(PQgetvalue(res, i, i_nperiod));
if (PQgetisnull(res, i, i_owning_tab))
{
tblinfo[i].owning_tab = InvalidOid;
@@ -8106,6 +8217,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
PGresult *res;
int ntups;
bool hasdefaults;
+ int ndumpablechecks; /* number of CHECK constraints that do
+ not belong to a period */
for (i = 0; i < numTables; i++)
{
@@ -8408,9 +8521,10 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
}
/*
- * Get info about table CHECK constraints
+ * Get info about table CHECK constraints that don't belong to a PERIOD
*/
- if (tbinfo->ncheck > 0)
+ ndumpablechecks = tbinfo->ncheck - tbinfo->nperiod;
+ if (ndumpablechecks > 0)
{
ConstraintInfo *constrs;
int numConstrs;
@@ -8421,7 +8535,25 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
tbinfo->dobj.name);
resetPQExpBuffer(q);
- if (fout->remoteVersion >= 90200)
+ if (fout->remoteVersion >= 0)
+ {
+ /*
+ * PERIODs were added in v12 and we don't dump CHECK
+ * constraints for them.
+ */
+ appendPQExpBuffer(q,
+ "SELECT tableoid, oid, conname, "
+ "pg_catalog.pg_get_constraintdef(oid) AS consrc, "
+ "conislocal, convalidated "
+ "FROM pg_catalog.pg_constraint "
+ "WHERE conrelid = '%u'::pg_catalog.oid "
+ " AND contype = 'c' "
+ " AND NOT EXISTS (SELECT FROM pg_period "
+ " WHERE (perrelid, perconstraint) = (conrelid, pg_constraint.oid)) "
+ "ORDER BY conname",
+ tbinfo->dobj.catId.oid);
+ }
+ else if (fout->remoteVersion >= 90200)
{
/*
* convalidated is new in 9.2 (actually, it is there in 9.1,
@@ -8463,12 +8595,12 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
res = ExecuteSqlQuery(fout, q->data, PGRES_TUPLES_OK);
numConstrs = PQntuples(res);
- if (numConstrs != tbinfo->ncheck)
+ if (numConstrs != ndumpablechecks)
{
write_msg(NULL, ngettext("expected %d check constraint on table \"%s\" but found %d\n",
"expected %d check constraints on table \"%s\" but found %d\n",
- tbinfo->ncheck),
- tbinfo->ncheck, tbinfo->dobj.name, numConstrs);
+ ndumpablechecks),
+ ndumpablechecks, tbinfo->dobj.name, numConstrs);
write_msg(NULL, "(The system catalogs might be corrupted.)\n");
exit_nicely(1);
}
@@ -8526,6 +8658,76 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
}
PQclear(res);
}
+
+ /*
+ * Get info about PERIOD definitions
+ */
+ if (tbinfo->nperiod > 0)
+ {
+ PeriodInfo *periods;
+ int numPeriods;
+
+ /* We shouldn't have any periods before v12 */
+ Assert(fout->remoteVersion >= 0); /* TODO */
+
+ if (g_verbose)
+ write_msg(NULL, "finding periods for table \"%s.%s\"\n",
+ 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, "
+ " no.nspname AS opcnamespace, o.opcname, "
+ " 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_opclass AS o ON o.oid = p.peropclass "
+ "JOIN pg_catalog.pg_namespace AS no ON no.oid = o.opcnamespace "
+ "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)
+ {
+ write_msg(NULL, ngettext("expected %d period on table \"%s\" but found %d\n",
+ "expected %d periods on table \"%s\" but found %d\n",
+ tbinfo->nperiod),
+ tbinfo->nperiod, tbinfo->dobj.name, numPeriods);
+ write_msg(NULL, "(The system catalogs might be corrupted.)\n");
+ 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].opcnamespace = pg_strdup(PQgetvalue(res, j, 5));
+ periods[j].opcname = pg_strdup(PQgetvalue(res, j, 6));
+ periods[j].conname = pg_strdup(PQgetvalue(res, j, 7));
+ }
+ PQclear(res);
+ }
}
destroyPQExpBuffer(q);
@@ -9736,6 +9938,8 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
case DO_FK_CONSTRAINT:
dumpConstraint(fout, (ConstraintInfo *) dobj);
break;
+ case DO_PERIOD:
+ break;
case DO_PROCLANG:
dumpProcLang(fout, (ProcLangInfo *) dobj);
break;
@@ -15553,10 +15757,38 @@ dumpTableSchema(Archive *fout, 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 *opcnamespace = pg_strdup(fmtId(period->opcnamespace));
+ char *opcname = pg_strdup(fmtId(period->opcname));
+ 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 (operator_class = %s.%s, constraint_name = %s)",
+ name, start, end,
+ opcnamespace, opcname,
+ conname);
+
+ actual_atts++;
+ }
+
/*
* Add non-inherited CHECK constraints, if any.
*/
- for (j = 0; j < tbinfo->ncheck; j++)
+ for (j = 0; j < tbinfo->ncheck - tbinfo->nperiod; j++)
{
ConstraintInfo *constr = &(tbinfo->checkexprs[j]);
@@ -15723,7 +15955,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
}
- for (k = 0; k < tbinfo->ncheck; k++)
+ for (k = 0; k < tbinfo->ncheck - tbinfo->nperiod; k++)
{
ConstraintInfo *constr = &(tbinfo->checkexprs[k]);
@@ -16001,7 +16233,7 @@ dumpTableSchema(Archive *fout, 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]);
@@ -17844,6 +18076,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 e96c662b1e..631e94a997 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -62,6 +62,7 @@ typedef enum
DO_TRIGGER,
DO_CONSTRAINT,
DO_FK_CONSTRAINT, /* see note for ConstraintInfo */
+ DO_PERIOD,
DO_PROCLANG,
DO_CAST,
DO_TABLE_DATA,
@@ -279,12 +280,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 */
char *reloftype; /* underlying type for typed table */
/* these two are set only if table is a sequence owned by a column: */
Oid owning_tab; /* OID of table owning sequence */
@@ -320,6 +323,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 */
char *partkeydef; /* partition key definition */
char *partbound; /* partition bound definition */
bool needs_override; /* has GENERATED ALWAYS AS IDENTITY */
@@ -448,6 +452,17 @@ typedef struct _constraintInfo
bool separate; /* true if must dump as separate item */
} 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 *opcnamespace; /* the name of the operator class schema */
+ char *opcname; /* the name of the operator class */
+ char *conname; /* the name of the CHECK constraint */
+} PeriodInfo;
+
typedef struct _procLangInfo
{
DumpableObject dobj;
@@ -698,6 +713,7 @@ extern InhInfo *getInherits(Archive *fout, int *numInherits);
extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
extern void getExtendedStatistics(Archive *fout);
extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
+/*extern void getPeriods(Archive *fout, TableInfo tblinfo[], int numTables);*/
extern RuleInfo *getRules(Archive *fout, int *numRules);
extern void getTriggers(Archive *fout, TableInfo tblinfo[], int numTables);
extern ProcLangInfo *getProcLangs(Archive *fout, int *numProcLangs);
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index d2b0949d6b..57aab962ef 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -63,6 +63,7 @@ static const int dbObjectTypePriority[] =
32, /* DO_TRIGGER */
27, /* DO_CONSTRAINT */
33, /* DO_FK_CONSTRAINT */
+ 30, /* DO_PERIOD */
2, /* DO_PROCLANG */
10, /* DO_CAST */
23, /* DO_TABLE_DATA */
@@ -1361,6 +1362,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 e5b3c1ebf9..d694448130 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2172,6 +2172,40 @@ describeOneTableDetails(const char *schemaname,
PGresult *result = NULL;
int tuples = 0;
+ /* print periods */
+ if (pset.sversion >= 0) /* TODO */
+ {
+ 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 46c271a46c..8031e9015b 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -149,6 +149,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 59fc052494..9e872a9b17 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -106,6 +106,10 @@ extern List *AddRelationNewConstraints(Relation rel,
extern void RelationClearMissing(Relation rel);
+extern Oid StorePeriod(Relation rel, const char *period,
+ AttrNumber startnum, AttrNumber endnum,
+ Oid opclass, Oid conoid);
+
extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum,
Node *expr, bool is_internal,
bool add_column_mode);
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index 24915824ca..7a2703901c 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -339,6 +339,11 @@ DECLARE_UNIQUE_INDEX(pg_replication_origin_roname_index, 6002, on pg_replication
DECLARE_UNIQUE_INDEX(pg_partitioned_table_partrelid_index, 3351, on pg_partitioned_table using btree(partrelid oid_ops));
#define PartitionedRelidIndexId 3351
+DECLARE_UNIQUE_INDEX(pg_period_oid_index, 4101, on pg_period using btree(oid oid_ops));
+#define PeriodObjectIndexId 4101
+DECLARE_UNIQUE_INDEX(pg_period_perrelid_pername_index, 3998, on pg_period using btree(perrelid oid_ops, pername name_ops));
+#define PeriodRelidNameIndexId 3998
+
DECLARE_UNIQUE_INDEX(pg_publication_oid_index, 6110, on pg_publication using btree(oid oid_ops));
#define PublicationObjectIndexId 6110
diff --git a/src/include/catalog/pg_period.h b/src/include/catalog/pg_period.h
new file mode 100644
index 0000000000..c5c84252ce
--- /dev/null
+++ b/src/include/catalog/pg_period.h
@@ -0,0 +1,51 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_period.h
+ * definition of the "period" system catalog (pg_period)
+ *
+ *
+ * Portions Copyright (c) 1996-2018, 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,3996,PeriodRelationId)
+{
+ 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 */
+ Oid peropclass; /* OID of the operator class used */
+ Oid perconstraint; /* OID of (start < end) constraint */
+ bool perislocal; /* is the period local or inherited? */
+ int32 perinhcount; /* number of parents having this period */
+} 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;
+
+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/nodes/nodes.h b/src/include/nodes/nodes.h
index adb159a6da..27d1c2a379 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -447,6 +447,7 @@ typedef enum NodeTag
T_ColumnDef,
T_IndexElem,
T_Constraint,
+ T_Period,
T_DefElem,
T_RangeTblEntry,
T_RangeTblFunction,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 6390f7e8c1..60d12268b9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1660,6 +1660,7 @@ typedef enum ObjectType
OBJECT_OPCLASS,
OBJECT_OPERATOR,
OBJECT_OPFAMILY,
+ OBJECT_PERIOD,
OBJECT_POLICY,
OBJECT_PROCEDURE,
OBJECT_PUBLICATION,
@@ -1749,6 +1750,8 @@ typedef enum AlterTableType
AT_AddIndexConstraint, /* add constraint using existing index */
AT_DropConstraint, /* drop constraint */
AT_DropConstraintRecurse, /* internal to commands/tablecmds.c */
+ 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 (...) */
@@ -2000,9 +2003,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, Period, and Constraint nodes are
+ * intermixed in tableElts; periods and constraints are NIL. After parse analysis,
+ * tableElts contains just ColumnDefs, periods contains just Period nodes, and constraints contains just
* Constraint nodes (in fact, only CONSTR_CHECK nodes, in the present
* implementation).
* ----------------------
@@ -2018,6 +2021,7 @@ typedef struct CreateStmt
PartitionBoundSpec *partbound; /* FOR VALUES clause */
PartitionSpec *partspec; /* PARTITION BY clause */
TypeName *ofTypename; /* OF typename */
+ List *periods; /* periods (list of Period nodes) */
List *constraints; /* constraints (list of Constraint nodes) */
List *options; /* options from WITH clause */
OnCommitAction oncommit; /* what do we do at COMMIT? */
@@ -2025,6 +2029,22 @@ typedef struct CreateStmt
bool if_not_exists; /* just do nothing if it already exists? */
} CreateStmt;
+
+/* ----------
+ * Definitions for periods in CreateStmt
+ * ----------
+ */
+
+typedef struct Period
+{
+ NodeTag type;
+ char *periodname; /* period name */
+ char *startcolname; /* name of start column */
+ char *endcolname; /* name of end column */
+ List *options; /* options from WITH clause */
+ int location; /* token location, or -1 if unknown */
+} Period;
+
/* ----------
* Definitions for constraints in CreateStmt
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 23db40147b..59ea903cbc 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -300,6 +300,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, RESERVED_KEYWORD)
PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h
index 4f333586ee..f28dfb0e4e 100644
--- a/src/include/utils/syscache.h
+++ b/src/include/utils/syscache.h
@@ -73,6 +73,8 @@ enum SysCacheIdentifier
OPFAMILYAMNAMENSP,
OPFAMILYOID,
PARTRELID,
+ PERIODNAME,
+ PERIODOID,
PROCNAMEARGSNSP,
PROCOID,
PUBLICATIONNAME,
diff --git a/src/test/regress/expected/periods.out b/src/test/regress/expected/periods.out
new file mode 100644
index 0000000000..787b68eff5
--- /dev/null
+++ b/src/test/regress/expected/periods.out
@@ -0,0 +1,55 @@
+/* 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: data type xml has no default operator class for access method "btree"
+HINT: You must specify an operator class for the index or define a default operator class for the data 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
+/* 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
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 0aa5357917..329864e7f1 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -136,6 +136,7 @@ pg_opclass|t
pg_operator|t
pg_opfamily|t
pg_partitioned_table|t
+pg_period|t
pg_pltemplate|t
pg_policy|t
pg_proc|t
@@ -165,6 +166,7 @@ pg_type|t
pg_user_mapping|t
point_tbl|t
polygon_tbl|t
+pt|f
quad_box_tbl|t
quad_point_tbl|t
quad_poly_tbl|t
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 16f979c8d9..95165c5f41 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -60,7 +60,7 @@ test: create_index create_view index_including
# ----------
# Another group of parallel tests
# ----------
-test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table vacuum drop_if_exists updatable_views rolenames roleattributes create_am hash_func
+test: create_aggregate create_function_3 create_cast constraints triggers inherit create_table_like typed_table periods vacuum drop_if_exists updatable_views rolenames roleattributes create_am hash_func
# ----------
# sanity_check does a vacuum, affecting the sort order of SELECT *
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 42632be675..80c9d857ba 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -75,6 +75,7 @@ test: triggers
test: inherit
test: create_table_like
test: typed_table
+test: periods
test: vacuum
test: drop_if_exists
test: updatable_views
diff --git a/src/test/regress/sql/periods.sql b/src/test/regress/sql/periods.sql
new file mode 100644
index 0000000000..4b40890b34
--- /dev/null
+++ b/src/test/regress/sql/periods.sql
@@ -0,0 +1,40 @@
+/* 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));
+
+/* 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;