SQL:2011 PERIODS vs Postgres Ranges?

Started by Paul A Jungwirthabout 7 years ago36 messages
#1Paul A Jungwirth
pj@illuminatedcomputing.com

Hello,

I'm interested in contributing some temporal database functionality to
Postgres, starting with temporal primary and foreign keys. I know some
other folks nearby interested in helping out, too. But before we begin
I'd like to ask the community about complying with the SQL:2011
standard [1]I'm using the draft docs at https://www.wiscorp.com/SQLStandards.html at the link titled "SQL:20nn Working Draft Documents". Several of the PDFs in that zip file mention the new temporal features, but by far the most important is Part 2 (7IWD2-02-Foundation-2011-12.pdf). If you search for "period" you should find lots of results. for these things.

In SQL:2011, temporal features all build upon PERIODs, which are a new
concept you can attach to tables. Each PERIOD is composed of a start
column and an end column (both of some date/time type). You define
PERIODs when you CREATE TABLE or ALTER TABLE. Then you refer to the
periods when you create primary keys or foreign keys to make them
temporal. There are also a handful of new operators for testing two
ranges for overlap/succession/etc.[2]4.14.2: The operators are overlaps, equals, contains, precedes, succeeds, immediately precedes, and immediate succeeds. Most PERIODs are for tracking
the history of a *thing* over time, but if the PERIOD is named
SYSTEM_TIME it instead tracks the history of changes to *your
database*.[3]See 4.14.1 for more about the special PERIOD named SYSTEM_TIME. Whereas application-time PERIODs store a history of a *thing*, the SYSTEM_TIME PERIOD stores a history of changes to the *database* itself. (Google for "bitemporal" to read more about this.)

Personally I think PERIODs are quite disappointing. They are not part
of relational theory. They are not a column, but something else. If
you say `SELECT * FROM t` you don't get `PERIODs` (as far as I can
tell). But you can mention PERIODs approximately wherever you can
mention columns [4]In Part 12 the `<identifier chain>` of SQL syntax is amended to include `PERIODs` as well as columns., so now we have to support them when projecting,
selecting, joining, aggregating, etc. (Or if we are permitted to not
support them in some of those places, isn't that even worse?)

You can see that PERIODs share a lot with Postgres's own range types.
But ranges are a real column, requiring no special-case behavior,
either for RDBMS implementers or SQL users. They have a richer set of
operators.[5]https://www.postgresql.org/docs/current/static/functions-range.html They don't require any special declarations to put them
in a table. They aren't limited to just date/time types. You can even
define new range types yourself (e.g. I've found it helpful before to
define inetrange and floatrange). Also the start/end columns of a
PERIOD must be not nullable,[6]4.6.5.3: "The columns shall both be of a datetime data type and known not nullable." so that unbounded ranges must use
sentinels like `01 JAN 0000` or `01 JAN 3000` instead. Also there is
no way (as far as I can tell) to define and use a period within a
subquery or CTE or view. Many of these criticisms of PERIODs you can
find in [7]C. J. Date, Hugh Darwen, Nikos Lorentzos. Time and Relational Theory, Second Edition: Temporal Databases in the Relational Model and SQL. 2nd edition, 2014., pages 403 - 410 (where "interval" means basically our own
range types), plus others: for example PERIODs are always closed/open,
you can only have a single application PERIOD per table, they are
wordy, etc.

I expect that any Postgres implementation of the standard would wind
up using ranges internally. For example a temporal primary key would
use an exclusion constraint based on a range expression, so if you had
a PERIOD defined on columns named `valid_start` and `valid_end`, the
PK would use something like `EXCLUDE USING gist (id WITH =,
tstzrange(valid_start, valid_end) WITH &&)`. Also the new SQL:2011
operators would be easy to implement on top of our range operators.
And then a temporal foreign key implementation would use either those
or raw range operators.

So is there any way for Postgres to offer the same temporal features,
but give users the choice of using either PERIODs or ranges? If we
built that, would the community be interested in it? I think there are
several possible ways to go about it:

1. Permit defining PERIODs on either a start/end column pair, or an
existing range column. Then everything else continues to use PERIODs.
This seems tidy to implement, although since it acquiesces to the
PERIOD-based approach for temporal functionality, it doesn't solve all
the problems above. Also as [9]https://www.postgresql-archive.org/Periods-td6022563.html points out, it would lead to
incompatibilities in the new `information_schema` views. E.g.
`periods` is supposed to have `start_column_name` and
`end_column_name` columns.[8]5.38 (7IWD2-11-Schemata-2011-12.pdf).

2. Permit either ranges or PERIODs in the new syntax, e.g. `PRIMARY
KEY (id, valid_at WITHOUT OVERLAPS)` where `valid_at` is either a
PERIOD or a range column. Similarly with foreign keys. There is
probably some `information_schema` messiness here too, but perhaps
less than with #1. This seems like a great alternative to
application-time PERIODs, but I'm not sure how you'd tell Postgres to
use a range column for the system-time dimension.[3]See 4.14.1 for more about the special PERIOD named SYSTEM_TIME. Whereas application-time PERIODs store a history of a *thing*, the SYSTEM_TIME PERIOD stores a history of changes to the *database* itself. Perhaps just a
function, and then the PERIOD of `SYSTEM_TIME` would call that
function (with a range expression).

3. Build our own abstractions on top of ranges, and then use those to
implement PERIOD-based features. This is the least clear option, and I
imagine it would require a lot more design effort. Our range types are
already a step in this direction. Does anyone think this approach has
promise? If so I can start thinking about how we'd do it. I imagine we
could use a lot of the ideas in [7]C. J. Date, Hugh Darwen, Nikos Lorentzos. Time and Relational Theory, Second Edition: Temporal Databases in the Relational Model and SQL. 2nd edition, 2014..

4. Just give up and follow the standard to the letter. I'm not
enthusiastic about this, but I also really want temporal features, so
I might still do the work if that's what folks preferred.

Left to my own devices I would probably go with a mix of #2 & #3,
where temporal functionality is exposed by a layer of public functions
that use ranges (maybe accepting PERIODs too), and then implement the
PERIOD-based syntax by calling those functions. Using functions for
the range-based layer isn't as "strong" an abstraction as designing
SQL syntax, so it should be less effort, and also reduce risk of
future conflicts. I'd still personally really appreciate *also* doing
some #2 though, so that I could access those features via SQL syntax
(not functions), but with ranges instead of PERIODs. What do the rest
of you think?

Also, just how strictly do we have to follow the standard? Requiring
sentinels like '01 JAN 3000` just seems so silly. Could Postgres
permit nullable start/end PERIOD columns, and give them the same
meaning as ranges (unbounded)? Even if I forgot about ranges
altogether, I'd sure love to avoid these sentinels.

Finally: I know Vik Fearing already made a start at defining
PERIODs.[9]https://www.postgresql-archive.org/Periods-td6022563.html I don't know if he's gone any further, but perhaps he can
chime in if so. I'd be happy to build on what he's done already.

I'm eager to start work on this, but I also want to get some community
buy-in before I go too far. Temporal is such a massive set of
concepts, I believe it's important to have some discussion before just
jumping in. (Btw I've written an annotated bibliography about temporal
databases at [10]https://illuminatedcomputing.com/posts/2017/12/temporal-databases-bibliography/ if anyone wants to read more.) If I can do anything
to facilitate a fuller plan, let me know. I can write up a more
detailed proposal, etc. Thanks for your feedback!

Yours,
Paul

[1]: I'm using the draft docs at https://www.wiscorp.com/SQLStandards.html at the link titled "SQL:20nn Working Draft Documents". Several of the PDFs in that zip file mention the new temporal features, but by far the most important is Part 2 (7IWD2-02-Foundation-2011-12.pdf). If you search for "period" you should find lots of results.
https://www.wiscorp.com/SQLStandards.html at the link titled "SQL:20nn
Working Draft Documents". Several of the PDFs in that zip file mention
the new temporal features, but by far the most important is Part 2
(7IWD2-02-Foundation-2011-12.pdf). If you search for "period" you
should find lots of results.

[2]: 4.14.2: The operators are overlaps, equals, contains, precedes, succeeds, immediately precedes, and immediate succeeds.
succeeds, immediately precedes, and immediate succeeds.

[3]: See 4.14.1 for more about the special PERIOD named SYSTEM_TIME. Whereas application-time PERIODs store a history of a *thing*, the SYSTEM_TIME PERIOD stores a history of changes to the *database* itself.
Whereas application-time PERIODs store a history of a *thing*, the
SYSTEM_TIME PERIOD stores a history of changes to the *database*
itself.

[4]: In Part 12 the `<identifier chain>` of SQL syntax is amended to include `PERIODs` as well as columns.
include `PERIODs` as well as columns.

[5]: https://www.postgresql.org/docs/current/static/functions-range.html

[6]: 4.6.5.3: "The columns shall both be of a datetime data type and known not nullable."
known not nullable."

[7]: C. J. Date, Hugh Darwen, Nikos Lorentzos. Time and Relational Theory, Second Edition: Temporal Databases in the Relational Model and SQL. 2nd edition, 2014.
Theory, Second Edition: Temporal Databases in the Relational Model and
SQL. 2nd edition, 2014.

[8]: 5.38 (7IWD2-11-Schemata-2011-12.pdf).

[9]: https://www.postgresql-archive.org/Periods-td6022563.html

[10]: https://illuminatedcomputing.com/posts/2017/12/temporal-databases-bibliography/

#2Isaac Morland
isaac.morland@gmail.com
In reply to: Paul A Jungwirth (#1)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Sun, 21 Oct 2018 at 14:18, Paul A Jungwirth <pj@illuminatedcomputing.com>
wrote:

Also, just how strictly do we have to follow the standard? Requiring
sentinels like '01 JAN 3000` just seems so silly. Could Postgres
permit nullable start/end PERIOD columns, and give them the same
meaning as ranges (unbounded)? Even if I forgot about ranges
altogether, I'd sure love to avoid these sentinels.

We have "infinity" and "-infinity" values in our date and timestamp types:

https://www.postgresql.org/docs/current/static/datatype-datetime.html

I think this avoids the silliness with sentinel values.

For myself, I don't care about PERIOD etc. one bit. The "every new
capability gets its own syntax" model that SQL follows is very
old-fashioned, and for good reason. I'm happy with ranges and exclusion
constraints. But if we can provide an implementation of PERIOD that makes
it easier to port applications written for legacy database systems, it
might be worthwhile.

#3Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Paul A Jungwirth (#1)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On 21/10/2018 21:17, Paul A Jungwirth wrote:

3. Build our own abstractions on top of ranges, and then use those to
implement PERIOD-based features. This is the least clear option, and I
imagine it would require a lot more design effort. Our range types are
already a step in this direction. Does anyone think this approach has
promise? If so I can start thinking about how we'd do it. I imagine we
could use a lot of the ideas in [7].
...
[7] C. J. Date, Hugh Darwen, Nikos Lorentzos. Time and Relational
Theory, Second Edition: Temporal Databases in the Relational Model and
SQL. 2nd edition, 2014.

+1 on this approach. I think [7] got the model right. If we can
implement SQL-standard PERIODs on top of it, then that's a bonus, but
having sane, flexible, coherent set of range operators is more important
to me.

What are we missing? It's been years since I read that book, but IIRC
temporal joins is one thing, at least. What features do you have in mind?

- Heikki

#4Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Heikki Linnakangas (#3)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Sun, Oct 21, 2018 at 12:11 PM Heikki Linnakangas <hlinnaka@iki.fi> wrote:

On 21/10/2018 21:17, Paul A Jungwirth wrote:

3. Build our own abstractions on top of ranges, and then use those to
implement PERIOD-based features.

+1 on this approach. I think [7] got the model right. If we can
implement SQL-standard PERIODs on top of it, then that's a bonus, but
having sane, flexible, coherent set of range operators is more important
to me.

Okay, I'm surprised to hear from you and Isaac that following the
standard isn't as important as I thought, but I'm certainly pleased
not to make it the focus. I just thought that Postgres's reputation
was to be pretty careful about sticking to it. (I think we could still
add a standard-compliant layer, but like you I don't feel a duty to
suffer from it.) It sounds like I should work out some proposed
function signatures and write up how to use them, and see what people
think. Is that a useful approach?

What are we missing?

Here are a few big ones:

1. Define temporal primary keys and foreign keys that are known to the
database catalog and controlled as higher-level objects. For instance
I wrote an extension at https://github.com/pjungwir/time_for_keys to
create temporal foreign keys, but the database isn't "aware" of them.
That means they are more cluttered in `\d foo` than necessary (you see
the trigger constraints instead of something about a foreign key),
they don't automatically disappear if you drop the column, it is hard
to make them "polymorphic" (My extension supports only
int+tstzrange.), they don't validate that the referenced table has a
declared temporal PK, they probably have slightly different
locking/transaction semantics than the real RI code, etc. This is what
I'd like to implement right now.

2. System time: automatically track DML changes to the table, and let
you query "as of" a given time.

3. Temporal joins. I don't want to tackle this myself, because there
is already an amazing proposed patch that does everything we could ask
for at https://www.postgresql-archive.org/PROPOSAL-Temporal-query-processing-with-range-types-tt5913058.html
(recently updated btw, so I hope someone will look at it!).

4. Temporal UPDATE/DELETE: these should be converted to instead change
the end time of old rows and insert new rows with the changed
attributes. I'm interested in implementing this too, but one thing at
a time. . . .

I really appreciate your sharing your thoughts!

Paul

#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Paul A Jungwirth (#4)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Hi

ne 21. 10. 2018 v 21:47 odesílatel Paul A Jungwirth <
pj@illuminatedcomputing.com> napsal:

On Sun, Oct 21, 2018 at 12:11 PM Heikki Linnakangas <hlinnaka@iki.fi>
wrote:

On 21/10/2018 21:17, Paul A Jungwirth wrote:

3. Build our own abstractions on top of ranges, and then use those to
implement PERIOD-based features.

+1 on this approach. I think [7] got the model right. If we can
implement SQL-standard PERIODs on top of it, then that's a bonus, but
having sane, flexible, coherent set of range operators is more important
to me.

Okay, I'm surprised to hear from you and Isaac that following the
standard isn't as important as I thought, but I'm certainly pleased
not to make it the focus. I just thought that Postgres's reputation
was to be pretty careful about sticking to it. (I think we could still
add a standard-compliant layer, but like you I don't feel a duty to
suffer from it.) It sounds like I should work out some proposed
function signatures and write up how to use them, and see what people
think. Is that a useful approach?

It can be very unhappy if we cannot to implement standard syntax and
behave. The implementation behind or another is not too important. We
should not to accept any design that don't allow implement standard.

The world is 10 years after standards (maybe more). Now, this feature is
implemented in MySQL/MariaDB, and I expecting a press to have standardized
syntax after 5 years.

Regards

Pavel

Show quoted text

What are we missing?

Here are a few big ones:

1. Define temporal primary keys and foreign keys that are known to the
database catalog and controlled as higher-level objects. For instance
I wrote an extension at https://github.com/pjungwir/time_for_keys to
create temporal foreign keys, but the database isn't "aware" of them.
That means they are more cluttered in `\d foo` than necessary (you see
the trigger constraints instead of something about a foreign key),
they don't automatically disappear if you drop the column, it is hard
to make them "polymorphic" (My extension supports only
int+tstzrange.), they don't validate that the referenced table has a
declared temporal PK, they probably have slightly different
locking/transaction semantics than the real RI code, etc. This is what
I'd like to implement right now.

2. System time: automatically track DML changes to the table, and let
you query "as of" a given time.

3. Temporal joins. I don't want to tackle this myself, because there
is already an amazing proposed patch that does everything we could ask
for at
https://www.postgresql-archive.org/PROPOSAL-Temporal-query-processing-with-range-types-tt5913058.html
(recently updated btw, so I hope someone will look at it!).

4. Temporal UPDATE/DELETE: these should be converted to instead change
the end time of old rows and insert new rows with the changed
attributes. I'm interested in implementing this too, but one thing at
a time. . . .

I really appreciate your sharing your thoughts!

Paul

#6Jeff Davis
pgsql@j-davis.com
In reply to: Heikki Linnakangas (#3)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Sun, 2018-10-21 at 22:10 +0300, Heikki Linnakangas wrote:

On 21/10/2018 21:17, Paul A Jungwirth wrote:

3. Build our own abstractions on top of ranges, and then use those
to
implement PERIOD-based features. This is the least clear option,
and I
imagine it would require a lot more design effort. Our range types
are
already a step in this direction. Does anyone think this approach
has
promise? If so I can start thinking about how we'd do it. I imagine
we
could use a lot of the ideas in [7].
...
[7] C. J. Date, Hugh Darwen, Nikos Lorentzos. Time and Relational
Theory, Second Edition: Temporal Databases in the Relational Model
and
SQL. 2nd edition, 2014.

+1 on this approach. I think [7] got the model right. If we can
implement SQL-standard PERIODs on top of it, then that's a bonus,
but
having sane, flexible, coherent set of range operators is more
important
to me.

+1 for approach #3 from me as well. It was my original intention for
range types, though my first priority was utility and not the standard.
I think we are likely to run into a few areas where they aren't a
perfect fit to the standard, but I think it's a promising approach and
we can probably work around those issues by using special operators.

What are we missing? It's been years since I read that book, but
IIRC
temporal joins is one thing, at least. What features do you have in
mind?

We do support temporal joins, just not as efficiently as I'd like, and
the language doesn't make it quite as clear as it could be.

I look at that book as a source of inspiration, but I don't think it's
simple to map features one-to-one. For instance, the model in [7] is
based heavily on pack/unpack operators, and it's hard for me to see how
those fit into SQL. Also, the pack/unpack operators have some
theoretical weirdness that the book does not make clear*.

Regards,
Jeff Davis

*: I asked in a temporal discussion group (that was unfortunately a
part of LinkedIn circa 2011 and I can't find any reference to the
discussion outside my mailbox). My question was about the significance
of the order when packing on two intervals. Hugh Darwen was kind enough
to reply at length, and offered a lot of insight, but was still
somewhat inconclusive.

#7Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: Jeff Davis (#6)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Hi Jeff,

Thanks for sharing your thoughts and encouragement! :-)

The model in [7] is
based heavily on pack/unpack operators, and it's hard for me to see
how those fit into SQL. Also, the pack/unpack operators have some
theoretical weirdness that the book does not make clear*.

*: My question was about the significance
of the order when packing on two intervals. Hugh Darwen was kind
enough to reply at length, and offered a lot of insight, but was still
somewhat inconclusive.

I'd be interested in seeing that conversation if you ever find it again.

I really like how Date/Darwen/Lorentzos use pack/unpack to explain
temporal operations as operating on every concurrent "instant"
separately, and then bringing the adjacent instants back together into
ranges again. Even if you don't materialize that approach, conceptually
it makes it easy to understand what's going on.

So what is great about the patch from Anton Dignös
(https://www.postgresql-archive.org/PROPOSAL-Temporal-query-processing-with-range-types-tt5913058.html)
is that (like Date/Darwen/Lorentzos) you still have temporal variants
for every operator in the relational algebra, but they give
straightforward & efficient implementations of each based on traditional
operators plus just their two new "normalize" and "align" operations. (I
think they renamed these in later papers/patches though?) Their main
paper is at https://files.ifi.uzh.ch/boehlen/Papers/modf174-dignoes.pdf
if anyone wants to read it. It's short! :-)

The biggest challenge implementing temporal operators in plain SQL is
merging/splitting ranges from the left & right sides of an operator so
they line up. A single row can get split into multiple rows, or several
rows might be merged into one, etc. You can see how tricky Snodgrass's
"coalesce" operation is in his book. I gave some example SQL to
implement coalesce with UNNEST plus a range_agg function at
https://github.com/pjungwir/range_agg but with the Dignös approach I
don't think you'd need that. Normalize/align targets roughly the same
problem.

Anyway I'd be curious whether the theoretical weirdness you found in
pack/unpack also applies to normalize/align.

Yours,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#8Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul Jungwirth (#7)
1 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Here is a patch for my progress on this so far. I'd love some comments
on the general approach, as I've never contributed anything this
involved before. It's not ready for a commitfest, but it would help me
to have some feedback. There are TODO comments with my major
questions.

This patch lets you say `CONSTRAINT foo PRIMARY KEY (cols, WITHOUT
OVERLAPS some_range_col)`, both in `CREATE TABLE` and `ALTER TABLE`.
It doesn't support foreign keys yet, and it only supports range
columns, not PERIODs. (I'm starting to realize that adding PERIODs
will be a lot of work, although I'm still up for it. :-) The approach
isn't exactly the #2+#3 approach I suggested previously, since
user-exposed functions seem like an odd fit with how things normally
flow out of the grammar, but it follows the goal of permitting either
ranges or PERIODs for temporal keys without breaking the SQL:2011
standard.

It adds regression and pg_dump tests, although no documentation yet. A
few of my new regress tests fail, but only the ones for PERIODs. I
don't know if I need to do anything for pg_dump's custom format. For
the SQL format it exports correct `ALTER TABLE ... ADD CONSTRAINT ...
(... WITHOUT OVERLAPS ...)` statements. Also I left a question in
bin/psql/describe.c about how to make \d show a PK WITHOUT OVERLAPS.

It is based on 3be97b97ed37b966173f027091f21d8a7605e2a5 from Nov 14,
but I can rebase it if you like.

If it's easier to read this in smaller bits, you can find my (somewhat
messy) commit history here:
https://github.com/pjungwir/postgresql/commits/temporal-pks

For a next step (assuming what I've done already isn't too bad): I
could either work on PERIODs (building on Vik Fearing's patch from a
few months ago), or add range-based temporal foreign keys. Any
suggestions?

Thanks!
Paul
On Sun, Oct 28, 2018 at 2:29 PM Paul Jungwirth
<pj@illuminatedcomputing.com> wrote:

Show quoted text

Hi Jeff,

Thanks for sharing your thoughts and encouragement! :-)

The model in [7] is
based heavily on pack/unpack operators, and it's hard for me to see
how those fit into SQL. Also, the pack/unpack operators have some
theoretical weirdness that the book does not make clear*.

*: My question was about the significance
of the order when packing on two intervals. Hugh Darwen was kind
enough to reply at length, and offered a lot of insight, but was still
somewhat inconclusive.

I'd be interested in seeing that conversation if you ever find it again.

I really like how Date/Darwen/Lorentzos use pack/unpack to explain
temporal operations as operating on every concurrent "instant"
separately, and then bringing the adjacent instants back together into
ranges again. Even if you don't materialize that approach, conceptually
it makes it easy to understand what's going on.

So what is great about the patch from Anton Dignös
(https://www.postgresql-archive.org/PROPOSAL-Temporal-query-processing-with-range-types-tt5913058.html)
is that (like Date/Darwen/Lorentzos) you still have temporal variants
for every operator in the relational algebra, but they give
straightforward & efficient implementations of each based on traditional
operators plus just their two new "normalize" and "align" operations. (I
think they renamed these in later papers/patches though?) Their main
paper is at https://files.ifi.uzh.ch/boehlen/Papers/modf174-dignoes.pdf
if anyone wants to read it. It's short! :-)

The biggest challenge implementing temporal operators in plain SQL is
merging/splitting ranges from the left & right sides of an operator so
they line up. A single row can get split into multiple rows, or several
rows might be merged into one, etc. You can see how tricky Snodgrass's
"coalesce" operation is in his book. I gave some example SQL to
implement coalesce with UNNEST plus a range_agg function at
https://github.com/pjungwir/range_agg but with the Dignös approach I
don't think you'd need that. Normalize/align targets roughly the same
problem.

Anyway I'd be curious whether the theoretical weirdness you found in
pack/unpack also applies to normalize/align.

Yours,

--
Paul ~{:-)
pj@illuminatedcomputing.com

Attachments:

temporal_pks_v0001.patchapplication/octet-stream; name=temporal_pks_v0001.patchDownload
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index db49968409..b782770531 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2917,6 +2917,7 @@ _copyConstraint(const Constraint *from)
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2effd51135..eb5516ea82 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -467,7 +467,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
@@ -3515,6 +3515,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3707,18 +3708,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3729,6 +3731,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3792,6 +3795,11 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' WITHOUT OVERLAPS columnElem { $$ = $4; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 2e222d822b..8e46c8370a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1900,7 +1900,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2268,6 +2268,142 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 			iparam->ordering = SORTBY_DEFAULT;
 			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
 			index->indexParams = lappend(index->indexParams, iparam);
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					// ereport(NOTICE, (errmsg("range %s vs column %s of type %d", without_overlaps_str, column->colname, column->typeName->typeOid)));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				iparam->name = without_overlaps_str;	// TODO: pstrdup here?
+				iparam->expr = NULL;
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+
+			List *opname = list_make1(makeString("&&"));
+			index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			index->accessMethod = "gist";
+			constraint->access_method = "gist";
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 29884f1c8b..eaaf97dfd8 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -316,7 +316,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 					  int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf);
+							 bool withoutOverlaps, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
@@ -1960,7 +1960,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				decompile_column_index_array(val, conForm->conrelid, false, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -1974,7 +1974,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2075,7 +2075,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2271,7 +2277,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2289,9 +2295,17 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", WITHOUT OVERLAPS %s", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index aecbd4a943..5b00050fba 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5067,8 +5067,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5134,7 +5135,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c8d01ed4a4..206c047739 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6772,7 +6772,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_conoid,
 				i_condef,
 				i_tablespace,
-				i_indreloptions;
+				i_indreloptions,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6826,7 +6827,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "t.reloptions AS indreloptions "
+							  "t.reloptions AS indreloptions, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -6863,7 +6865,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "t.reloptions AS indreloptions "
+							  "t.reloptions AS indreloptions, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6896,7 +6899,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "t.reloptions AS indreloptions "
+							  "t.reloptions AS indreloptions, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6925,7 +6929,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "null AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "t.reloptions AS indreloptions "
+							  "t.reloptions AS indreloptions, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -6957,7 +6962,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "null AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "null AS indreloptions "
+							  "null AS indreloptions, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -6995,6 +7001,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_condef = PQfnumber(res, "condef");
 		i_tablespace = PQfnumber(res, "tablespace");
 		i_indreloptions = PQfnumber(res, "indreloptions");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7052,6 +7059,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16429,9 +16437,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", WITHOUT OVERLAPS %s",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 685ad78669..528c527056 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -446,6 +446,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+  bool    withoutoverlaps;  /* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ec751a7c23..888c92480d 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -631,6 +631,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., WITHOUT OVERLAPS ...)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, WITHOUT OVERLAPS col2));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, WITHOUT OVERLAPS col2);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 4ca0db1d0c..11751d3bc2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2287,6 +2287,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 630cabe0b8..70ff930af4 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -134,7 +134,7 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9da8bf2f88..f47e43ca10 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2143,6 +2143,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+  Node    *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 9c7a60c092..a3e404b2a4 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -204,6 +204,7 @@ timestamp_tbl|f
 timestamptz_tbl|f
 timetz_tbl|f
 varchar_tbl|f
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
new file mode 100644
index 0000000000..3d1be41adc
--- /dev/null
+++ b/src/test/regress/expected/without_overlaps.out
@@ -0,0 +1,104 @@
+-- Tests for WITHOUT OVERLAPS.
+--
+-- test input parser
+--
+-- PK with no columns just WITHOUT OVERLAPS:
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (WITHOUT OVERLAPS valid_at)
+);
+ERROR:  syntax error at or near "OVERLAPS"
+LINE 3: ...NSTRAINT without_overlaps_pk PRIMARY KEY (WITHOUT OVERLAPS v...
+                                                             ^
+-- PK with a range column that isn't there:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+ERROR:  range or PERIOD "valid_at" named in WITHOUT OVERLAPS does not exist
+-- PK with a PERIOD that isn't there:
+-- PK with a non-range column:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+ERROR:  column "valid_at" named in WITHOUT OVERLAPS is not a range type
+-- PK with one column plus a range:
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, WITHOUT OVERLAPS valid_at)
+);
+DROP TABLE without_overlaps_test2;
+-- PK with one column plus a PERIOD:
+-- PK with two columns plus a PERIOD:
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, WITHOUT OVERLAPS valid_at);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+(1 row)
+
+--
+-- test PK inserts
+--
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+ERROR:  conflicting key value violates exclusion constraint "without_overlaps_pk"
+DETAIL:  Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+ERROR:  null value in column "id" violates not-null constraint
+DETAIL:  Failing row contains (null, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+ERROR:  null value in column "valid_at" violates not-null constraint
+DETAIL:  Failing row contains ([3,4), null).
+--
+-- test changing the PK's dependencies
+--
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ERROR:  column "valid_at" is in a primary key
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 289c658483..29c2660acd 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info
+test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info without_overlaps
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index bc43b18c62..c4b3f68a6c 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -19,6 +19,7 @@ test: uuid
 test: enum
 test: money
 test: rangetypes
+test: without_overlaps
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..ce34d94414
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,114 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (WITHOUT OVERLAPS valid_at)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, WITHOUT OVERLAPS valid_at)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, WITHOUT OVERLAPS valid_at);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, WITHOUT OVERLAPS valid_at)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
#9Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#8)
1 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Fri, Nov 23, 2018 at 3:41 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Here is a patch for my progress on this so far.

Well this is embarrassing, but my last patch used the mistaken syntax
`PRIMARY KEY (cols, WITHOUT OVERLAPS col)`. Here is a new patch which
uses the correct syntax `PRIMARY KEY (cols, col WITHOUT OVERLAPS)`.
Sorry about that! Also I went ahead and rebased it off current master.

Yours,
Paul

Attachments:

temporal_pks_v0002.patchapplication/octet-stream; name=temporal_pks_v0002.patchDownload
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index db49968409..b782770531 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2917,6 +2917,7 @@ _copyConstraint(const Constraint *from)
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2c2208ffb7..c51d94e7d6 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -467,7 +467,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
@@ -3494,6 +3494,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3686,18 +3687,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3708,6 +3710,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3771,6 +3774,11 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 52582d0a13..219bb47c43 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1868,7 +1868,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2235,6 +2235,142 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 			iparam->ordering = SORTBY_DEFAULT;
 			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
 			index->indexParams = lappend(index->indexParams, iparam);
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					// ereport(NOTICE, (errmsg("range %s vs column %s of type %d", without_overlaps_str, column->colname, column->typeName->typeOid)));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				iparam->name = without_overlaps_str;	// TODO: pstrdup here?
+				iparam->expr = NULL;
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+
+			List *opname = list_make1(makeString("&&"));
+			index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			index->accessMethod = "gist";
+			constraint->access_method = "gist";
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 4857caecaa..bb6bdd777e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -316,7 +316,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 					  int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf);
+							 bool withoutOverlaps, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
@@ -1960,7 +1960,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				decompile_column_index_array(val, conForm->conrelid, false, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -1974,7 +1974,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2075,7 +2075,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2271,7 +2277,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2289,9 +2295,17 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index c3071db1cd..a073765a84 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5002,8 +5002,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5069,7 +5070,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 9c4e1dc32a..114511a859 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6753,7 +6753,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_conoid,
 				i_condef,
 				i_tablespace,
-				i_indreloptions;
+				i_indreloptions,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6807,7 +6808,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "t.reloptions AS indreloptions "
+							  "t.reloptions AS indreloptions, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -6844,7 +6846,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "t.reloptions AS indreloptions "
+							  "t.reloptions AS indreloptions, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6877,7 +6880,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "t.reloptions AS indreloptions "
+							  "t.reloptions AS indreloptions, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6906,7 +6910,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "null AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "t.reloptions AS indreloptions "
+							  "t.reloptions AS indreloptions, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -6938,7 +6943,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "c.oid AS conoid, "
 							  "null AS condef, "
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
-							  "null AS indreloptions "
+							  "null AS indreloptions, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -6976,6 +6982,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_condef = PQfnumber(res, "condef");
 		i_tablespace = PQfnumber(res, "tablespace");
 		i_indreloptions = PQfnumber(res, "indreloptions");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7033,6 +7040,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16409,9 +16417,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 789d6a24e2..d1db5d687d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -445,6 +445,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+  bool    withoutoverlaps;  /* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 8d66fd2ee7..5d7fe74050 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -612,6 +612,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 0a181b01d9..78c9a26817 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2311,6 +2311,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 0e4007389b..0ce60d0c46 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -136,7 +136,7 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e5bdc1cec5..da6600fc53 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2141,6 +2141,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+  Node    *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index c77060d36c..6c66d2ab6d 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -204,6 +204,7 @@ timestamp_tbl|f
 timestamptz_tbl|f
 timetz_tbl|f
 varchar_tbl|f
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
new file mode 100644
index 0000000000..53475c1e7e
--- /dev/null
+++ b/src/test/regress/expected/without_overlaps.out
@@ -0,0 +1,104 @@
+-- Tests for WITHOUT OVERLAPS.
+--
+-- test input parser
+--
+-- PK with no columns just WITHOUT OVERLAPS:
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+ERROR:  syntax error at or near "WITHOUT"
+LINE 3: ...STRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OV...
+                                                             ^
+-- PK with a range column that isn't there:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  range or PERIOD "valid_at" named in WITHOUT OVERLAPS does not exist
+-- PK with a PERIOD that isn't there:
+-- PK with a non-range column:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  column "valid_at" named in WITHOUT OVERLAPS is not a range type
+-- PK with one column plus a range:
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+-- PK with one column plus a PERIOD:
+-- PK with two columns plus a PERIOD:
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
+--
+-- test PK inserts
+--
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+ERROR:  conflicting key value violates exclusion constraint "without_overlaps_pk"
+DETAIL:  Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+ERROR:  null value in column "id" violates not-null constraint
+DETAIL:  Failing row contains (null, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+ERROR:  null value in column "valid_at" violates not-null constraint
+DETAIL:  Failing row contains ([3,4), null).
+--
+-- test changing the PK's dependencies
+--
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ERROR:  column "valid_at" is in a primary key
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..7084c68904
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,114 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
#10David Steele
david@pgmasters.net
In reply to: Paul A Jungwirth (#9)
Re: Re: SQL:2011 PERIODS vs Postgres Ranges?

Hi Paul,

On 11/24/18 4:55 AM, Paul A Jungwirth wrote:

On Fri, Nov 23, 2018 at 3:41 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

Here is a patch for my progress on this so far.

Well this is embarrassing, but my last patch used the mistaken syntax
`PRIMARY KEY (cols, WITHOUT OVERLAPS col)`. Here is a new patch which
uses the correct syntax `PRIMARY KEY (cols, col WITHOUT OVERLAPS)`.
Sorry about that! Also I went ahead and rebased it off current master.

I have marked this patch as targeting PG13 since it is clearly not
material for PG12. I also added you as the patch author.

Regards,
--
-David
david@pgmasters.net

#11Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: David Steele (#10)
1 attachment(s)
Re: Re: SQL:2011 PERIODS vs Postgres Ranges?

On Tue, Mar 5, 2019 at 12:35 AM David Steele <david@pgmasters.net> wrote:

I have marked this patch as targeting PG13 since it is clearly not
material for PG12. I also added you as the patch author.

Thanks David! Targeting PG13 was my intention, so sorry if I messed up
the commitfest entry.

Here is a new patch rebased on top of master. My questions are inline
as TODO comments for whoever does the review. I'm pretty far along
with an add-on patch to create temporal *foreign* keys too, which I
think should be part of this same bundle of work. If anyone happens to
review the PK patch soon, it might help me avoid the same mistakes in
the FK work, but if not that's fine too. :-)

Yours,
Paul

Attachments:

temporal_pks_v0003.patchapplication/octet-stream; name=temporal_pks_v0003.patchDownload
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index a8a735c247..c12fb6e42c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2920,6 +2920,7 @@ _copyConstraint(const Constraint *from)
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e23e68fdb3..163a976d04 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -470,7 +470,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
@@ -3459,6 +3459,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3651,18 +3652,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3673,6 +3675,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3736,6 +3739,11 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index a37d1f18be..d7a2646491 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1872,7 +1872,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2239,6 +2239,142 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 			iparam->ordering = SORTBY_DEFAULT;
 			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
 			index->indexParams = lappend(index->indexParams, iparam);
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					// ereport(NOTICE, (errmsg("range %s vs column %s of type %d", without_overlaps_str, column->colname, column->typeName->typeOid)));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				iparam->name = without_overlaps_str;	// TODO: pstrdup here?
+				iparam->expr = NULL;
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+
+			List *opname = list_make1(makeString("&&"));
+			index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			index->accessMethod = "gist";
+			constraint->access_method = "gist";
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 85055bbb95..a9d18ed94e 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -317,7 +317,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 					  int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf);
+							 bool withoutOverlaps, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
@@ -1961,7 +1961,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				decompile_column_index_array(val, conForm->conrelid, false, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -1975,7 +1975,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2076,7 +2076,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2272,7 +2278,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2290,9 +2296,17 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d9ffb78484..3dbcf5b507 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4949,8 +4949,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5016,7 +5017,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e962ae7e91..7427711894 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6825,7 +6825,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6887,7 +6888,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -6926,7 +6928,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6961,7 +6964,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6992,7 +6996,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7026,7 +7031,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7066,6 +7072,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7125,6 +7132,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16578,9 +16586,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 2e1b90acd0..4816cc5f79 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -448,6 +448,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+	bool		withoutoverlaps;	/* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index de6895122e..b16d991caf 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -632,6 +632,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 779e48437c..baf15bac06 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2318,6 +2318,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c87bdedbbb..efb764daba 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -136,7 +136,7 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index fe35783359..60d595ba44 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2154,6 +2154,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+  Node    *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index aaaa488b3c..ecdc1b1dab 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -213,6 +213,7 @@ timestamptz_tbl|f
 timetz_tbl|f
 varchar_tbl|f
 view_base_table|t
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
new file mode 100644
index 0000000000..53475c1e7e
--- /dev/null
+++ b/src/test/regress/expected/without_overlaps.out
@@ -0,0 +1,104 @@
+-- Tests for WITHOUT OVERLAPS.
+--
+-- test input parser
+--
+-- PK with no columns just WITHOUT OVERLAPS:
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+ERROR:  syntax error at or near "WITHOUT"
+LINE 3: ...STRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OV...
+                                                             ^
+-- PK with a range column that isn't there:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  range or PERIOD "valid_at" named in WITHOUT OVERLAPS does not exist
+-- PK with a PERIOD that isn't there:
+-- PK with a non-range column:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  column "valid_at" named in WITHOUT OVERLAPS is not a range type
+-- PK with one column plus a range:
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+-- PK with one column plus a PERIOD:
+-- PK with two columns plus a PERIOD:
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
+--
+-- test PK inserts
+--
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+ERROR:  conflicting key value violates exclusion constraint "without_overlaps_pk"
+DETAIL:  Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+ERROR:  null value in column "id" violates not-null constraint
+DETAIL:  Failing row contains (null, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+ERROR:  null value in column "valid_at" violates not-null constraint
+DETAIL:  Failing row contains ([3,4), null).
+--
+-- test changing the PK's dependencies
+--
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ERROR:  column "valid_at" is in a primary key
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4051a4ad4e..7f5d975d16 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -122,7 +122,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info
+test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info without_overlaps
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index ac1ea622d6..5e2e68c3a3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -19,6 +19,7 @@ test: uuid
 test: enum
 test: money
 test: rangetypes
+test: without_overlaps
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..7084c68904
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,114 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
#12David Steele
david@pgmasters.net
In reply to: Paul A Jungwirth (#11)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Hi Pail,

On 3/10/19 2:41 AM, Paul A Jungwirth wrote:

On Tue, Mar 5, 2019 at 12:35 AM David Steele <david@pgmasters.net> wrote:

I have marked this patch as targeting PG13 since it is clearly not
material for PG12. I also added you as the patch author.

Thanks David! Targeting PG13 was my intention, so sorry if I messed up
the commitfest entry.

No worries, that's what I'm here for!

Here is a new patch rebased on top of master. My questions are inline
as TODO comments for whoever does the review. I'm pretty far along
with an add-on patch to create temporal *foreign* keys too, which I
think should be part of this same bundle of work. If anyone happens to
review the PK patch soon, it might help me avoid the same mistakes in
the FK work, but if not that's fine too. :-)

Don't worry if you don't attract review in this CF since most people are
focused on PG12 items. Even so, getting your patch in early helps
because it will have history by the time the final CFs for PG13 come around.

Regards,
--
-David
david@pgmasters.net

#13Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: David Steele (#12)
1 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Sat, Mar 9, 2019 at 10:42 PM David Steele <david@pgmasters.net> wrote:

On 3/10/19 2:41 AM, Paul A Jungwirth wrote:

I'm pretty far along
with an add-on patch to create temporal *foreign* keys too, which I
think should be part of this same bundle of work.

Here is that patch. I've changed the title from "temporal_pks" to
"temporal_fks" but it includes both.

I've rebased on top of latest master, but also the patch assumes my
other range_agg patch is already included. (I use range_agg to help
implement the foreign key checks.)

This patch is hopefully getting close, but I'd still call it a WIP. It
has docs and tests, but it hasn't had any review yet (and neither has
range_agg). Here are a few limitations:

- It supports only range types, not SQL:2011 PERIODs. I'd like to add
PERIODs too, but that seems like a major undertaking. I would probably
need to partner with someone more experienced to get it done. Maybe it
should be a separate CF entry.
- The foreign keys support only NO ACTION and RESTRICT, not
CASCADE/SET NULL/SET DEFAULT. The latter options are hard to implement
now but would be trivial after adding UPDATE/DELETE FROM t FOR PORTION
OF r FROM t1 TO t2, so I'm thinking that will be next on my list. :-)
- I'm sure there are plenty of errors & infelicities people more
knowledgeable than me can point out.

Any feedback is gratefully welcomed. :-)

Yours,
Paul

Attachments:

temporal_fks_v0001.patchapplication/octet-stream; name=temporal_fks_v0001.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 44a61ef99e..7362879f92 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -78,9 +78,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
   UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
-  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
+  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_inverval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
-  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
 class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 <phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">temporal_interval</replaceable> in a <literal>PRIMARY KEY</literal> or <literal>FOREIGN KEY</literal> constraint is:</phrase>
+
+<replaceable class="parameter">range_column_name</replaceable>
+
 </synopsis>
 
  </refsynopsisdiv>
@@ -909,7 +914,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term><literal>PRIMARY KEY</literal> (column constraint)</term>
-    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ]
+    [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] )</literal>
     <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
     <listitem>
      <para>
@@ -942,7 +948,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
      <para>
       Adding a <literal>PRIMARY KEY</literal> constraint will automatically
-      create a unique btree index on the column or group of columns used in the
+      create a unique btree (or GiST if temporal) index on the column or group of columns used in the
       constraint.  The optional <literal>INCLUDE</literal> clause allows a list
       of columns to be specified which will be included in the non-key portion
       of the index.  Although uniqueness is not enforced on the included columns,
@@ -950,6 +956,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       included columns (e.g. <literal>DROP COLUMN</literal>) can cause cascaded
       constraint and index deletion.
      </para>
+
+     <para>
+      A <literal>PRIMARY KEY</literal> with a <literal>WITHOUT OVERLAPS</literal> option
+      is a <emphasis>temporal</emphasis> primary key.
+      The <literal>WITHOUT OVERLAPS</literal> column
+      must be a range type and is used to constrain the record's applicability
+      to just that range (usually a range of dates or timestamps).
+      The main part of the primary key may be repeated elsewhere in the table,
+      as long as records with the same key don't overlap in the
+      <literal>WITHOUT OVERLAPS</literal> column.
+     </para>
+
+     <para>
+      A temporal <literal>PRIMARY KEY</literal> is enforced with an
+      <literal>EXCLUDE</literal> constraint rather than a <literal>UNIQUE</literal>
+      constraint, backed by a GiST index. You may need to install the
+      <xref linkend="btree-gist"/> extension to create temporal primary keys.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1006,8 +1030,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    <varlistentry>
     <term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal> (column constraint)</term>
 
-   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
-    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] )
+    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH <replaceable class="parameter">matchtype</replaceable> ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ]
     [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal>
@@ -1018,11 +1042,29 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       These clauses specify a foreign key constraint, which requires
       that a group of one or more columns of the new table must only
       contain values that match values in the referenced
-      column(s) of some row of the referenced table.  If the <replaceable
+      column(s) of some row of the referenced table.
+      If the <replaceable
       class="parameter">refcolumn</replaceable> list is omitted, the
       primary key of the <replaceable class="parameter">reftable</replaceable>
       is used.  The referenced columns must be the columns of a non-deferrable
-      unique or primary key constraint in the referenced table.  The user
+      unique or primary key constraint in the referenced table.
+     </para>
+
+     <para>
+      If the last column is marked with <literal>PERIOD</literal>,
+      it must be a range column, and the referenced table
+      must have a temporal primary key.
+      The non-<literal>PERIOD</literal> columns are treated normally
+      (and there must be at least one of them),
+      but the <literal>PERIOD</literal> column is not compared for equality.
+      Instead the constraint is considered satisfied
+      if the referenced table has matching records whose combined ranges completely cover
+      the referencing record.
+      In other words, the reference must have a referent for its entire duration.
+     </para>
+
+     <para>
+      The user
       must have <literal>REFERENCES</literal> permission on the referenced table
       (either the whole table, or the specific referenced columns).  The
       addition of a foreign key constraint requires a
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index 368b1dea3e..f6cfbb68f6 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -237,6 +237,7 @@ sub ParseData
 	# Scan the input file.
 	while (<$ifd>)
 	{
+    next if /^#/;
 		my $hash_ref;
 
 		if (/{/)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 6cffe550b3..751bde3f04 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2401,6 +2401,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
+							  false,	/* contemporal */
 							  is_internal); /* internally constructed? */
 
 	pfree(ccbin);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index c8d22e1b65..5a501e8ae2 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1768,6 +1768,7 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
+								   false,	/* contemporal */
 								   is_internal);
 
 	/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b6145593a3..e913741103 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -76,6 +76,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
+					  bool conTemporal,
 					  bool is_internal)
 {
 	Relation	conDesc;
@@ -184,6 +185,7 @@ CreateConstraintEntry(const char *constraintName,
 	values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
 	values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
 	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+	values[Anum_pg_constraint_contemporal - 1] = BoolGetDatum(conTemporal);
 
 	if (conkeyArray)
 		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index bfcf9472d7..bc6992ad0a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -328,9 +328,12 @@ static int transformColumnNameList(Oid relId, List *colList,
 static int transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses);
 static Oid transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
 						Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
@@ -338,7 +341,7 @@ static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
 static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
 static void validateForeignKeyConstraint(char *conname,
 							 Relation rel, Relation pkrel,
-							 Oid pkindOid, Oid constraintOid);
+							 Oid pkindOid, Oid constraintOid, bool temporal);
 static void ATController(AlterTableStmt *parsetree,
 			 Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -423,12 +426,12 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 					   Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks, int16 *pkattnum, int16 *fkattnum,
 					   Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-					   bool old_check_ok);
+					   bool old_check_ok, bool is_temporal);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
 						Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode);
+						bool old_check_ok, bool is_temporal, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 						   Relation partitionRel);
 static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
@@ -445,6 +448,12 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
 							 Oid parentConstrOid, int numfks,
 							 AttrNumber *mapped_conkey, AttrNumber *confkey,
 							 Oid *conpfeqop);
+static void FindFKComparisonOperators(Constraint *fkconstraint,
+					AlteredTableInfo *tab, int i, int16 *fkattnum,
+					bool *old_check_ok, ListCell **old_pfeqop_item,
+					Oid pktype, Oid fktype, Oid opclass,
+					bool is_temporal, bool for_overlaps,
+					Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 					 DropBehavior behavior,
 					 bool recurse, bool recursing,
@@ -4678,7 +4687,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 
 				validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
 											 con->refindid,
-											 con->conid);
+											 con->conid,
+											 fkconstraint->fk_period != NULL);
 
 				/*
 				 * No need to mark the constraint row as validated, we did
@@ -7541,6 +7551,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	bool		is_temporal = (fkconstraint->fk_period != NULL);
+	int16		pkperiodattnum[1];
+	int16		fkperiodattnum[1];
+	Oid			pkperiodtypoid[1];
+	Oid			fkperiodtypoid[1];
 	int			i;
 	int			numfks,
 				numpks;
@@ -7643,6 +7658,17 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
+	if (is_temporal)
+	{
+	  MemSet(pkperiodattnum, 0, sizeof(pkperiodattnum));
+	  MemSet(fkperiodattnum, 0, sizeof(fkperiodattnum));
+	  MemSet(pkperiodtypoid, 0, sizeof(pkperiodtypoid));
+	  MemSet(fkperiodtypoid, 0, sizeof(fkperiodtypoid));
+	  List *fk_period = list_make1(fkconstraint->fk_period);
+	  transformColumnNameList(RelationGetRelid(rel),
+							  fk_period,
+							  fkperiodattnum, fkperiodtypoid);
+	}
 
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
@@ -7655,6 +7681,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
 											&fkconstraint->pk_attrs,
 											pkattnum, pktypoid,
+											&fkconstraint->pk_period,
+											pkperiodattnum, pkperiodtypoid,
 											opclasses);
 	}
 	else
@@ -7662,8 +7690,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformColumnNameList(RelationGetRelid(pkrel),
 										 fkconstraint->pk_attrs,
 										 pkattnum, pktypoid);
+		if (is_temporal) {
+			List *pk_period = list_make1(fkconstraint->pk_period);
+			transformColumnNameList(RelationGetRelid(pkrel),
+									pk_period,
+									pkperiodattnum, pkperiodtypoid);
+		}
 		/* Look for an index matching the column list */
 		indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
+										   is_temporal, pkperiodattnum,
 										   opclasses);
 	}
 
@@ -7713,6 +7748,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
+	// TODO: Need a check that if one side has a PERIOD the other does too
+
 	/*
 	 * On the strength of a previous constraint, we might avoid scanning
 	 * tables to validate this one.  See below.
@@ -7722,187 +7759,27 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		Oid			pktype = pktypoid[i];
-		Oid			fktype = fktypoid[i];
-		Oid			fktyped;
-		HeapTuple	cla_ht;
-		Form_pg_opclass cla_tup;
-		Oid			amid;
-		Oid			opfamily;
-		Oid			opcintype;
-		Oid			pfeqop;
-		Oid			ppeqop;
-		Oid			ffeqop;
-		int16		eqstrategy;
-		Oid			pfeqop_right;
-
-		/* We need several fields out of the pg_opclass entry */
-		cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
-		if (!HeapTupleIsValid(cla_ht))
-			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
-		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
-		amid = cla_tup->opcmethod;
-		opfamily = cla_tup->opcfamily;
-		opcintype = cla_tup->opcintype;
-		ReleaseSysCache(cla_ht);
-
-		/*
-		 * Check it's a btree; currently this can never fail since no other
-		 * index AMs support unique indexes.  If we ever did have other types
-		 * of unique indexes, we'd need a way to determine which operator
-		 * strategy number is equality.  (Is it reasonable to insist that
-		 * every such index AM use btree's number for equality?)
-		 */
-		if (amid != BTREE_AM_OID)
-			elog(ERROR, "only b-tree indexes are supported for foreign keys");
-		eqstrategy = BTEqualStrategyNumber;
-
-		/*
-		 * There had better be a primary equality operator for the index.
-		 * We'll use it for PK = PK comparisons.
-		 */
-		ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
-									 eqstrategy);
-
-		if (!OidIsValid(ppeqop))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 eqstrategy, opcintype, opcintype, opfamily);
-
-		/*
-		 * Are there equality operators that take exactly the FK type? Assume
-		 * we should look through any domain here.
-		 */
-		fktyped = getBaseType(fktype);
-
-		pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
-									 eqstrategy);
-		if (OidIsValid(pfeqop))
-		{
-			pfeqop_right = fktyped;
-			ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
-										 eqstrategy);
-		}
-		else
-		{
-			/* keep compiler quiet */
-			pfeqop_right = InvalidOid;
-			ffeqop = InvalidOid;
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-		{
-			/*
-			 * Otherwise, look for an implicit cast from the FK type to the
-			 * opcintype, and if found, use the primary equality operator.
-			 * This is a bit tricky because opcintype might be a polymorphic
-			 * type such as ANYARRAY or ANYENUM; so what we have to test is
-			 * whether the two actual column types can be concurrently cast to
-			 * that type.  (Otherwise, we'd fail to reject combinations such
-			 * as int[] and point[].)
-			 */
-			Oid			input_typeids[2];
-			Oid			target_typeids[2];
-
-			input_typeids[0] = pktype;
-			input_typeids[1] = fktype;
-			target_typeids[0] = opcintype;
-			target_typeids[1] = opcintype;
-			if (can_coerce_type(2, input_typeids, target_typeids,
-								COERCION_IMPLICIT))
-			{
-				pfeqop = ffeqop = ppeqop;
-				pfeqop_right = opcintype;
-			}
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("foreign key constraint \"%s\" cannot be implemented",
-							fkconstraint->conname),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of incompatible types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktype),
-							   format_type_be(pktype))));
-
-		if (old_check_ok)
-		{
-			/*
-			 * When a pfeqop changes, revalidate the constraint.  We could
-			 * permit intra-opfamily changes, but that adds subtle complexity
-			 * without any concrete benefit for core types.  We need not
-			 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
-			 */
-			old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
-			old_pfeqop_item = lnext(old_pfeqop_item);
-		}
-		if (old_check_ok)
-		{
-			Oid			old_fktype;
-			Oid			new_fktype;
-			CoercionPathType old_pathtype;
-			CoercionPathType new_pathtype;
-			Oid			old_castfunc;
-			Oid			new_castfunc;
-			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
-												   fkattnum[i] - 1);
-
-			/*
-			 * Identify coercion pathways from each of the old and new FK-side
-			 * column types to the right (foreign) operand type of the pfeqop.
-			 * We may assume that pg_constraint.conkey is not changing.
-			 */
-			old_fktype = attr->atttypid;
-			new_fktype = fktype;
-			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
-										&old_castfunc);
-			new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
-										&new_castfunc);
-
-			/*
-			 * Upon a change to the cast from the FK column to its pfeqop
-			 * operand, revalidate the constraint.  For this evaluation, a
-			 * binary coercion cast is equivalent to no cast at all.  While
-			 * type implementors should design implicit casts with an eye
-			 * toward consistency of operations like equality, we cannot
-			 * assume here that they have done so.
-			 *
-			 * A function with a polymorphic argument could change behavior
-			 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
-			 * when the cast destination is polymorphic, we only avoid
-			 * revalidation if the input type has not changed at all.  Given
-			 * just the core data types and operator classes, this requirement
-			 * prevents no would-be optimizations.
-			 *
-			 * If the cast converts from a base type to a domain thereon, then
-			 * that domain type must be the opcintype of the unique index.
-			 * Necessarily, the primary key column must then be of the domain
-			 * type.  Since the constraint was previously valid, all values on
-			 * the foreign side necessarily exist on the primary side and in
-			 * turn conform to the domain.  Consequently, we need not treat
-			 * domains specially here.
-			 *
-			 * Since we require that all collations share the same notion of
-			 * equality (which they do, because texteq reduces to bitwise
-			 * equality), we don't compare collation here.
-			 *
-			 * We need not directly consider the PK type.  It's necessarily
-			 * binary coercible to the opcintype of the unique index column,
-			 * and ri_triggers.c will only deal with PK datums in terms of
-			 * that opcintype.  Changing the opcintype also changes pfeqop.
-			 */
-			old_check_ok = (new_pathtype == old_pathtype &&
-							new_castfunc == old_castfunc &&
-							(!IsPolymorphicType(pfeqop_right) ||
-							 new_fktype == old_fktype));
-
-		}
-
-		pfeqoperators[i] = pfeqop;
-		ppeqoperators[i] = ppeqop;
-		ffeqoperators[i] = ffeqop;
+		FindFKComparisonOperators(
+				fkconstraint, tab, i, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pktypoid[i], fktypoid[i], opclasses[i],
+				is_temporal, false,
+				&pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]);
+	}
+	if (is_temporal) {
+		pkattnum[numpks] = pkperiodattnum[0];
+		fkattnum[numpks] = fkperiodattnum[0];
+		pktypoid[numpks] = pkperiodtypoid[0];
+		fktypoid[numpks] = fkperiodtypoid[0];
+
+		FindFKComparisonOperators(
+				fkconstraint, tab, numpks, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pkperiodtypoid[0], fkperiodtypoid[0], opclasses[numpks],
+				is_temporal, true,
+				&pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]);
+		numfks += 1;
+		numpks += 1;
 	}
 
 	/*
@@ -7918,7 +7795,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
-									 old_check_ok);
+									 old_check_ok,
+									 is_temporal);
 
 	/* Now handle the referencing side. */
 	addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
@@ -7931,6 +7809,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							ppeqoperators,
 							ffeqoperators,
 							old_check_ok,
+							is_temporal,
 							lockmode);
 
 	/*
@@ -7971,7 +7850,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 					   Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks,
 					   int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok,
+					   bool is_temporal)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -8047,12 +7927,13 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  fkconstraint->fk_upd_action,
 									  fkconstraint->fk_del_action,
 									  fkconstraint->fk_matchtype,
-									  NULL, /* no exclusion constraint */
+									  NULL,
 									  NULL, /* no check constraint */
 									  NULL,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
+									  is_temporal,
 									  false);	/* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -8128,7 +8009,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
-								   old_check_ok);
+								   old_check_ok, is_temporal);
 
 			/* Done -- clean up (but keep the lock) */
 			table_close(partRel, NoLock);
@@ -8177,7 +8058,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 						Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode)
+						bool old_check_ok, bool is_temporal, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
 
@@ -8323,6 +8204,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
+									  is_temporal,
 									  false);
 
 			/*
@@ -8349,6 +8231,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									ppeqoperators,
 									ffeqoperators,
 									old_check_ok,
+									is_temporal,
 									lockmode);
 
 			table_close(partition, NoLock);
@@ -8481,6 +8364,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 								   conpfeqop,
 								   conppeqop,
 								   conffeqop);
+
 		for (int i = 0; i < numfks; i++)
 			mapped_confkey[i] = attmap[confkey[i] - 1];
 
@@ -8526,7 +8410,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
-							   true);
+							   true,
+							   constrForm->contemporal);
 
 		table_close(fkRel, NoLock);
 		ReleaseSysCache(tuple);
@@ -8720,6 +8605,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
+								  constrForm->contemporal,
 								  true);
 
 		/* Set up partition dependencies for the new constraint */
@@ -8749,11 +8635,213 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conppeqop,
 								conffeqop,
 								false,	/* no old check exists */
+								constrForm->contemporal,
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
 	}
 }
 
+static void
+FindFKComparisonOperators(Constraint *fkconstraint,
+		AlteredTableInfo *tab,
+		int i,
+		int16 *fkattnum,
+		bool *old_check_ok,
+		ListCell **old_pfeqop_item,
+		Oid pktype, Oid fktype, Oid opclass,
+		bool is_temporal, bool for_overlaps,
+		Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut)
+{
+	Oid			fktyped;
+	HeapTuple	cla_ht;
+	Form_pg_opclass cla_tup;
+	Oid			amid;
+	Oid			opfamily;
+	Oid			opcintype;
+	Oid			pfeqop;
+	Oid			ppeqop;
+	Oid			ffeqop;
+	int16		eqstrategy;
+	Oid			pfeqop_right;
+
+	/* We need several fields out of the pg_opclass entry */
+	cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(cla_ht))
+		elog(ERROR, "cache lookup failed for opclass %u", opclass);
+	cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+	amid = cla_tup->opcmethod;
+	opfamily = cla_tup->opcfamily;
+	opcintype = cla_tup->opcintype;
+	ReleaseSysCache(cla_ht);
+
+	if (is_temporal)
+	{
+		if (amid != GIST_AM_OID)
+			elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
+		eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
+	}
+	else
+	{
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other types
+		 * of unique indexes, we'd need a way to determine which operator
+		 * strategy number is equality.  (Is it reasonable to insist that
+		 * every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+	}
+
+	/*
+	 * There had better be a primary equality operator for the index.
+	 * We'll use it for PK = PK comparisons.
+	 */
+	ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
+								 eqstrategy);
+
+	if (!OidIsValid(ppeqop))
+		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+			 eqstrategy, opcintype, opcintype, opfamily);
+
+	/*
+	 * Are there equality operators that take exactly the FK type? Assume
+	 * we should look through any domain here.
+	 */
+	fktyped = getBaseType(fktype);
+
+	pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+								 eqstrategy);
+	if (OidIsValid(pfeqop))
+	{
+		pfeqop_right = fktyped;
+		ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+									 eqstrategy);
+	}
+	else
+	{
+		/* keep compiler quiet */
+		pfeqop_right = InvalidOid;
+		ffeqop = InvalidOid;
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		/*
+		 * Otherwise, look for an implicit cast from the FK type to the
+		 * opcintype, and if found, use the primary equality operator.
+		 * This is a bit tricky because opcintype might be a polymorphic
+		 * type such as ANYARRAY or ANYENUM; so what we have to test is
+		 * whether the two actual column types can be concurrently cast to
+		 * that type.  (Otherwise, we'd fail to reject combinations such
+		 * as int[] and point[].)
+		 */
+		Oid			input_typeids[2];
+		Oid			target_typeids[2];
+
+		input_typeids[0] = pktype;
+		input_typeids[1] = fktype;
+		target_typeids[0] = opcintype;
+		target_typeids[1] = opcintype;
+		if (can_coerce_type(2, input_typeids, target_typeids,
+							COERCION_IMPLICIT))
+		{
+			pfeqop = ffeqop = ppeqop;
+			pfeqop_right = opcintype;
+		}
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("foreign key constraint \"%s\" cannot be implemented",
+						fkconstraint->conname),
+				 errdetail("Key columns \"%s\" and \"%s\" "
+						   "are of incompatible types: %s and %s.",
+						   strVal(list_nth(fkconstraint->fk_attrs, i)),
+						   strVal(list_nth(fkconstraint->pk_attrs, i)),
+						   format_type_be(fktype),
+						   format_type_be(pktype))));
+
+	if (*old_check_ok)
+	{
+		/*
+		 * When a pfeqop changes, revalidate the constraint.  We could
+		 * permit intra-opfamily changes, but that adds subtle complexity
+		 * without any concrete benefit for core types.  We need not
+		 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
+		 */
+		*old_check_ok = (pfeqop == lfirst_oid(*old_pfeqop_item));
+		*old_pfeqop_item = lnext(*old_pfeqop_item);
+	}
+	if (*old_check_ok)
+	{
+		Oid			old_fktype;
+		Oid			new_fktype;
+		CoercionPathType old_pathtype;
+		CoercionPathType new_pathtype;
+		Oid			old_castfunc;
+		Oid			new_castfunc;
+		Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+											   fkattnum[i] - 1);
+
+		/*
+		 * Identify coercion pathways from each of the old and new FK-side
+		 * column types to the right (foreign) operand type of the pfeqop.
+		 * We may assume that pg_constraint.conkey is not changing.
+		 */
+		old_fktype = attr->atttypid;
+		new_fktype = fktype;
+		old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
+									&old_castfunc);
+		new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
+									&new_castfunc);
+
+		/*
+		 * Upon a change to the cast from the FK column to its pfeqop
+		 * operand, revalidate the constraint.  For this evaluation, a
+		 * binary coercion cast is equivalent to no cast at all.  While
+		 * type implementors should design implicit casts with an eye
+		 * toward consistency of operations like equality, we cannot
+		 * assume here that they have done so.
+		 *
+		 * A function with a polymorphic argument could change behavior
+		 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
+		 * when the cast destination is polymorphic, we only avoid
+		 * revalidation if the input type has not changed at all.  Given
+		 * just the core data types and operator classes, this requirement
+		 * prevents no would-be optimizations.
+		 *
+		 * If the cast converts from a base type to a domain thereon, then
+		 * that domain type must be the opcintype of the unique index.
+		 * Necessarily, the primary key column must then be of the domain
+		 * type.  Since the constraint was previously valid, all values on
+		 * the foreign side necessarily exist on the primary side and in
+		 * turn conform to the domain.  Consequently, we need not treat
+		 * domains specially here.
+		 *
+		 * Since we require that all collations share the same notion of
+		 * equality (which they do, because texteq reduces to bitwise
+		 * equality), we don't compare collation here.
+		 *
+		 * We need not directly consider the PK type.  It's necessarily
+		 * binary coercible to the opcintype of the unique index column,
+		 * and ri_triggers.c will only deal with PK datums in terms of
+		 * that opcintype.  Changing the opcintype also changes pfeqop.
+		 */
+		*old_check_ok = (new_pathtype == old_pathtype &&
+						new_castfunc == old_castfunc &&
+						(!IsPolymorphicType(pfeqop_right) ||
+						 new_fktype == old_fktype));
+
+	}
+
+	*pfeqopOut = pfeqop;
+	*ppeqopOut = ppeqop;
+	*ffeqopOut = ffeqop;
+}
+
 /*
  * When the parent of a partition receives [the referencing side of] a foreign
  * key, we must propagate that foreign key to the partition.  However, the
@@ -9134,7 +9222,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 con->oid);
+										 con->oid, con->contemporal);
 			table_close(refrel, NoLock);
 
 			/*
@@ -9267,10 +9355,12 @@ transformColumnNameList(Oid relId, List *colList,
  *
  *	Look up the names, attnums, and types of the primary key attributes
  *	for the pkrel.  Also return the index OID and index opclasses of the
- *	index supporting the primary key.
+ *	index supporting the primary key.  If this is a temporal primary key,
+ *	also set the WITHOUT OVERLAPS attribute name, attnum, and atttypid.
  *
  *	All parameters except pkrel are output parameters.  Also, the function
- *	return value is the number of attributes in the primary key.
+ *	return value is the number of attributes in the primary key,
+ *	not including the WITHOUT OVERLAPS if any.
  *
  *	Used when the column list in the REFERENCES specification is omitted.
  */
@@ -9278,6 +9368,8 @@ static int
 transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses)
 {
 	List	   *indexoidlist;
@@ -9345,35 +9437,50 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 	/*
 	 * Now build the list of PK attributes from the indkey definition (we
 	 * assume a primary key cannot have expressional elements)
+	 * TODO: range expressions will be how we support PERIODs though.
 	 */
 	*attnamelist = NIL;
 	for (i = 0; i < indexStruct->indnkeyatts; i++)
 	{
 		int			pkattno = indexStruct->indkey.values[i];
 
-		attnums[i] = pkattno;
-		atttypids[i] = attnumTypeId(pkrel, pkattno);
-		opclasses[i] = indclass->values[i];
-		*attnamelist = lappend(*attnamelist,
-							   makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		if (i == indexStruct->indnkeyatts - 1 && indexStruct->indisexclusion)
+		{
+			periodattnums[i] = pkattno;
+			periodatttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*periodattname = (Node *)makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))));
+		}
+		else
+		{
+			attnums[i] = pkattno;
+			atttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*attnamelist = lappend(*attnamelist,
+								  makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		}
 	}
 
 	ReleaseSysCache(indexTuple);
 
-	return i;
+	if (indexStruct->indisexclusion) return i - 1;
+	else return i;
 }
 
 /*
  * transformFkeyCheckAttrs -
  *
  *	Make sure that the attributes of a referenced table belong to a unique
- *	(or primary key) constraint.  Return the OID of the index supporting
- *	the constraint, as well as the opclasses associated with the index
+ *	(or primary key) constraint.  Or if this is a temporal foreign key
+ *	the primary key should be an exclusion constraint instead.
+ *	Return the OID of the index supporting the constraint,
+ *	as well as the opclasses associated with the index
  *	columns.
  */
 static Oid
 transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
 						Oid *opclasses) /* output parameter */
 {
 	Oid			indexoid = InvalidOid;
@@ -9400,6 +9507,10 @@ transformFkeyCheckAttrs(Relation pkrel,
 						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 						 errmsg("foreign key referenced-columns list must not contain duplicates")));
 		}
+		if (is_temporal && attnums[i] == periodattnums[0])
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+					 errmsg("foreign key referenced-columns list must not contain duplicates")));
 	}
 
 	/*
@@ -9421,12 +9532,16 @@ transformFkeyCheckAttrs(Relation pkrel,
 		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		/*
-		 * Must have the right number of columns; must be unique and not a
+		 * Must have the right number of columns; must be unique
+		 * (or if temporal then exclusion instead) and not a
 		 * partial index; forget it if there are any expressions, too. Invalid
 		 * indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
-			indexStruct->indisunique &&
+		if ((is_temporal
+			  ? (indexStruct->indnkeyatts == numattrs + 1 &&
+				 indexStruct->indisexclusion)
+			  : (indexStruct->indnkeyatts == numattrs &&
+				 indexStruct->indisunique)) &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
 			heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
@@ -9466,6 +9581,19 @@ transformFkeyCheckAttrs(Relation pkrel,
 				if (!found)
 					break;
 			}
+			if (is_temporal)
+			{
+				found = false;
+				for (j = 0; j < numattrs + 1; j++)
+				{
+					if (periodattnums[0] == indexStruct->indkey.values[j])
+					{
+						opclasses[numattrs] = indclass->values[j];
+						found = true;
+						break;
+					}
+				}
+			}
 
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
@@ -9655,7 +9783,8 @@ validateForeignKeyConstraint(char *conname,
 							 Relation rel,
 							 Relation pkrel,
 							 Oid pkindOid,
-							 Oid constraintOid)
+							 Oid constraintOid,
+							 bool temporal)
 {
 	TupleTableSlot *slot;
 	TableScanDesc scan;
@@ -9685,8 +9814,10 @@ validateForeignKeyConstraint(char *conname,
 	/*
 	 * See if we can do it with a single LEFT JOIN query.  A false result
 	 * indicates we must proceed with the fire-the-trigger method.
+	 * We can't do a LEFT JOIN for temporal FKs yet,
+	 * but we can once we support temporal left joins.
 	 */
-	if (RI_Initial_Check(&trig, rel, pkrel))
+	if (!temporal && RI_Initial_Check(&trig, rel, pkrel))
 		return;
 
 	/*
@@ -9748,6 +9879,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 					 Oid constraintOid, Oid indexOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
+	bool is_temporal = fkconstraint->fk_period;
 
 	/*
 	 * Note: for a self-referential FK (referencing and referenced tables are
@@ -9759,7 +9891,10 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = "RI_ConstraintTrigger_c";
+	if (is_temporal)
+		fk_trigger->trigname = "TRI_ConstraintTrigger_c";
+	else
+		fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
 	fk_trigger->timing = TRIGGER_TYPE_AFTER;
@@ -9767,12 +9902,18 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	/* Either ON INSERT or ON UPDATE */
 	if (on_insert)
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_ins");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
 		fk_trigger->events = TRIGGER_TYPE_INSERT;
 	}
 	else
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_upd");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
 		fk_trigger->events = TRIGGER_TYPE_UPDATE;
 	}
 
@@ -9818,37 +9959,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_del_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_del");
+				break;
+				/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_del");
+				break;
+				*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
@@ -9874,37 +10056,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_upd_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_upd");
+				break;
+			/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_upd");
+				break;
+			*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 2beb378145..9adc3c24ba 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -776,6 +776,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* isnoinherit */
+											  false, /* contemporal */
 											  isInternal);	/* is_internal */
 	}
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7e6bcc5239..c491475fd5 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3174,6 +3174,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
+							  false,	/* contemporal */
 							  false);	/* is_internal */
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..314e56f4fb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2921,12 +2921,15 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(where_clause);
 	COPY_NODE_FIELD(pktable);
 	COPY_NODE_FIELD(fk_attrs);
+	COPY_NODE_FIELD(fk_period);
 	COPY_NODE_FIELD(pk_attrs);
+	COPY_NODE_FIELD(pk_period);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3dc0e8a4fb..bb3904b19a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -473,10 +473,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause optionalPeriodName
+				where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
+%type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
@@ -667,7 +669,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
 
@@ -3462,6 +3464,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3678,18 +3681,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3700,6 +3704,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3728,19 +3733,21 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
-				opt_column_list key_match key_actions ConstraintAttributeSpec
+			| FOREIGN KEY '(' columnList optionalPeriodName ')' REFERENCES qualified_name
+				opt_column_and_period_list key_match key_actions ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_FOREIGN;
 					n->location = @1;
-					n->pktable			= $7;
+					n->pktable			= $8;
 					n->fk_attrs			= $4;
-					n->pk_attrs			= $8;
-					n->fk_matchtype		= $9;
-					n->fk_upd_action	= (char) ($10 >> 8);
-					n->fk_del_action	= (char) ($10 & 0xFF);
-					processCASbits($11, @11, "FOREIGN KEY",
+					n->fk_period		= $5;
+					n->pk_attrs			= linitial($9);
+					n->pk_period		= lsecond($9);
+					n->fk_matchtype		= $10;
+					n->fk_upd_action	= (char) ($11 >> 8);
+					n->fk_del_action	= (char) ($11 & 0xFF);
+					processCASbits($12, @12, "FOREIGN KEY",
 								   &n->deferrable, &n->initdeferred,
 								   &n->skip_validation, NULL,
 								   yyscanner);
@@ -3763,6 +3770,21 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+optionalPeriodName:
+			',' PERIOD columnElem { $$ = $3; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+opt_column_and_period_list:
+			'(' columnList optionalPeriodName ')'			{ $$ = list_make2($2, $3); }
+			| /*EMPTY*/								{ $$ = list_make2(NIL, NULL); }
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
@@ -15472,6 +15494,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 4564c0ae81..6a2f7cf81a 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1962,7 +1962,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2351,6 +2351,151 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				notnullcmd->name = pstrdup(key);
 				notnullcmds = lappend(notnullcmds, notnullcmd);
 			}
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					// ereport(NOTICE, (errmsg("range %s vs column %s of type %d", without_overlaps_str, column->colname, column->typeName->typeOid)));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				iparam->name = without_overlaps_str;	// TODO: pstrdup here?
+				iparam->expr = NULL;
+
+				/*
+				 * Force the column to NOT NULL since it is part of the primary key.
+				 */
+				AlterTableCmd *notnullcmd = makeNode(AlterTableCmd);
+
+				notnullcmd->subtype = AT_SetNotNull;
+				notnullcmd->name = pstrdup(without_overlaps_str);
+				notnullcmds = lappend(notnullcmds, notnullcmd);
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+
+			List *opname = list_make1(makeString("&&"));
+			index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			index->accessMethod = "gist";
+			constraint->access_method = "gist";
 		}
 	}
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 095334b336..103a432d80 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -109,6 +109,7 @@ typedef struct RI_ConstraintInfo
 	char		confupdtype;	/* foreign key's ON UPDATE action */
 	char		confdeltype;	/* foreign key's ON DELETE action */
 	char		confmatchtype;	/* foreign key's match type */
+	bool		temporal;		/* if the foreign key is temporal */
 	int			nkeys;			/* number of key columns */
 	int16		pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
 	int16		fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
@@ -353,18 +354,46 @@ RI_FKey_check(TriggerData *trigdata)
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
-		 *		   FOR KEY SHARE OF x
+		 *	SELECT 1
+		 *	FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *	FOR KEY SHARE OF x
 		 * The type id's for the $ parameters are those of the
 		 * corresponding FK attributes.
+		 *
+		 * But for temporal FKs we need to make sure
+		 * the FK's range is completely covered.
+		 * So we use this query instead:
+		 *  SELECT 1
+		 *  FROM (
+		 *	    SELECT	range_agg(r, true, true) AS r
+		 *	    FROM	(
+		 *			SELECT pkperiodatt AS r
+		 *			FROM   [ONLY] pktable x
+		 *			WHERE  pkatt1 = $1 [AND ...]
+		 *			FOR KEY SHARE OF x
+		 *		) x1
+		 *  ) x2
+		 *  WHERE $n <@ x2.r[1]
+		 * Note if FOR KEY SHARE ever allows aggregate functions
+		 * we can make this a bit simpler.
 		 * ----------
 		 */
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
 		quoteRelationName(pkrelname, pk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 pk_only, pkrelname);
+		if (riinfo->temporal)
+		{
+			quoteOneName(attname,
+					RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			appendStringInfo(&querybuf,
+					"SELECT 1 FROM (SELECT range_agg(r, true, true) AS r FROM (SELECT %s AS r FROM %s%s x",
+					attname, pk_only, pkrelname);
+		}
+		else {
+			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
+							 pk_only, pkrelname);
+		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -382,6 +411,8 @@ RI_FKey_check(TriggerData *trigdata)
 			queryoids[i] = fk_type;
 		}
 		appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
+		if (riinfo->temporal)
+			appendStringInfo(&querybuf, ") x1) x2 WHERE $%d <@ x2.r[1]", riinfo->nkeys);
 
 		/* Prepare and save the plan */
 		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -1272,6 +1303,128 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 	return true;
 }
 
+/* ----------
+ * TRI_FKey_check_ins -
+ *
+ *	Check temporal foreign key existence at insert event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_ins(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+	/*
+	 * Share code with UPDATE case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_check_upd -
+ *
+ *	Check temporal foreign key existence at update event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_upd(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+	/*
+	 * Share code with INSERT case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_noaction_del -
+ *
+ *	Give an error and roll back the current transaction if the
+ *	delete has resulted in a violation of the given temporal
+ *	referential integrity constraint.
+ * ----------
+ */
+Datum
+TRI_FKey_noaction_del(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
+
+	/*
+	 * Share code with RESTRICT/UPDATE cases.
+	 */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_del -
+ *
+ * Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the delete is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with NO ACTION/UPDATE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+/*
+ * TRI_FKey_noaction_upd -
+ *
+ * Give an error and roll back the current transaction if the
+ * update has resulted in a violation of the given referential
+ * integrity constraint.
+ */
+Datum
+TRI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with RESTRICT/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_upd -
+ *
+ * Restrict update of PK to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the update is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with NO ACTION/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+
 /*
  * RI_Initial_Check -
  *
@@ -2047,6 +2200,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo->confupdtype = conForm->confupdtype;
 	riinfo->confdeltype = conForm->confdeltype;
 	riinfo->confmatchtype = conForm->confmatchtype;
+	riinfo->temporal = conForm->contemporal;
 
 	DeconstructFkConstraintRow(tup,
 							   &riinfo->nkeys,
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1e3bcb47b8..f3c8553f13 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -317,7 +317,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 					  int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf);
+							 bool withoutOverlaps, bool withPeriod, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 					   const Oid *excludeOps,
@@ -1985,6 +1985,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			{
 				Datum		val;
 				bool		isnull;
+				bool		hasperiod;
 				const char *string;
 
 				/* Start off the constraint definition */
@@ -1997,7 +1998,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it is a temporal foreign key
+				 * then it uses PERIOD.
+				 */
+				hasperiod = DatumGetBool(SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_contemporal, &isnull));
+				decompile_column_index_array(val, conForm->conrelid, false, hasperiod, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2011,7 +2018,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, hasperiod, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2112,7 +2119,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2314,7 +2327,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, bool withPeriod, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2332,9 +2345,21 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
+		else if (withPeriod && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", PERIOD %s", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index d0f6f715e6..1c12cd7e00 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4977,8 +4977,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5044,7 +5045,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index a8f1ad7333..ce43e15223 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6808,7 +6808,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6869,7 +6870,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -6908,7 +6910,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6943,7 +6946,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6974,7 +6978,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7008,7 +7013,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7048,6 +7054,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7107,6 +7114,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16557,9 +16565,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 4f9ebb4904..59dd160ebf 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -449,6 +449,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+  bool    withoutoverlaps;  /* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 5721882b3b..c3bcc95381 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -632,6 +632,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 773107227d..61a2a9a757 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2352,6 +2352,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 5a969fc61e..acccc546bc 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -103,6 +103,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	/* Has a local definition and cannot be inherited */
 	bool		connoinherit;
 
+	/*
+	 * For primary and foreign keys, signifies the last column is a range 
+	 * and should use overlaps instead of equals.
+	 */
+	bool		contemporal;
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 
 	/*
@@ -117,26 +123,26 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	int16		confkey[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * If a foreign key, the OIDs of the PK = FK comparison operators for each
 	 * column of the constraint
 	 */
 	Oid			conpfeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * If a foreign key, the OIDs of the PK = PK comparison operators for each
 	 * column of the constraint (i.e., equality for the referenced columns)
 	 */
 	Oid			conppeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * If a foreign key, the OIDs of the FK = FK comparison operators for each
 	 * column of the constraint (i.e., equality for the referencing columns)
 	 */
 	Oid			conffeqop[1];
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
@@ -211,6 +217,7 @@ extern Oid CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
+					  bool conTemporal,
 					  bool is_internal);
 
 extern void RemoveConstraintById(Oid conId);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 249220c8a2..16abc3722f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3678,6 +3678,50 @@
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'RI_FKey_noaction_upd' },
 
+# Temporal referential integrity constraint triggers
+{ oid => '6122', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_ins' },
+{ oid => '6123', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_upd' },
+# { oid => '6124', descr => 'temporal referential integrity ON DELETE CASCADE',
+#   proname => 'TRI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_del' },
+# { oid => '6125', descr => 'temporal referential integrity ON UPDATE CASCADE',
+#   proname => 'TRI_FKey_cascade_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_upd' },
+{ oid => '6126', descr => 'temporal referential integrity ON DELETE RESTRICT',
+  proname => 'TRI_FKey_restrict_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_del' },
+{ oid => '6127', descr => 'temporal referential integrity ON UPDATE RESTRICT',
+  proname => 'TRI_FKey_restrict_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_upd' },
+# { oid => '6128', descr => 'temporal referential integrity ON DELETE SET NULL',
+#   proname => 'TRI_FKey_setnull_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_del' },
+# { oid => '6129', descr => 'temporal referential integrity ON UPDATE SET NULL',
+#   proname => 'TRI_FKey_setnull_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_upd' },
+# { oid => '6130', descr => 'temporal referential integrity ON DELETE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_del', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_del' },
+# { oid => '6131', descr => 'temporal referential integrity ON UPDATE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_upd', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_upd' },
+{ oid => '6132', descr => 'temporal referential integrity ON DELETE NO ACTION',
+  proname => 'TRI_FKey_noaction_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_del' },
+{ oid => '6133', descr => 'temporal referential integrity ON UPDATE NO ACTION',
+  proname => 'TRI_FKey_noaction_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_upd' },
+
 { oid => '1666',
   proname => 'varbiteq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'varbit varbit', prosrc => 'biteq' },
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 12e9730dd0..b4f8e4f4a1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2163,7 +2163,9 @@ typedef struct Constraint
 	/* Fields used for FOREIGN KEY constraints: */
 	RangeVar   *pktable;		/* Primary key table */
 	List	   *fk_attrs;		/* Attributes of foreign key */
+	Node	   *fk_period;		/* String node naming Period or range column */
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
+	Node	   *pk_period;		/* String node naming Period or range column */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
 	char		fk_del_action;	/* ON DELETE action */
@@ -2171,6 +2173,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+	Node	   *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..92628f390a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -299,6 +299,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, RESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index bf4558952c..19dc930280 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -169,6 +169,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+referencing_period_test|t
 reservations|f
 road|t
 shighway|t
@@ -210,6 +211,7 @@ timetz_tbl|f
 tmp|f
 varchar_tbl|f
 view_base_table|t
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
new file mode 100644
index 0000000000..371f4ec8ea
--- /dev/null
+++ b/src/test/regress/expected/without_overlaps.out
@@ -0,0 +1,346 @@
+-- Tests for WITHOUT OVERLAPS.
+--
+-- test input parser
+--
+-- PK with no columns just WITHOUT OVERLAPS:
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+ERROR:  syntax error at or near "WITHOUT"
+LINE 3: ...STRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OV...
+                                                             ^
+-- PK with a range column that isn't there:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  range or PERIOD "valid_at" named in WITHOUT OVERLAPS does not exist
+-- PK with a PERIOD that isn't there:
+-- PK with a non-range column:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  column "valid_at" named in WITHOUT OVERLAPS is not a range type
+-- PK with one column plus a range:
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+-- PK with one column plus a PERIOD:
+-- PK with two columns plus a PERIOD:
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
+--
+-- test PK inserts
+--
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+ERROR:  conflicting key value violates exclusion constraint "without_overlaps_pk"
+DETAIL:  Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+ERROR:  null value in column "id" violates not-null constraint
+DETAIL:  Failing row contains (null, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+ERROR:  null value in column "valid_at" violates not-null constraint
+DETAIL:  Failing row contains ([3,4), null).
+--
+-- test changing the PK's dependencies
+--
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ERROR:  column "valid_at" is in a primary key
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
+--
+-- test FK parser
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+                                      pg_get_constraintdef                                      
+------------------------------------------------------------------------------------------------
+ FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
+(1 row)
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Tue May 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([8,9), ["Tue Jan 02 00:00:00 2018","Thu Mar 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+--
+-- test FK parent updates SET NULL
+--
+--
+-- test FK parent updates SET DEFAULT
+--
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+--
+-- test FK parent deletes SET NULL
+--
+--
+-- test FK parent deletes SET DEFAULT
+--
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f23fe8d870..a6aad7f85f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info without_overlaps
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index ca200eb599..6d0eee26f5 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -19,6 +19,7 @@ test: uuid
 test: enum
 test: money
 test: rangetypes
+test: without_overlaps
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..fe5dd80db7
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,354 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
+
+--
+-- test FK parser
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+-- TODO
+--
+-- test FK parent updates SET NULL
+--
+-- TODO
+--
+-- test FK parent updates SET DEFAULT
+--
+-- TODO
+
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+-- TODO
+--
+-- test FK parent deletes SET NULL
+--
+-- TODO
+--
+-- test FK parent deletes SET DEFAULT
+--
+-- TODO
#14Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#13)
2 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Here is that patch. I've changed the title from "temporal_pks" to
"temporal_fks" but it includes both.

Here are rebased patches to add temporal PKs and FKs. Note they depend
on my other commitfest entry adding a range_agg function. The PKs
patch should be applied first, then the FKs patch. These are mostly
the same as before, but I've added a small optimization to the FK
patch. The traditional FK code skips the UPDATE triggers if the PK/FK
columns didn't change. For temporal RI, we can broaden the condition:
on a PK update, if the new PK range is a superset of the old (and the
other parts of the key are equal), we can skip the check. On an FK
update, if the new FK range is a subset of the old (and the other
parts of the key are equal), we can skip the check.

These are still very WIP patches. They include a bunch of TODO
comments where I'm hoping to get feedback. Also their "taste" is
surely questionable. In many cases I took the least-obtrusive path
even where that might not be the best one. If anyone wants to offer
some corrections, I would gratefully pay attention. :-)

My plan is to work on UPDATE/DELETE FOR PORTION OF next, then use that
to add CASCADE functionality to temporal FKs. I've started reading how
executor nodes work, but I'll probably reach out with some questions.
. . . :-) Also I need to decide how triggers should behave in a
temporal update/delete. I have my own opinion about what is sensible,
but I haven't found guidance in the standard, so I'm going to see what
other RDBMSes are doing. (If someone has a reference to a part of the
standard I overlooked, let me know! :-)

Yours,
Paul

Attachments:

temporal_fks_v0002.patchapplication/octet-stream; name=temporal_fks_v0002.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 8e1bfe4d78..2d47deb8ec 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -78,9 +78,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
   UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
-  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
+  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_inverval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
-  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
 class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 <phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">temporal_interval</replaceable> in a <literal>PRIMARY KEY</literal> or <literal>FOREIGN KEY</literal> constraint is:</phrase>
+
+<replaceable class="parameter">range_column_name</replaceable>
+
 </synopsis>
 
  </refsynopsisdiv>
@@ -909,7 +914,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term><literal>PRIMARY KEY</literal> (column constraint)</term>
-    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ]
+    [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] )</literal>
     <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
     <listitem>
      <para>
@@ -942,7 +948,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
      <para>
       Adding a <literal>PRIMARY KEY</literal> constraint will automatically
-      create a unique btree index on the column or group of columns used in the
+      create a unique btree (or GiST if temporal) index on the column or group of columns used in the
       constraint.  The optional <literal>INCLUDE</literal> clause allows a list
       of columns to be specified which will be included in the non-key portion
       of the index.  Although uniqueness is not enforced on the included columns,
@@ -950,6 +956,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       included columns (e.g. <literal>DROP COLUMN</literal>) can cause cascaded
       constraint and index deletion.
      </para>
+
+     <para>
+      A <literal>PRIMARY KEY</literal> with a <literal>WITHOUT OVERLAPS</literal> option
+      is a <emphasis>temporal</emphasis> primary key.
+      The <literal>WITHOUT OVERLAPS</literal> column
+      must be a range type and is used to constrain the record's applicability
+      to just that range (usually a range of dates or timestamps).
+      The main part of the primary key may be repeated elsewhere in the table,
+      as long as records with the same key don't overlap in the
+      <literal>WITHOUT OVERLAPS</literal> column.
+     </para>
+
+     <para>
+      A temporal <literal>PRIMARY KEY</literal> is enforced with an
+      <literal>EXCLUDE</literal> constraint rather than a <literal>UNIQUE</literal>
+      constraint, backed by a GiST index. You may need to install the
+      <xref linkend="btree-gist"/> extension to create temporal primary keys.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1006,8 +1030,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    <varlistentry>
     <term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal> (column constraint)</term>
 
-   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
-    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] )
+    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH <replaceable class="parameter">matchtype</replaceable> ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ]
     [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal>
@@ -1018,11 +1042,29 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       These clauses specify a foreign key constraint, which requires
       that a group of one or more columns of the new table must only
       contain values that match values in the referenced
-      column(s) of some row of the referenced table.  If the <replaceable
+      column(s) of some row of the referenced table.
+      If the <replaceable
       class="parameter">refcolumn</replaceable> list is omitted, the
       primary key of the <replaceable class="parameter">reftable</replaceable>
       is used.  The referenced columns must be the columns of a non-deferrable
-      unique or primary key constraint in the referenced table.  The user
+      unique or primary key constraint in the referenced table.
+     </para>
+
+     <para>
+      If the last column is marked with <literal>PERIOD</literal>,
+      it must be a range column, and the referenced table
+      must have a temporal primary key.
+      The non-<literal>PERIOD</literal> columns are treated normally
+      (and there must be at least one of them),
+      but the <literal>PERIOD</literal> column is not compared for equality.
+      Instead the constraint is considered satisfied
+      if the referenced table has matching records whose combined ranges completely cover
+      the referencing record.
+      In other words, the reference must have a referent for its entire duration.
+     </para>
+
+     <para>
+      The user
       must have <literal>REFERENCES</literal> permission on the referenced table
       (either the whole table, or the specific referenced columns).  The
       addition of a foreign key constraint requires a
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index 368b1dea3e..f6cfbb68f6 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -237,6 +237,7 @@ sub ParseData
 	# Scan the input file.
 	while (<$ifd>)
 	{
+    next if /^#/;
 		my $hash_ref;
 
 		if (/{/)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 86820eecfc..4c0ce5c4cd 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2401,6 +2401,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
+							  false,	/* contemporal */
 							  is_internal); /* internally constructed? */
 
 	pfree(ccbin);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index d2e4f53a80..f929116b17 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1770,6 +1770,7 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
+								   false,	/* contemporal */
 								   is_internal);
 
 	/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b6145593a3..e913741103 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -76,6 +76,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
+					  bool conTemporal,
 					  bool is_internal)
 {
 	Relation	conDesc;
@@ -184,6 +185,7 @@ CreateConstraintEntry(const char *constraintName,
 	values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
 	values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
 	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+	values[Anum_pg_constraint_contemporal - 1] = BoolGetDatum(conTemporal);
 
 	if (conkeyArray)
 		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index cb2c5e181b..db720fd765 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -328,9 +328,12 @@ static int	transformColumnNameList(Oid relId, List *colList,
 static int	transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 									   List **attnamelist,
 									   int16 *attnums, Oid *atttypids,
+										 Node **periodattname,
+										 int16 *periodattnums, Oid *periodatttypids,
 									   Oid *opclasses);
 static Oid	transformFkeyCheckAttrs(Relation pkrel,
 									int numattrs, int16 *attnums,
+									bool is_temporal, int16 *periodattnums,
 									Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
@@ -338,7 +341,7 @@ static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
 static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
 static void validateForeignKeyConstraint(char *conname,
 										 Relation rel, Relation pkrel,
-										 Oid pkindOid, Oid constraintOid);
+										 Oid pkindOid, Oid constraintOid, bool temporal);
 static void ATController(AlterTableStmt *parsetree,
 						 Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -423,12 +426,12 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 											Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 											int numfks, int16 *pkattnum, int16 *fkattnum,
 											Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-											bool old_check_ok);
+											bool old_check_ok, bool is_temporal);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
 									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 									int numfks, int16 *pkattnum, int16 *fkattnum,
 									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-									bool old_check_ok, LOCKMODE lockmode);
+									bool old_check_ok, bool is_temporal, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 									   Relation partitionRel);
 static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
@@ -445,6 +448,12 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
 										 Oid parentConstrOid, int numfks,
 										 AttrNumber *mapped_conkey, AttrNumber *confkey,
 										 Oid *conpfeqop);
+static void FindFKComparisonOperators(Constraint *fkconstraint,
+					AlteredTableInfo *tab, int i, int16 *fkattnum,
+					bool *old_check_ok, ListCell **old_pfeqop_item,
+					Oid pktype, Oid fktype, Oid opclass,
+					bool is_temporal, bool for_overlaps,
+					Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 								 DropBehavior behavior,
 								 bool recurse, bool recursing,
@@ -4690,7 +4699,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 
 				validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
 											 con->refindid,
-											 con->conid);
+											 con->conid,
+											 fkconstraint->fk_period != NULL);
 
 				/*
 				 * No need to mark the constraint row as validated, we did
@@ -7553,6 +7563,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	bool		is_temporal = (fkconstraint->fk_period != NULL);
+	int16		pkperiodattnum[1];
+	int16		fkperiodattnum[1];
+	Oid			pkperiodtypoid[1];
+	Oid			fkperiodtypoid[1];
 	int			i;
 	int			numfks,
 				numpks;
@@ -7655,6 +7670,17 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
+	if (is_temporal)
+	{
+	  MemSet(pkperiodattnum, 0, sizeof(pkperiodattnum));
+	  MemSet(fkperiodattnum, 0, sizeof(fkperiodattnum));
+	  MemSet(pkperiodtypoid, 0, sizeof(pkperiodtypoid));
+	  MemSet(fkperiodtypoid, 0, sizeof(fkperiodtypoid));
+	  List *fk_period = list_make1(fkconstraint->fk_period);
+	  transformColumnNameList(RelationGetRelid(rel),
+							  fk_period,
+							  fkperiodattnum, fkperiodtypoid);
+	}
 
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
@@ -7667,6 +7693,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
 											&fkconstraint->pk_attrs,
 											pkattnum, pktypoid,
+											&fkconstraint->pk_period,
+											pkperiodattnum, pkperiodtypoid,
 											opclasses);
 	}
 	else
@@ -7674,8 +7702,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformColumnNameList(RelationGetRelid(pkrel),
 										 fkconstraint->pk_attrs,
 										 pkattnum, pktypoid);
+		if (is_temporal) {
+			List *pk_period = list_make1(fkconstraint->pk_period);
+			transformColumnNameList(RelationGetRelid(pkrel),
+									pk_period,
+									pkperiodattnum, pkperiodtypoid);
+		}
 		/* Look for an index matching the column list */
 		indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
+										   is_temporal, pkperiodattnum,
 										   opclasses);
 	}
 
@@ -7725,6 +7760,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
+	// TODO: Need a check that if one side has a PERIOD the other does too
+
 	/*
 	 * On the strength of a previous constraint, we might avoid scanning
 	 * tables to validate this one.  See below.
@@ -7734,187 +7771,27 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		Oid			pktype = pktypoid[i];
-		Oid			fktype = fktypoid[i];
-		Oid			fktyped;
-		HeapTuple	cla_ht;
-		Form_pg_opclass cla_tup;
-		Oid			amid;
-		Oid			opfamily;
-		Oid			opcintype;
-		Oid			pfeqop;
-		Oid			ppeqop;
-		Oid			ffeqop;
-		int16		eqstrategy;
-		Oid			pfeqop_right;
-
-		/* We need several fields out of the pg_opclass entry */
-		cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
-		if (!HeapTupleIsValid(cla_ht))
-			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
-		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
-		amid = cla_tup->opcmethod;
-		opfamily = cla_tup->opcfamily;
-		opcintype = cla_tup->opcintype;
-		ReleaseSysCache(cla_ht);
-
-		/*
-		 * Check it's a btree; currently this can never fail since no other
-		 * index AMs support unique indexes.  If we ever did have other types
-		 * of unique indexes, we'd need a way to determine which operator
-		 * strategy number is equality.  (Is it reasonable to insist that
-		 * every such index AM use btree's number for equality?)
-		 */
-		if (amid != BTREE_AM_OID)
-			elog(ERROR, "only b-tree indexes are supported for foreign keys");
-		eqstrategy = BTEqualStrategyNumber;
-
-		/*
-		 * There had better be a primary equality operator for the index.
-		 * We'll use it for PK = PK comparisons.
-		 */
-		ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
-									 eqstrategy);
-
-		if (!OidIsValid(ppeqop))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 eqstrategy, opcintype, opcintype, opfamily);
-
-		/*
-		 * Are there equality operators that take exactly the FK type? Assume
-		 * we should look through any domain here.
-		 */
-		fktyped = getBaseType(fktype);
-
-		pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
-									 eqstrategy);
-		if (OidIsValid(pfeqop))
-		{
-			pfeqop_right = fktyped;
-			ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
-										 eqstrategy);
-		}
-		else
-		{
-			/* keep compiler quiet */
-			pfeqop_right = InvalidOid;
-			ffeqop = InvalidOid;
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-		{
-			/*
-			 * Otherwise, look for an implicit cast from the FK type to the
-			 * opcintype, and if found, use the primary equality operator.
-			 * This is a bit tricky because opcintype might be a polymorphic
-			 * type such as ANYARRAY or ANYENUM; so what we have to test is
-			 * whether the two actual column types can be concurrently cast to
-			 * that type.  (Otherwise, we'd fail to reject combinations such
-			 * as int[] and point[].)
-			 */
-			Oid			input_typeids[2];
-			Oid			target_typeids[2];
-
-			input_typeids[0] = pktype;
-			input_typeids[1] = fktype;
-			target_typeids[0] = opcintype;
-			target_typeids[1] = opcintype;
-			if (can_coerce_type(2, input_typeids, target_typeids,
-								COERCION_IMPLICIT))
-			{
-				pfeqop = ffeqop = ppeqop;
-				pfeqop_right = opcintype;
-			}
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("foreign key constraint \"%s\" cannot be implemented",
-							fkconstraint->conname),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of incompatible types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktype),
-							   format_type_be(pktype))));
-
-		if (old_check_ok)
-		{
-			/*
-			 * When a pfeqop changes, revalidate the constraint.  We could
-			 * permit intra-opfamily changes, but that adds subtle complexity
-			 * without any concrete benefit for core types.  We need not
-			 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
-			 */
-			old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
-			old_pfeqop_item = lnext(old_pfeqop_item);
-		}
-		if (old_check_ok)
-		{
-			Oid			old_fktype;
-			Oid			new_fktype;
-			CoercionPathType old_pathtype;
-			CoercionPathType new_pathtype;
-			Oid			old_castfunc;
-			Oid			new_castfunc;
-			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
-												   fkattnum[i] - 1);
-
-			/*
-			 * Identify coercion pathways from each of the old and new FK-side
-			 * column types to the right (foreign) operand type of the pfeqop.
-			 * We may assume that pg_constraint.conkey is not changing.
-			 */
-			old_fktype = attr->atttypid;
-			new_fktype = fktype;
-			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
-										&old_castfunc);
-			new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
-										&new_castfunc);
-
-			/*
-			 * Upon a change to the cast from the FK column to its pfeqop
-			 * operand, revalidate the constraint.  For this evaluation, a
-			 * binary coercion cast is equivalent to no cast at all.  While
-			 * type implementors should design implicit casts with an eye
-			 * toward consistency of operations like equality, we cannot
-			 * assume here that they have done so.
-			 *
-			 * A function with a polymorphic argument could change behavior
-			 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
-			 * when the cast destination is polymorphic, we only avoid
-			 * revalidation if the input type has not changed at all.  Given
-			 * just the core data types and operator classes, this requirement
-			 * prevents no would-be optimizations.
-			 *
-			 * If the cast converts from a base type to a domain thereon, then
-			 * that domain type must be the opcintype of the unique index.
-			 * Necessarily, the primary key column must then be of the domain
-			 * type.  Since the constraint was previously valid, all values on
-			 * the foreign side necessarily exist on the primary side and in
-			 * turn conform to the domain.  Consequently, we need not treat
-			 * domains specially here.
-			 *
-			 * Since we require that all collations share the same notion of
-			 * equality (which they do, because texteq reduces to bitwise
-			 * equality), we don't compare collation here.
-			 *
-			 * We need not directly consider the PK type.  It's necessarily
-			 * binary coercible to the opcintype of the unique index column,
-			 * and ri_triggers.c will only deal with PK datums in terms of
-			 * that opcintype.  Changing the opcintype also changes pfeqop.
-			 */
-			old_check_ok = (new_pathtype == old_pathtype &&
-							new_castfunc == old_castfunc &&
-							(!IsPolymorphicType(pfeqop_right) ||
-							 new_fktype == old_fktype));
-
-		}
-
-		pfeqoperators[i] = pfeqop;
-		ppeqoperators[i] = ppeqop;
-		ffeqoperators[i] = ffeqop;
+		FindFKComparisonOperators(
+				fkconstraint, tab, i, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pktypoid[i], fktypoid[i], opclasses[i],
+				is_temporal, false,
+				&pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]);
+	}
+	if (is_temporal) {
+		pkattnum[numpks] = pkperiodattnum[0];
+		fkattnum[numpks] = fkperiodattnum[0];
+		pktypoid[numpks] = pkperiodtypoid[0];
+		fktypoid[numpks] = fkperiodtypoid[0];
+
+		FindFKComparisonOperators(
+				fkconstraint, tab, numpks, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pkperiodtypoid[0], fkperiodtypoid[0], opclasses[numpks],
+				is_temporal, true,
+				&pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]);
+		numfks += 1;
+		numpks += 1;
 	}
 
 	/*
@@ -7930,7 +7807,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
-									 old_check_ok);
+									 old_check_ok,
+									 is_temporal);
 
 	/* Now handle the referencing side. */
 	addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
@@ -7943,6 +7821,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							ppeqoperators,
 							ffeqoperators,
 							old_check_ok,
+							is_temporal,
 							lockmode);
 
 	/*
@@ -7983,7 +7862,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 					   Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks,
 					   int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok,
+					   bool is_temporal)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -8059,12 +7939,13 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  fkconstraint->fk_upd_action,
 									  fkconstraint->fk_del_action,
 									  fkconstraint->fk_matchtype,
-									  NULL, /* no exclusion constraint */
+									  NULL,
 									  NULL, /* no check constraint */
 									  NULL,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
+									  is_temporal,
 									  false);	/* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -8140,7 +8021,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
-								   old_check_ok);
+								   old_check_ok, is_temporal);
 
 			/* Done -- clean up (but keep the lock) */
 			table_close(partRel, NoLock);
@@ -8189,7 +8070,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 						Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode)
+						bool old_check_ok, bool is_temporal, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
 
@@ -8335,6 +8216,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
+									  is_temporal,
 									  false);
 
 			/*
@@ -8361,6 +8243,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									ppeqoperators,
 									ffeqoperators,
 									old_check_ok,
+									is_temporal,
 									lockmode);
 
 			table_close(partition, NoLock);
@@ -8493,6 +8376,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 								   conpfeqop,
 								   conppeqop,
 								   conffeqop);
+
 		for (int i = 0; i < numfks; i++)
 			mapped_confkey[i] = attmap[confkey[i] - 1];
 
@@ -8538,7 +8422,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
-							   true);
+							   true,
+							   constrForm->contemporal);
 
 		table_close(fkRel, NoLock);
 		ReleaseSysCache(tuple);
@@ -8732,6 +8617,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
+								  constrForm->contemporal,
 								  true);
 
 		/* Set up partition dependencies for the new constraint */
@@ -8761,11 +8647,213 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conppeqop,
 								conffeqop,
 								false,	/* no old check exists */
+								constrForm->contemporal,
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
 	}
 }
 
+static void
+FindFKComparisonOperators(Constraint *fkconstraint,
+		AlteredTableInfo *tab,
+		int i,
+		int16 *fkattnum,
+		bool *old_check_ok,
+		ListCell **old_pfeqop_item,
+		Oid pktype, Oid fktype, Oid opclass,
+		bool is_temporal, bool for_overlaps,
+		Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut)
+{
+	Oid			fktyped;
+	HeapTuple	cla_ht;
+	Form_pg_opclass cla_tup;
+	Oid			amid;
+	Oid			opfamily;
+	Oid			opcintype;
+	Oid			pfeqop;
+	Oid			ppeqop;
+	Oid			ffeqop;
+	int16		eqstrategy;
+	Oid			pfeqop_right;
+
+	/* We need several fields out of the pg_opclass entry */
+	cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(cla_ht))
+		elog(ERROR, "cache lookup failed for opclass %u", opclass);
+	cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+	amid = cla_tup->opcmethod;
+	opfamily = cla_tup->opcfamily;
+	opcintype = cla_tup->opcintype;
+	ReleaseSysCache(cla_ht);
+
+	if (is_temporal)
+	{
+		if (amid != GIST_AM_OID)
+			elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
+		eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
+	}
+	else
+	{
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other types
+		 * of unique indexes, we'd need a way to determine which operator
+		 * strategy number is equality.  (Is it reasonable to insist that
+		 * every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+	}
+
+	/*
+	 * There had better be a primary equality operator for the index.
+	 * We'll use it for PK = PK comparisons.
+	 */
+	ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
+								 eqstrategy);
+
+	if (!OidIsValid(ppeqop))
+		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+			 eqstrategy, opcintype, opcintype, opfamily);
+
+	/*
+	 * Are there equality operators that take exactly the FK type? Assume
+	 * we should look through any domain here.
+	 */
+	fktyped = getBaseType(fktype);
+
+	pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+								 eqstrategy);
+	if (OidIsValid(pfeqop))
+	{
+		pfeqop_right = fktyped;
+		ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+									 eqstrategy);
+	}
+	else
+	{
+		/* keep compiler quiet */
+		pfeqop_right = InvalidOid;
+		ffeqop = InvalidOid;
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		/*
+		 * Otherwise, look for an implicit cast from the FK type to the
+		 * opcintype, and if found, use the primary equality operator.
+		 * This is a bit tricky because opcintype might be a polymorphic
+		 * type such as ANYARRAY or ANYENUM; so what we have to test is
+		 * whether the two actual column types can be concurrently cast to
+		 * that type.  (Otherwise, we'd fail to reject combinations such
+		 * as int[] and point[].)
+		 */
+		Oid			input_typeids[2];
+		Oid			target_typeids[2];
+
+		input_typeids[0] = pktype;
+		input_typeids[1] = fktype;
+		target_typeids[0] = opcintype;
+		target_typeids[1] = opcintype;
+		if (can_coerce_type(2, input_typeids, target_typeids,
+							COERCION_IMPLICIT))
+		{
+			pfeqop = ffeqop = ppeqop;
+			pfeqop_right = opcintype;
+		}
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("foreign key constraint \"%s\" cannot be implemented",
+						fkconstraint->conname),
+				 errdetail("Key columns \"%s\" and \"%s\" "
+						   "are of incompatible types: %s and %s.",
+						   strVal(list_nth(fkconstraint->fk_attrs, i)),
+						   strVal(list_nth(fkconstraint->pk_attrs, i)),
+						   format_type_be(fktype),
+						   format_type_be(pktype))));
+
+	if (*old_check_ok)
+	{
+		/*
+		 * When a pfeqop changes, revalidate the constraint.  We could
+		 * permit intra-opfamily changes, but that adds subtle complexity
+		 * without any concrete benefit for core types.  We need not
+		 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
+		 */
+		*old_check_ok = (pfeqop == lfirst_oid(*old_pfeqop_item));
+		*old_pfeqop_item = lnext(*old_pfeqop_item);
+	}
+	if (*old_check_ok)
+	{
+		Oid			old_fktype;
+		Oid			new_fktype;
+		CoercionPathType old_pathtype;
+		CoercionPathType new_pathtype;
+		Oid			old_castfunc;
+		Oid			new_castfunc;
+		Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+											   fkattnum[i] - 1);
+
+		/*
+		 * Identify coercion pathways from each of the old and new FK-side
+		 * column types to the right (foreign) operand type of the pfeqop.
+		 * We may assume that pg_constraint.conkey is not changing.
+		 */
+		old_fktype = attr->atttypid;
+		new_fktype = fktype;
+		old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
+									&old_castfunc);
+		new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
+									&new_castfunc);
+
+		/*
+		 * Upon a change to the cast from the FK column to its pfeqop
+		 * operand, revalidate the constraint.  For this evaluation, a
+		 * binary coercion cast is equivalent to no cast at all.  While
+		 * type implementors should design implicit casts with an eye
+		 * toward consistency of operations like equality, we cannot
+		 * assume here that they have done so.
+		 *
+		 * A function with a polymorphic argument could change behavior
+		 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
+		 * when the cast destination is polymorphic, we only avoid
+		 * revalidation if the input type has not changed at all.  Given
+		 * just the core data types and operator classes, this requirement
+		 * prevents no would-be optimizations.
+		 *
+		 * If the cast converts from a base type to a domain thereon, then
+		 * that domain type must be the opcintype of the unique index.
+		 * Necessarily, the primary key column must then be of the domain
+		 * type.  Since the constraint was previously valid, all values on
+		 * the foreign side necessarily exist on the primary side and in
+		 * turn conform to the domain.  Consequently, we need not treat
+		 * domains specially here.
+		 *
+		 * Since we require that all collations share the same notion of
+		 * equality (which they do, because texteq reduces to bitwise
+		 * equality), we don't compare collation here.
+		 *
+		 * We need not directly consider the PK type.  It's necessarily
+		 * binary coercible to the opcintype of the unique index column,
+		 * and ri_triggers.c will only deal with PK datums in terms of
+		 * that opcintype.  Changing the opcintype also changes pfeqop.
+		 */
+		*old_check_ok = (new_pathtype == old_pathtype &&
+						new_castfunc == old_castfunc &&
+						(!IsPolymorphicType(pfeqop_right) ||
+						 new_fktype == old_fktype));
+
+	}
+
+	*pfeqopOut = pfeqop;
+	*ppeqopOut = ppeqop;
+	*ffeqopOut = ffeqop;
+}
+
 /*
  * When the parent of a partition receives [the referencing side of] a foreign
  * key, we must propagate that foreign key to the partition.  However, the
@@ -9146,7 +9234,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 con->oid);
+										 con->oid, con->contemporal);
 			table_close(refrel, NoLock);
 
 			/*
@@ -9279,10 +9367,12 @@ transformColumnNameList(Oid relId, List *colList,
  *
  *	Look up the names, attnums, and types of the primary key attributes
  *	for the pkrel.  Also return the index OID and index opclasses of the
- *	index supporting the primary key.
+ *	index supporting the primary key.  If this is a temporal primary key,
+ *	also set the WITHOUT OVERLAPS attribute name, attnum, and atttypid.
  *
  *	All parameters except pkrel are output parameters.  Also, the function
- *	return value is the number of attributes in the primary key.
+ *	return value is the number of attributes in the primary key,
+ *	not including the WITHOUT OVERLAPS if any.
  *
  *	Used when the column list in the REFERENCES specification is omitted.
  */
@@ -9290,6 +9380,8 @@ static int
 transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses)
 {
 	List	   *indexoidlist;
@@ -9357,35 +9449,50 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 	/*
 	 * Now build the list of PK attributes from the indkey definition (we
 	 * assume a primary key cannot have expressional elements)
+	 * TODO: range expressions will be how we support PERIODs though.
 	 */
 	*attnamelist = NIL;
 	for (i = 0; i < indexStruct->indnkeyatts; i++)
 	{
 		int			pkattno = indexStruct->indkey.values[i];
 
-		attnums[i] = pkattno;
-		atttypids[i] = attnumTypeId(pkrel, pkattno);
-		opclasses[i] = indclass->values[i];
-		*attnamelist = lappend(*attnamelist,
-							   makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		if (i == indexStruct->indnkeyatts - 1 && indexStruct->indisexclusion)
+		{
+			periodattnums[i] = pkattno;
+			periodatttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*periodattname = (Node *)makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))));
+		}
+		else
+		{
+			attnums[i] = pkattno;
+			atttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*attnamelist = lappend(*attnamelist,
+								  makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		}
 	}
 
 	ReleaseSysCache(indexTuple);
 
-	return i;
+	if (indexStruct->indisexclusion) return i - 1;
+	else return i;
 }
 
 /*
  * transformFkeyCheckAttrs -
  *
  *	Make sure that the attributes of a referenced table belong to a unique
- *	(or primary key) constraint.  Return the OID of the index supporting
- *	the constraint, as well as the opclasses associated with the index
+ *	(or primary key) constraint.  Or if this is a temporal foreign key
+ *	the primary key should be an exclusion constraint instead.
+ *	Return the OID of the index supporting the constraint,
+ *	as well as the opclasses associated with the index
  *	columns.
  */
 static Oid
 transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
 						Oid *opclasses) /* output parameter */
 {
 	Oid			indexoid = InvalidOid;
@@ -9412,6 +9519,10 @@ transformFkeyCheckAttrs(Relation pkrel,
 						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 						 errmsg("foreign key referenced-columns list must not contain duplicates")));
 		}
+		if (is_temporal && attnums[i] == periodattnums[0])
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+					 errmsg("foreign key referenced-columns list must not contain duplicates")));
 	}
 
 	/*
@@ -9433,12 +9544,16 @@ transformFkeyCheckAttrs(Relation pkrel,
 		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		/*
-		 * Must have the right number of columns; must be unique and not a
+		 * Must have the right number of columns; must be unique
+		 * (or if temporal then exclusion instead) and not a
 		 * partial index; forget it if there are any expressions, too. Invalid
 		 * indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
-			indexStruct->indisunique &&
+		if ((is_temporal
+			  ? (indexStruct->indnkeyatts == numattrs + 1 &&
+				 indexStruct->indisexclusion)
+			  : (indexStruct->indnkeyatts == numattrs &&
+				 indexStruct->indisunique)) &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
 			heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
@@ -9478,6 +9593,19 @@ transformFkeyCheckAttrs(Relation pkrel,
 				if (!found)
 					break;
 			}
+			if (is_temporal)
+			{
+				found = false;
+				for (j = 0; j < numattrs + 1; j++)
+				{
+					if (periodattnums[0] == indexStruct->indkey.values[j])
+					{
+						opclasses[numattrs] = indclass->values[j];
+						found = true;
+						break;
+					}
+				}
+			}
 
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
@@ -9667,7 +9795,8 @@ validateForeignKeyConstraint(char *conname,
 							 Relation rel,
 							 Relation pkrel,
 							 Oid pkindOid,
-							 Oid constraintOid)
+							 Oid constraintOid,
+							 bool temporal)
 {
 	TupleTableSlot *slot;
 	TableScanDesc scan;
@@ -9697,8 +9826,10 @@ validateForeignKeyConstraint(char *conname,
 	/*
 	 * See if we can do it with a single LEFT JOIN query.  A false result
 	 * indicates we must proceed with the fire-the-trigger method.
+	 * We can't do a LEFT JOIN for temporal FKs yet,
+	 * but we can once we support temporal left joins.
 	 */
-	if (RI_Initial_Check(&trig, rel, pkrel))
+	if (!temporal && RI_Initial_Check(&trig, rel, pkrel))
 		return;
 
 	/*
@@ -9760,6 +9891,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 					 Oid constraintOid, Oid indexOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
+	bool is_temporal = fkconstraint->fk_period;
 
 	/*
 	 * Note: for a self-referential FK (referencing and referenced tables are
@@ -9771,7 +9903,10 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = "RI_ConstraintTrigger_c";
+	if (is_temporal)
+		fk_trigger->trigname = "TRI_ConstraintTrigger_c";
+	else
+		fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
 	fk_trigger->timing = TRIGGER_TYPE_AFTER;
@@ -9779,12 +9914,18 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	/* Either ON INSERT or ON UPDATE */
 	if (on_insert)
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_ins");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
 		fk_trigger->events = TRIGGER_TYPE_INSERT;
 	}
 	else
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_upd");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
 		fk_trigger->events = TRIGGER_TYPE_UPDATE;
 	}
 
@@ -9830,37 +9971,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_del_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_del");
+				break;
+				/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_del");
+				break;
+				*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
@@ -9886,37 +10068,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_upd_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_upd");
+				break;
+			/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_upd");
+				break;
+			*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 316692b7c2..897cdafdb8 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -776,6 +776,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* isnoinherit */
+											  false, /* contemporal */
 											  isInternal);	/* is_internal */
 	}
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index e9c8873ade..ce793e9f8a 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3174,6 +3174,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
+							  false,	/* contemporal */
 							  false);	/* is_internal */
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78090fbbf8..314e56f4fb 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2921,7 +2921,9 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(where_clause);
 	COPY_NODE_FIELD(pktable);
 	COPY_NODE_FIELD(fk_attrs);
+	COPY_NODE_FIELD(fk_period);
 	COPY_NODE_FIELD(pk_attrs);
+	COPY_NODE_FIELD(pk_period);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
 	COPY_SCALAR_FIELD(fk_del_action);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 1898521e98..09a97176a3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -473,10 +473,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem withoutOverlapsClause where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause optionalPeriodName
+				where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
+%type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
@@ -667,7 +669,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
 
@@ -3731,19 +3733,21 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
-				opt_column_list key_match key_actions ConstraintAttributeSpec
+			| FOREIGN KEY '(' columnList optionalPeriodName ')' REFERENCES qualified_name
+				opt_column_and_period_list key_match key_actions ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_FOREIGN;
 					n->location = @1;
-					n->pktable			= $7;
+					n->pktable			= $8;
 					n->fk_attrs			= $4;
-					n->pk_attrs			= $8;
-					n->fk_matchtype		= $9;
-					n->fk_upd_action	= (char) ($10 >> 8);
-					n->fk_del_action	= (char) ($10 & 0xFF);
-					processCASbits($11, @11, "FOREIGN KEY",
+					n->fk_period		= $5;
+					n->pk_attrs			= linitial($9);
+					n->pk_period		= lsecond($9);
+					n->fk_matchtype		= $10;
+					n->fk_upd_action	= (char) ($11 >> 8);
+					n->fk_del_action	= (char) ($11 & 0xFF);
+					processCASbits($12, @12, "FOREIGN KEY",
 								   &n->deferrable, &n->initdeferred,
 								   &n->skip_validation, NULL,
 								   yyscanner);
@@ -3771,6 +3775,16 @@ withoutOverlapsClause:
 			| /*EMPTY*/               { $$ = NULL; }
 	;
 
+optionalPeriodName:
+			',' PERIOD columnElem { $$ = $3; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+opt_column_and_period_list:
+			'(' columnList optionalPeriodName ')'			{ $$ = list_make2($2, $3); }
+			| /*EMPTY*/								{ $$ = list_make2(NIL, NULL); }
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
@@ -15481,6 +15495,7 @@ reserved_keyword:
 			| ONLY
 			| OR
 			| ORDER
+			| PERIOD
 			| PLACING
 			| PRIMARY
 			| REFERENCES
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 44a6eef5bb..443e605617 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -109,6 +109,7 @@ typedef struct RI_ConstraintInfo
 	char		confupdtype;	/* foreign key's ON UPDATE action */
 	char		confdeltype;	/* foreign key's ON DELETE action */
 	char		confmatchtype;	/* foreign key's match type */
+	bool		temporal;		/* if the foreign key is temporal */
 	int			nkeys;			/* number of key columns */
 	int16		pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
 	int16		fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
@@ -192,7 +193,7 @@ static int	ri_NullCheck(TupleDesc tupdesc, TupleTableSlot *slot,
 static void ri_BuildQueryKey(RI_QueryKey *key,
 							 const RI_ConstraintInfo *riinfo,
 							 int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+static bool ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
 							   Datum oldvalue, Datum newvalue);
@@ -353,18 +354,46 @@ RI_FKey_check(TriggerData *trigdata)
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
-		 *		   FOR KEY SHARE OF x
+		 *	SELECT 1
+		 *	FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *	FOR KEY SHARE OF x
 		 * The type id's for the $ parameters are those of the
 		 * corresponding FK attributes.
+		 *
+		 * But for temporal FKs we need to make sure
+		 * the FK's range is completely covered.
+		 * So we use this query instead:
+		 *  SELECT 1
+		 *  FROM (
+		 *	    SELECT	range_agg(r, true, true) AS r
+		 *	    FROM	(
+		 *			SELECT pkperiodatt AS r
+		 *			FROM   [ONLY] pktable x
+		 *			WHERE  pkatt1 = $1 [AND ...]
+		 *			FOR KEY SHARE OF x
+		 *		) x1
+		 *  ) x2
+		 *  WHERE $n <@ x2.r[1]
+		 * Note if FOR KEY SHARE ever allows aggregate functions
+		 * we can make this a bit simpler.
 		 * ----------
 		 */
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
 		quoteRelationName(pkrelname, pk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 pk_only, pkrelname);
+		if (riinfo->temporal)
+		{
+			quoteOneName(attname,
+					RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			appendStringInfo(&querybuf,
+					"SELECT 1 FROM (SELECT range_agg(r, true, true) AS r FROM (SELECT %s AS r FROM %s%s x",
+					attname, pk_only, pkrelname);
+		}
+		else {
+			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
+							 pk_only, pkrelname);
+		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -382,6 +411,8 @@ RI_FKey_check(TriggerData *trigdata)
 			queryoids[i] = fk_type;
 		}
 		appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
+		if (riinfo->temporal)
+			appendStringInfo(&querybuf, ") x1) x2 WHERE $%d <@ x2.r[1]", riinfo->nkeys);
 
 		/* Prepare and save the plan */
 		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -1176,7 +1207,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
 		return false;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
+	if (newslot && ri_KeysStable(pk_rel, oldslot, newslot, riinfo, true))
 		return false;
 
 	/* Else we need to fire the trigger. */
@@ -1269,13 +1300,135 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 		return true;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
+	if (ri_KeysStable(fk_rel, oldslot, newslot, riinfo, false))
 		return false;
 
 	/* Else we need to fire the trigger. */
 	return true;
 }
 
+/* ----------
+ * TRI_FKey_check_ins -
+ *
+ *	Check temporal foreign key existence at insert event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_ins(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+	/*
+	 * Share code with UPDATE case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_check_upd -
+ *
+ *	Check temporal foreign key existence at update event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_upd(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+	/*
+	 * Share code with INSERT case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_noaction_del -
+ *
+ *	Give an error and roll back the current transaction if the
+ *	delete has resulted in a violation of the given temporal
+ *	referential integrity constraint.
+ * ----------
+ */
+Datum
+TRI_FKey_noaction_del(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
+
+	/*
+	 * Share code with RESTRICT/UPDATE cases.
+	 */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_del -
+ *
+ * Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the delete is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with NO ACTION/UPDATE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+/*
+ * TRI_FKey_noaction_upd -
+ *
+ * Give an error and roll back the current transaction if the
+ * update has resulted in a violation of the given referential
+ * integrity constraint.
+ */
+Datum
+TRI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with RESTRICT/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_upd -
+ *
+ * Restrict update of PK to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the update is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with NO ACTION/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+
 /*
  * RI_Initial_Check -
  *
@@ -2051,6 +2204,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo->confupdtype = conForm->confupdtype;
 	riinfo->confdeltype = conForm->confdeltype;
 	riinfo->confmatchtype = conForm->confmatchtype;
+	riinfo->temporal = conForm->contemporal;
 
 	DeconstructFkConstraintRow(tup,
 							   &riinfo->nkeys,
@@ -2649,9 +2803,12 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 
 
 /*
- * ri_KeysEqual -
+ * ri_KeysStable -
  *
- * Check if all key values in OLD and NEW are equal.
+ * Check if all key values in OLD and NEW are "equivalent":
+ * For normal FKs we check for equality.
+ * For temporal FKs we check that the PK side is a superset of its old value,
+ * or the FK side is a subset.
  *
  * Note: at some point we might wish to redefine this as checking for
  * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
@@ -2659,7 +2816,7 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
  * previously found at least one of the rows to contain no nulls.
  */
 static bool
-ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -2692,29 +2849,43 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 
 		if (rel_is_pk)
 		{
-			/*
-			 * If we are looking at the PK table, then do a bytewise
-			 * comparison.  We must propagate PK changes if the value is
-			 * changed to one that "looks" different but would compare as
-			 * equal using the equality operator.  This only makes a
-			 * difference for ON UPDATE CASCADE, but for consistency we treat
-			 * all changes to the PK the same.
-			 */
-			Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, newvalue, oldvalue));
+			}
+			else
+			{
+				/*
+				 * If we are looking at the PK table, then do a bytewise
+				 * comparison.  We must propagate PK changes if the value is
+				 * changed to one that "looks" different but would compare as
+				 * equal using the equality operator.  This only makes a
+				 * difference for ON UPDATE CASCADE, but for consistency we treat
+				 * all changes to the PK the same.
+				 */
+				Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
 
-			if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
-				return false;
+				if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
+					return false;
+			}
 		}
 		else
 		{
-			/*
-			 * For the FK table, compare with the appropriate equality
-			 * operator.  Changes that compare equal will still satisfy the
-			 * constraint after the update.
-			 */
-			if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
-									oldvalue, newvalue))
-				return false;
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, oldvalue, newvalue));
+			}
+			else
+			{
+				/*
+				 * For the FK table, compare with the appropriate equality
+				 * operator.  Changes that compare equal will still satisfy the
+				 * constraint after the update.
+				 */
+				if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
+										oldvalue, newvalue))
+					return false;
+			}
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9bab433f6b..2507ef4da7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -317,7 +317,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 								   int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int	decompile_column_index_array(Datum column_index_array, Oid relId,
-										 bool withoutOverlaps, StringInfo buf);
+										 bool withoutOverlaps, bool withPeriod, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									const Oid *excludeOps,
@@ -1985,6 +1985,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			{
 				Datum		val;
 				bool		isnull;
+				bool		hasperiod;
 				const char *string;
 
 				/* Start off the constraint definition */
@@ -1997,7 +1998,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, false, &buf);
+				/*
+				 * If it is a temporal foreign key
+				 * then it uses PERIOD.
+				 */
+				hasperiod = DatumGetBool(SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_contemporal, &isnull));
+				decompile_column_index_array(val, conForm->conrelid, false, hasperiod, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2011,7 +2018,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, false, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, hasperiod, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2118,7 +2125,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 				 */
 				SysCacheGetAttr(CONSTROID, tup,
 						  Anum_pg_constraint_conexclop, &isnull);
-				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, &buf);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2320,7 +2327,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 bool withoutOverlaps, StringInfo buf)
+							 bool withoutOverlaps, bool withPeriod, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2345,6 +2352,10 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		{
 			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
 		}
+		else if (withPeriod && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", PERIOD %s", quote_identifier(colName));
+		}
 		else
 		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 5d872befd1..0baa22c0e5 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -103,6 +103,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	/* Has a local definition and cannot be inherited */
 	bool		connoinherit;
 
+	/*
+	 * For primary and foreign keys, signifies the last column is a range 
+	 * and should use overlaps instead of equals.
+	 */
+	bool		contemporal;
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 
 	/*
@@ -117,19 +123,19 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	int16		confkey[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * If a foreign key, the OIDs of the PK = FK comparison operators for each
 	 * column of the constraint
 	 */
 	Oid			conpfeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * If a foreign key, the OIDs of the PK = PK comparison operators for each
 	 * column of the constraint (i.e., equality for the referenced columns)
 	 */
 	Oid			conppeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * If a foreign key, the OIDs of the FK = FK comparison operators for each
 	 * column of the constraint (i.e., equality for the referencing columns)
 	 */
 	Oid			conffeqop[1];
@@ -211,6 +217,7 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conIsLocal,
 								  int conInhCount,
 								  bool conNoInherit,
+								  bool conTemporal,
 								  bool is_internal);
 
 extern void RemoveConstraintById(Oid conId);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 249220c8a2..16abc3722f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3678,6 +3678,50 @@
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'RI_FKey_noaction_upd' },
 
+# Temporal referential integrity constraint triggers
+{ oid => '6122', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_ins' },
+{ oid => '6123', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_upd' },
+# { oid => '6124', descr => 'temporal referential integrity ON DELETE CASCADE',
+#   proname => 'TRI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_del' },
+# { oid => '6125', descr => 'temporal referential integrity ON UPDATE CASCADE',
+#   proname => 'TRI_FKey_cascade_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_upd' },
+{ oid => '6126', descr => 'temporal referential integrity ON DELETE RESTRICT',
+  proname => 'TRI_FKey_restrict_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_del' },
+{ oid => '6127', descr => 'temporal referential integrity ON UPDATE RESTRICT',
+  proname => 'TRI_FKey_restrict_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_upd' },
+# { oid => '6128', descr => 'temporal referential integrity ON DELETE SET NULL',
+#   proname => 'TRI_FKey_setnull_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_del' },
+# { oid => '6129', descr => 'temporal referential integrity ON UPDATE SET NULL',
+#   proname => 'TRI_FKey_setnull_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_upd' },
+# { oid => '6130', descr => 'temporal referential integrity ON DELETE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_del', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_del' },
+# { oid => '6131', descr => 'temporal referential integrity ON UPDATE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_upd', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_upd' },
+{ oid => '6132', descr => 'temporal referential integrity ON DELETE NO ACTION',
+  proname => 'TRI_FKey_noaction_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_del' },
+{ oid => '6133', descr => 'temporal referential integrity ON UPDATE NO ACTION',
+  proname => 'TRI_FKey_noaction_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_upd' },
+
 { oid => '1666',
   proname => 'varbiteq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'varbit varbit', prosrc => 'biteq' },
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 3bb21c2589..5eb49738c8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2162,7 +2162,9 @@ typedef struct Constraint
 	/* Fields used for FOREIGN KEY constraints: */
 	RangeVar   *pktable;		/* Primary key table */
 	List	   *fk_attrs;		/* Attributes of foreign key */
+	Node	   *fk_period;		/* String node naming Period or range column */
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
+	Node	   *pk_period;		/* String node naming Period or range column */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
 	char		fk_del_action;	/* ON DELETE action */
@@ -2171,7 +2173,7 @@ typedef struct Constraint
 									 * self */
 
   /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
-  Node    *without_overlaps; /* String node naming PERIOD or range column */
+	Node	   *without_overlaps; /* String node naming PERIOD or range column */
 
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..92628f390a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -299,6 +299,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, RESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 8cc6b9fc76..929fd9db40 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -170,6 +170,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+referencing_period_test|t
 reservations|f
 road|t
 shighway|t
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
index 53475c1e7e..a3c6f1ff45 100644
--- a/src/test/regress/expected/without_overlaps.out
+++ b/src/test/regress/expected/without_overlaps.out
@@ -53,6 +53,26 @@ CREATE TABLE without_overlaps_test2 (
 ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
 DROP TABLE without_overlaps_test2;
 DROP TYPE textrange2;
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
 DROP TABLE without_overlaps_test;
 CREATE TABLE without_overlaps_test (
 	id int4range,
@@ -102,3 +122,242 @@ ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING ts
 ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
 ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
 DROP TABLE without_overlaps_test2;
+--
+-- test FK parser
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+                                      pg_get_constraintdef                                      
+------------------------------------------------------------------------------------------------
+ FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
+(1 row)
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Tue May 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([8,9), ["Tue Jan 02 00:00:00 2018","Thu Mar 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+--
+-- test FK parent updates SET NULL
+--
+--
+-- test FK parent updates SET DEFAULT
+--
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+--
+-- test FK parent deletes SET NULL
+--
+--
+-- test FK parent deletes SET DEFAULT
+--
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
index 7084c68904..83824a55d3 100644
--- a/src/test/regress/sql/without_overlaps.sql
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -67,6 +67,25 @@ ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
 DROP TABLE without_overlaps_test2;
 DROP TYPE textrange2;
 
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
 DROP TABLE without_overlaps_test;
 CREATE TABLE without_overlaps_test (
 	id int4range,
@@ -112,3 +131,239 @@ ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING ts
 ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
 ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
 DROP TABLE without_overlaps_test2;
+
+--
+-- test FK parser
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+-- TODO
+--
+-- test FK parent updates SET NULL
+--
+-- TODO
+--
+-- test FK parent updates SET DEFAULT
+--
+-- TODO
+
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+-- TODO
+--
+-- test FK parent deletes SET NULL
+--
+-- TODO
+--
+-- test FK parent deletes SET DEFAULT
+--
+-- TODO
temporal_pks_v0004.patchapplication/octet-stream; name=temporal_pks_v0004.patchDownload
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..78090fbbf8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2927,6 +2927,7 @@ _copyConstraint(const Constraint *from)
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8311b1dd46..1898521e98 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -473,7 +473,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
@@ -3462,6 +3462,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3678,18 +3679,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3700,6 +3702,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3763,6 +3766,11 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 7450d74b7a..a0add088ad 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1962,7 +1962,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2351,6 +2351,151 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				notnullcmd->name = pstrdup(key);
 				notnullcmds = lappend(notnullcmds, notnullcmd);
 			}
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					// ereport(NOTICE, (errmsg("range %s vs column %s of type %d", without_overlaps_str, column->colname, column->typeName->typeOid)));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				iparam->name = without_overlaps_str;	// TODO: pstrdup here?
+				iparam->expr = NULL;
+
+				/*
+				 * Force the column to NOT NULL since it is part of the primary key.
+				 */
+				AlterTableCmd *notnullcmd = makeNode(AlterTableCmd);
+
+				notnullcmd->subtype = AT_SetNotNull;
+				notnullcmd->name = pstrdup(without_overlaps_str);
+				notnullcmds = lappend(notnullcmds, notnullcmd);
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+
+			List *opname = list_make1(makeString("&&"));
+			index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			index->accessMethod = "gist";
+			constraint->access_method = "gist";
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b9fdd99db8..9bab433f6b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -317,7 +317,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 								   int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int	decompile_column_index_array(Datum column_index_array, Oid relId,
-										 StringInfo buf);
+										 bool withoutOverlaps, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									const Oid *excludeOps,
@@ -1997,7 +1997,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				decompile_column_index_array(val, conForm->conrelid, false, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2011,7 +2011,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2112,7 +2112,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2314,7 +2320,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2332,9 +2338,17 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 2b992d7832..98807c6cd5 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4977,8 +4977,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5044,7 +5045,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8909a45d61..714ac9ff64 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6838,7 +6838,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6899,7 +6900,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -6938,7 +6940,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6973,7 +6976,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7004,7 +7008,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7038,7 +7043,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7078,6 +7084,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7137,6 +7144,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16585,9 +16593,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index c3c2ea1473..2e7a97f2ba 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -449,6 +449,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+	bool		withoutoverlaps;	/* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index c56bf00e4b..942a1cc794 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -632,6 +632,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 97167d2c4b..dbabe9e9a2 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2352,6 +2352,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c1e60c7dfd..5d872befd1 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -136,7 +136,7 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..3bb21c2589 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2170,6 +2170,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+  Node    *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 395bfeb75d..8cc6b9fc76 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -211,6 +211,7 @@ timetz_tbl|f
 tmp|f
 varchar_tbl|f
 view_base_table|t
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
new file mode 100644
index 0000000000..53475c1e7e
--- /dev/null
+++ b/src/test/regress/expected/without_overlaps.out
@@ -0,0 +1,104 @@
+-- Tests for WITHOUT OVERLAPS.
+--
+-- test input parser
+--
+-- PK with no columns just WITHOUT OVERLAPS:
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+ERROR:  syntax error at or near "WITHOUT"
+LINE 3: ...STRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OV...
+                                                             ^
+-- PK with a range column that isn't there:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  range or PERIOD "valid_at" named in WITHOUT OVERLAPS does not exist
+-- PK with a PERIOD that isn't there:
+-- PK with a non-range column:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  column "valid_at" named in WITHOUT OVERLAPS is not a range type
+-- PK with one column plus a range:
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+-- PK with one column plus a PERIOD:
+-- PK with two columns plus a PERIOD:
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
+--
+-- test PK inserts
+--
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+ERROR:  conflicting key value violates exclusion constraint "without_overlaps_pk"
+DETAIL:  Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+ERROR:  null value in column "id" violates not-null constraint
+DETAIL:  Failing row contains (null, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+ERROR:  null value in column "valid_at" violates not-null constraint
+DETAIL:  Failing row contains ([3,4), null).
+--
+-- test changing the PK's dependencies
+--
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ERROR:  column "valid_at" is in a primary key
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index f23fe8d870..a6aad7f85f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info without_overlaps
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index ca200eb599..6d0eee26f5 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -19,6 +19,7 @@ test: uuid
 test: enum
 test: money
 test: rangetypes
+test: without_overlaps
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..7084c68904
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,114 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
#15Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Paul A Jungwirth (#14)
Re: SQL:2011 PERIODS vs Postgres Ranges?

The patch requires to rebase on the master branch.

The new status of this patch is: Waiting on Author

#16Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Ibrar Ahmed (#15)
1 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Hi Paul,

I have rebased the patch to master (1e2fddfa33d3c7cc93ca3ee0f32852699bd3e012)
and fixed some compilation warning. Now I am reviewing the actual code.

On Fri, Jul 26, 2019 at 6:35 PM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

The patch requires to rebase on the master branch.

The new status of this patch is: Waiting on Author

--
Ibrar Ahmed

Attachments:

temporal_pks_fks_v005.patchapplication/octet-stream; name=temporal_pks_fks_v005.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 9009addb9c..dfbc65d327 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -78,9 +78,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
   UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
-  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
+  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_inverval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
-  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
 class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 <phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">temporal_interval</replaceable> in a <literal>PRIMARY KEY</literal> or <literal>FOREIGN KEY</literal> constraint is:</phrase>
+
+<replaceable class="parameter">range_column_name</replaceable>
+
 </synopsis>
 
  </refsynopsisdiv>
@@ -909,7 +914,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term><literal>PRIMARY KEY</literal> (column constraint)</term>
-    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ]
+    [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] )</literal>
     <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
     <listitem>
      <para>
@@ -942,7 +948,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
      <para>
       Adding a <literal>PRIMARY KEY</literal> constraint will automatically
-      create a unique btree index on the column or group of columns used in the
+      create a unique btree (or GiST if temporal) index on the column or group of columns used in the
       constraint.  The optional <literal>INCLUDE</literal> clause allows a list
       of columns to be specified which will be included in the non-key portion
       of the index.  Although uniqueness is not enforced on the included columns,
@@ -950,6 +956,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       included columns (e.g. <literal>DROP COLUMN</literal>) can cause cascaded
       constraint and index deletion.
      </para>
+
+     <para>
+      A <literal>PRIMARY KEY</literal> with a <literal>WITHOUT OVERLAPS</literal> option
+      is a <emphasis>temporal</emphasis> primary key.
+      The <literal>WITHOUT OVERLAPS</literal> column
+      must be a range type and is used to constrain the record's applicability
+      to just that range (usually a range of dates or timestamps).
+      The main part of the primary key may be repeated elsewhere in the table,
+      as long as records with the same key don't overlap in the
+      <literal>WITHOUT OVERLAPS</literal> column.
+     </para>
+
+     <para>
+      A temporal <literal>PRIMARY KEY</literal> is enforced with an
+      <literal>EXCLUDE</literal> constraint rather than a <literal>UNIQUE</literal>
+      constraint, backed by a GiST index. You may need to install the
+      <xref linkend="btree-gist"/> extension to create temporal primary keys.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1006,8 +1030,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    <varlistentry>
     <term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal> (column constraint)</term>
 
-   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
-    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] )
+    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH <replaceable class="parameter">matchtype</replaceable> ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ]
     [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal>
@@ -1018,11 +1042,29 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       These clauses specify a foreign key constraint, which requires
       that a group of one or more columns of the new table must only
       contain values that match values in the referenced
-      column(s) of some row of the referenced table.  If the <replaceable
+      column(s) of some row of the referenced table.
+      If the <replaceable
       class="parameter">refcolumn</replaceable> list is omitted, the
       primary key of the <replaceable class="parameter">reftable</replaceable>
       is used.  The referenced columns must be the columns of a non-deferrable
-      unique or primary key constraint in the referenced table.  The user
+      unique or primary key constraint in the referenced table.
+     </para>
+
+     <para>
+      If the last column is marked with <literal>PERIOD</literal>,
+      it must be a range column, and the referenced table
+      must have a temporal primary key.
+      The non-<literal>PERIOD</literal> columns are treated normally
+      (and there must be at least one of them),
+      but the <literal>PERIOD</literal> column is not compared for equality.
+      Instead the constraint is considered satisfied
+      if the referenced table has matching records whose combined ranges completely cover
+      the referencing record.
+      In other words, the reference must have a referent for its entire duration.
+     </para>
+
+     <para>
+      The user
       must have <literal>REFERENCES</literal> permission on the referenced table
       (either the whole table, or the specific referenced columns).  The
       addition of a foreign key constraint requires a
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index 368b1dea3e..f6cfbb68f6 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -237,6 +237,7 @@ sub ParseData
 	# Scan the input file.
 	while (<$ifd>)
 	{
+    next if /^#/;
 		my $hash_ref;
 
 		if (/{/)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9d0f..c1346d765c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2399,6 +2399,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
+							  false,	/* contemporal */
 							  is_internal); /* internally constructed? */
 
 	pfree(ccbin);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 99ae159f98..41645ec692 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1832,6 +1832,7 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
+								   false,	/* contemporal */
 								   is_internal);
 
 	/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b6145593a3..e913741103 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -76,6 +76,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
+					  bool conTemporal,
 					  bool is_internal)
 {
 	Relation	conDesc;
@@ -184,6 +185,7 @@ CreateConstraintEntry(const char *constraintName,
 	values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
 	values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
 	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+	values[Anum_pg_constraint_contemporal - 1] = BoolGetDatum(conTemporal);
 
 	if (conkeyArray)
 		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fb2be10794..0ca8e2004c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -328,9 +328,12 @@ static int	transformColumnNameList(Oid relId, List *colList,
 static int	transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 									   List **attnamelist,
 									   int16 *attnums, Oid *atttypids,
+										 Node **periodattname,
+										 int16 *periodattnums, Oid *periodatttypids,
 									   Oid *opclasses);
 static Oid	transformFkeyCheckAttrs(Relation pkrel,
 									int numattrs, int16 *attnums,
+									bool is_temporal, int16 *periodattnums,
 									Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
@@ -338,7 +341,7 @@ static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
 static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
 static void validateForeignKeyConstraint(char *conname,
 										 Relation rel, Relation pkrel,
-										 Oid pkindOid, Oid constraintOid);
+										 Oid pkindOid, Oid constraintOid, bool temporal);
 static void ATController(AlterTableStmt *parsetree,
 						 Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -424,12 +427,12 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 											Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 											int numfks, int16 *pkattnum, int16 *fkattnum,
 											Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-											bool old_check_ok);
+											bool old_check_ok, bool is_temporal);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
 									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 									int numfks, int16 *pkattnum, int16 *fkattnum,
 									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-									bool old_check_ok, LOCKMODE lockmode);
+									bool old_check_ok, bool is_temporal, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 									   Relation partitionRel);
 static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
@@ -446,6 +449,12 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
 										 Oid parentConstrOid, int numfks,
 										 AttrNumber *mapped_conkey, AttrNumber *confkey,
 										 Oid *conpfeqop);
+static void FindFKComparisonOperators(Constraint *fkconstraint,
+					AlteredTableInfo *tab, int i, int16 *fkattnum,
+					bool *old_check_ok, ListCell **old_pfeqop_item,
+					Oid pktype, Oid fktype, Oid opclass,
+					bool is_temporal, bool for_overlaps,
+					Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 								 DropBehavior behavior,
 								 bool recurse, bool recursing,
@@ -4709,7 +4718,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 
 				validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
 											 con->refindid,
-											 con->conid);
+											 con->conid,
+											 fkconstraint->fk_period != NULL);
 
 				/*
 				 * No need to mark the constraint row as validated, we did
@@ -7604,6 +7614,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	bool		is_temporal = (fkconstraint->fk_period != NULL);
+	int16		pkperiodattnum[1];
+	int16		fkperiodattnum[1];
+	Oid			pkperiodtypoid[1];
+	Oid			fkperiodtypoid[1];
 	int			i;
 	int			numfks,
 				numpks;
@@ -7706,6 +7721,18 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
+	if (is_temporal)
+	{
+	  List *fk_period;	
+	  MemSet(pkperiodattnum, 0, sizeof(pkperiodattnum));
+	  MemSet(fkperiodattnum, 0, sizeof(fkperiodattnum));
+	  MemSet(pkperiodtypoid, 0, sizeof(pkperiodtypoid));
+	  MemSet(fkperiodtypoid, 0, sizeof(fkperiodtypoid));
+	  fk_period = list_make1(fkconstraint->fk_period);
+	  transformColumnNameList(RelationGetRelid(rel),
+							  fk_period,
+							  fkperiodattnum, fkperiodtypoid);
+	}
 
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
@@ -7718,6 +7745,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
 											&fkconstraint->pk_attrs,
 											pkattnum, pktypoid,
+											&fkconstraint->pk_period,
+											pkperiodattnum, pkperiodtypoid,
 											opclasses);
 	}
 	else
@@ -7725,8 +7754,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformColumnNameList(RelationGetRelid(pkrel),
 										 fkconstraint->pk_attrs,
 										 pkattnum, pktypoid);
+		if (is_temporal) {
+			List *pk_period = list_make1(fkconstraint->pk_period);
+			transformColumnNameList(RelationGetRelid(pkrel),
+									pk_period,
+									pkperiodattnum, pkperiodtypoid);
+		}
 		/* Look for an index matching the column list */
 		indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
+										   is_temporal, pkperiodattnum,
 										   opclasses);
 	}
 
@@ -7776,6 +7812,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
+	// TODO: Need a check that if one side has a PERIOD the other does too
+
 	/*
 	 * On the strength of a previous constraint, we might avoid scanning
 	 * tables to validate this one.  See below.
@@ -7785,188 +7823,29 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		Oid			pktype = pktypoid[i];
-		Oid			fktype = fktypoid[i];
-		Oid			fktyped;
-		HeapTuple	cla_ht;
-		Form_pg_opclass cla_tup;
-		Oid			amid;
-		Oid			opfamily;
-		Oid			opcintype;
-		Oid			pfeqop;
-		Oid			ppeqop;
-		Oid			ffeqop;
-		int16		eqstrategy;
-		Oid			pfeqop_right;
-
-		/* We need several fields out of the pg_opclass entry */
-		cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
-		if (!HeapTupleIsValid(cla_ht))
-			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
-		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
-		amid = cla_tup->opcmethod;
-		opfamily = cla_tup->opcfamily;
-		opcintype = cla_tup->opcintype;
-		ReleaseSysCache(cla_ht);
-
-		/*
-		 * Check it's a btree; currently this can never fail since no other
-		 * index AMs support unique indexes.  If we ever did have other types
-		 * of unique indexes, we'd need a way to determine which operator
-		 * strategy number is equality.  (Is it reasonable to insist that
-		 * every such index AM use btree's number for equality?)
-		 */
-		if (amid != BTREE_AM_OID)
-			elog(ERROR, "only b-tree indexes are supported for foreign keys");
-		eqstrategy = BTEqualStrategyNumber;
-
-		/*
-		 * There had better be a primary equality operator for the index.
-		 * We'll use it for PK = PK comparisons.
-		 */
-		ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
-									 eqstrategy);
-
-		if (!OidIsValid(ppeqop))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 eqstrategy, opcintype, opcintype, opfamily);
-
-		/*
-		 * Are there equality operators that take exactly the FK type? Assume
-		 * we should look through any domain here.
-		 */
-		fktyped = getBaseType(fktype);
-
-		pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
-									 eqstrategy);
-		if (OidIsValid(pfeqop))
-		{
-			pfeqop_right = fktyped;
-			ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
-										 eqstrategy);
-		}
-		else
-		{
-			/* keep compiler quiet */
-			pfeqop_right = InvalidOid;
-			ffeqop = InvalidOid;
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-		{
-			/*
-			 * Otherwise, look for an implicit cast from the FK type to the
-			 * opcintype, and if found, use the primary equality operator.
-			 * This is a bit tricky because opcintype might be a polymorphic
-			 * type such as ANYARRAY or ANYENUM; so what we have to test is
-			 * whether the two actual column types can be concurrently cast to
-			 * that type.  (Otherwise, we'd fail to reject combinations such
-			 * as int[] and point[].)
-			 */
-			Oid			input_typeids[2];
-			Oid			target_typeids[2];
-
-			input_typeids[0] = pktype;
-			input_typeids[1] = fktype;
-			target_typeids[0] = opcintype;
-			target_typeids[1] = opcintype;
-			if (can_coerce_type(2, input_typeids, target_typeids,
-								COERCION_IMPLICIT))
-			{
-				pfeqop = ffeqop = ppeqop;
-				pfeqop_right = opcintype;
-			}
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("foreign key constraint \"%s\" cannot be implemented",
-							fkconstraint->conname),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of incompatible types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktype),
-							   format_type_be(pktype))));
-
-		if (old_check_ok)
-		{
-			/*
-			 * When a pfeqop changes, revalidate the constraint.  We could
-			 * permit intra-opfamily changes, but that adds subtle complexity
-			 * without any concrete benefit for core types.  We need not
-			 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
-			 */
-			old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
-			old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
-									old_pfeqop_item);
-		}
-		if (old_check_ok)
-		{
-			Oid			old_fktype;
-			Oid			new_fktype;
-			CoercionPathType old_pathtype;
-			CoercionPathType new_pathtype;
-			Oid			old_castfunc;
-			Oid			new_castfunc;
-			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
-												   fkattnum[i] - 1);
-
-			/*
-			 * Identify coercion pathways from each of the old and new FK-side
-			 * column types to the right (foreign) operand type of the pfeqop.
-			 * We may assume that pg_constraint.conkey is not changing.
-			 */
-			old_fktype = attr->atttypid;
-			new_fktype = fktype;
-			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
-										&old_castfunc);
-			new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
-										&new_castfunc);
-
-			/*
-			 * Upon a change to the cast from the FK column to its pfeqop
-			 * operand, revalidate the constraint.  For this evaluation, a
-			 * binary coercion cast is equivalent to no cast at all.  While
-			 * type implementors should design implicit casts with an eye
-			 * toward consistency of operations like equality, we cannot
-			 * assume here that they have done so.
-			 *
-			 * A function with a polymorphic argument could change behavior
-			 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
-			 * when the cast destination is polymorphic, we only avoid
-			 * revalidation if the input type has not changed at all.  Given
-			 * just the core data types and operator classes, this requirement
-			 * prevents no would-be optimizations.
-			 *
-			 * If the cast converts from a base type to a domain thereon, then
-			 * that domain type must be the opcintype of the unique index.
-			 * Necessarily, the primary key column must then be of the domain
-			 * type.  Since the constraint was previously valid, all values on
-			 * the foreign side necessarily exist on the primary side and in
-			 * turn conform to the domain.  Consequently, we need not treat
-			 * domains specially here.
-			 *
-			 * Since we require that all collations share the same notion of
-			 * equality (which they do, because texteq reduces to bitwise
-			 * equality), we don't compare collation here.
-			 *
-			 * We need not directly consider the PK type.  It's necessarily
-			 * binary coercible to the opcintype of the unique index column,
-			 * and ri_triggers.c will only deal with PK datums in terms of
-			 * that opcintype.  Changing the opcintype also changes pfeqop.
-			 */
-			old_check_ok = (new_pathtype == old_pathtype &&
-							new_castfunc == old_castfunc &&
-							(!IsPolymorphicType(pfeqop_right) ||
-							 new_fktype == old_fktype));
-		}
-
-		pfeqoperators[i] = pfeqop;
-		ppeqoperators[i] = ppeqop;
-		ffeqoperators[i] = ffeqop;
+		FindFKComparisonOperators(
+				fkconstraint, tab, i, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pktypoid[i], fktypoid[i], opclasses[i],
+				is_temporal, false,
+				&pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]);
 	}
+	if (is_temporal)
+	{
+		pkattnum[numpks] = pkperiodattnum[0];
+		fkattnum[numpks] = fkperiodattnum[0];
+		pktypoid[numpks] = pkperiodtypoid[0];
+		fktypoid[numpks] = fkperiodtypoid[0];
+
+		FindFKComparisonOperators(
+				fkconstraint, tab, numpks, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pkperiodtypoid[0], fkperiodtypoid[0], opclasses[numpks],
+				is_temporal, true,
+				&pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]);
+		numfks += 1;
+		numpks += 1;
+ 	}
 
 	/*
 	 * Create all the constraint and trigger objects, recursing to partitions
@@ -7981,7 +7860,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
-									 old_check_ok);
+									 old_check_ok,
+									 is_temporal);
 
 	/* Now handle the referencing side. */
 	addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
@@ -7994,6 +7874,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							ppeqoperators,
 							ffeqoperators,
 							old_check_ok,
+							is_temporal,
 							lockmode);
 
 	/*
@@ -8034,7 +7915,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 					   Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks,
 					   int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok,
+					   bool is_temporal)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -8110,12 +7992,13 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  fkconstraint->fk_upd_action,
 									  fkconstraint->fk_del_action,
 									  fkconstraint->fk_matchtype,
-									  NULL, /* no exclusion constraint */
+									  NULL,
 									  NULL, /* no check constraint */
 									  NULL,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
+									  is_temporal,
 									  false);	/* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -8191,7 +8074,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
-								   old_check_ok);
+								   old_check_ok, is_temporal);
 
 			/* Done -- clean up (but keep the lock) */
 			table_close(partRel, NoLock);
@@ -8240,7 +8123,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 						Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode)
+						bool old_check_ok, bool is_temporal, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
 
@@ -8386,6 +8269,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
+									  is_temporal,
 									  false);
 
 			/*
@@ -8412,6 +8296,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									ppeqoperators,
 									ffeqoperators,
 									old_check_ok,
+									is_temporal,
 									lockmode);
 
 			table_close(partition, NoLock);
@@ -8544,6 +8429,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 								   conpfeqop,
 								   conppeqop,
 								   conffeqop);
+
 		for (int i = 0; i < numfks; i++)
 			mapped_confkey[i] = attmap[confkey[i] - 1];
 
@@ -8589,7 +8475,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
-							   true);
+							   true,
+							   constrForm->contemporal);
 
 		table_close(fkRel, NoLock);
 		ReleaseSysCache(tuple);
@@ -8783,6 +8670,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
+								  constrForm->contemporal,
 								  true);
 
 		/* Set up partition dependencies for the new constraint */
@@ -8812,11 +8700,214 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conppeqop,
 								conffeqop,
 								false,	/* no old check exists */
+								constrForm->contemporal,
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
 	}
 }
 
+static void
+FindFKComparisonOperators(Constraint *fkconstraint,
+		AlteredTableInfo *tab,
+		int i,
+		int16 *fkattnum,
+		bool *old_check_ok,
+		ListCell **old_pfeqop_item,
+		Oid pktype, Oid fktype, Oid opclass,
+		bool is_temporal, bool for_overlaps,
+		Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut)
+{
+	Oid			fktyped;
+	HeapTuple	cla_ht;
+	Form_pg_opclass cla_tup;
+	Oid			amid;
+	Oid			opfamily;
+	Oid			opcintype;
+	Oid			pfeqop;
+	Oid			ppeqop;
+	Oid			ffeqop;
+	int16		eqstrategy;
+	Oid			pfeqop_right;
+
+	/* We need several fields out of the pg_opclass entry */
+	cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(cla_ht))
+		elog(ERROR, "cache lookup failed for opclass %u", opclass);
+	cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+	amid = cla_tup->opcmethod;
+	opfamily = cla_tup->opcfamily;
+	opcintype = cla_tup->opcintype;
+	ReleaseSysCache(cla_ht);
+
+	if (is_temporal)
+	{
+		if (amid != GIST_AM_OID)
+			elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
+		eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
+	}
+	else
+	{
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other types
+		 * of unique indexes, we'd need a way to determine which operator
+		 * strategy number is equality.  (Is it reasonable to insist that
+		 * every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+	}
+
+	/*
+	 * There had better be a primary equality operator for the index.
+	 * We'll use it for PK = PK comparisons.
+	 */
+	ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
+								 eqstrategy);
+
+	if (!OidIsValid(ppeqop))
+		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+			 eqstrategy, opcintype, opcintype, opfamily);
+
+	/*
+	 * Are there equality operators that take exactly the FK type? Assume
+	 * we should look through any domain here.
+	 */
+	fktyped = getBaseType(fktype);
+
+	pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+								 eqstrategy);
+	if (OidIsValid(pfeqop))
+	{
+		pfeqop_right = fktyped;
+		ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+									 eqstrategy);
+	}
+	else
+	{
+		/* keep compiler quiet */
+		pfeqop_right = InvalidOid;
+		ffeqop = InvalidOid;
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		/*
+		 * Otherwise, look for an implicit cast from the FK type to the
+		 * opcintype, and if found, use the primary equality operator.
+		 * This is a bit tricky because opcintype might be a polymorphic
+		 * type such as ANYARRAY or ANYENUM; so what we have to test is
+		 * whether the two actual column types can be concurrently cast to
+		 * that type.  (Otherwise, we'd fail to reject combinations such
+		 * as int[] and point[].)
+		 */
+		Oid			input_typeids[2];
+		Oid			target_typeids[2];
+
+		input_typeids[0] = pktype;
+		input_typeids[1] = fktype;
+		target_typeids[0] = opcintype;
+		target_typeids[1] = opcintype;
+		if (can_coerce_type(2, input_typeids, target_typeids,
+							COERCION_IMPLICIT))
+		{
+			pfeqop = ffeqop = ppeqop;
+			pfeqop_right = opcintype;
+		}
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("foreign key constraint \"%s\" cannot be implemented",
+						fkconstraint->conname),
+				 errdetail("Key columns \"%s\" and \"%s\" "
+						   "are of incompatible types: %s and %s.",
+						   strVal(list_nth(fkconstraint->fk_attrs, i)),
+						   strVal(list_nth(fkconstraint->pk_attrs, i)),
+						   format_type_be(fktype),
+						   format_type_be(pktype))));
+
+	if (*old_check_ok)
+	{
+		/*
+		 * When a pfeqop changes, revalidate the constraint.  We could
+		 * permit intra-opfamily changes, but that adds subtle complexity
+		 * without any concrete benefit for core types.  We need not
+		 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
+		 */
+		*old_check_ok = (pfeqop == lfirst_oid(*old_pfeqop_item));
+		*old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
+								*old_pfeqop_item);
+	}
+	if (*old_check_ok)
+	{
+		Oid			old_fktype;
+		Oid			new_fktype;
+		CoercionPathType old_pathtype;
+		CoercionPathType new_pathtype;
+		Oid			old_castfunc;
+		Oid			new_castfunc;
+		Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+											   fkattnum[i] - 1);
+
+		/*
+		 * Identify coercion pathways from each of the old and new FK-side
+		 * column types to the right (foreign) operand type of the pfeqop.
+		 * We may assume that pg_constraint.conkey is not changing.
+		 */
+		old_fktype = attr->atttypid;
+		new_fktype = fktype;
+		old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
+									&old_castfunc);
+		new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
+									&new_castfunc);
+
+		/*
+		 * Upon a change to the cast from the FK column to its pfeqop
+		 * operand, revalidate the constraint.  For this evaluation, a
+		 * binary coercion cast is equivalent to no cast at all.  While
+		 * type implementors should design implicit casts with an eye
+		 * toward consistency of operations like equality, we cannot
+		 * assume here that they have done so.
+		 *
+		 * A function with a polymorphic argument could change behavior
+		 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
+		 * when the cast destination is polymorphic, we only avoid
+		 * revalidation if the input type has not changed at all.  Given
+		 * just the core data types and operator classes, this requirement
+		 * prevents no would-be optimizations.
+		 *
+		 * If the cast converts from a base type to a domain thereon, then
+		 * that domain type must be the opcintype of the unique index.
+		 * Necessarily, the primary key column must then be of the domain
+		 * type.  Since the constraint was previously valid, all values on
+		 * the foreign side necessarily exist on the primary side and in
+		 * turn conform to the domain.  Consequently, we need not treat
+		 * domains specially here.
+		 *
+		 * Since we require that all collations share the same notion of
+		 * equality (which they do, because texteq reduces to bitwise
+		 * equality), we don't compare collation here.
+		 *
+		 * We need not directly consider the PK type.  It's necessarily
+		 * binary coercible to the opcintype of the unique index column,
+		 * and ri_triggers.c will only deal with PK datums in terms of
+		 * that opcintype.  Changing the opcintype also changes pfeqop.
+		 */
+		*old_check_ok = (new_pathtype == old_pathtype &&
+						new_castfunc == old_castfunc &&
+						(!IsPolymorphicType(pfeqop_right) ||
+						 new_fktype == old_fktype));
+
+	}
+
+	*pfeqopOut = pfeqop;
+	*ppeqopOut = ppeqop;
+	*ffeqopOut = ffeqop;
+}
+
 /*
  * When the parent of a partition receives [the referencing side of] a foreign
  * key, we must propagate that foreign key to the partition.  However, the
@@ -9197,7 +9288,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 con->oid);
+										 con->oid, con->contemporal);
 			table_close(refrel, NoLock);
 
 			/*
@@ -9330,10 +9421,12 @@ transformColumnNameList(Oid relId, List *colList,
  *
  *	Look up the names, attnums, and types of the primary key attributes
  *	for the pkrel.  Also return the index OID and index opclasses of the
- *	index supporting the primary key.
+ *	index supporting the primary key.  If this is a temporal primary key,
+ *	also set the WITHOUT OVERLAPS attribute name, attnum, and atttypid.
  *
  *	All parameters except pkrel are output parameters.  Also, the function
- *	return value is the number of attributes in the primary key.
+ *	return value is the number of attributes in the primary key,
+ *	not including the WITHOUT OVERLAPS if any.
  *
  *	Used when the column list in the REFERENCES specification is omitted.
  */
@@ -9341,6 +9434,8 @@ static int
 transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses)
 {
 	List	   *indexoidlist;
@@ -9408,35 +9503,50 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 	/*
 	 * Now build the list of PK attributes from the indkey definition (we
 	 * assume a primary key cannot have expressional elements)
+	 * TODO: range expressions will be how we support PERIODs though.
 	 */
 	*attnamelist = NIL;
 	for (i = 0; i < indexStruct->indnkeyatts; i++)
 	{
 		int			pkattno = indexStruct->indkey.values[i];
 
-		attnums[i] = pkattno;
-		atttypids[i] = attnumTypeId(pkrel, pkattno);
-		opclasses[i] = indclass->values[i];
-		*attnamelist = lappend(*attnamelist,
-							   makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		if (i == indexStruct->indnkeyatts - 1 && indexStruct->indisexclusion)
+		{
+			periodattnums[i] = pkattno;
+			periodatttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*periodattname = (Node *)makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))));
+		}
+		else
+		{
+			attnums[i] = pkattno;
+			atttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*attnamelist = lappend(*attnamelist,
+								  makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		}
 	}
 
 	ReleaseSysCache(indexTuple);
 
-	return i;
+	if (indexStruct->indisexclusion) return i - 1;
+	else return i;
 }
 
 /*
  * transformFkeyCheckAttrs -
  *
  *	Make sure that the attributes of a referenced table belong to a unique
- *	(or primary key) constraint.  Return the OID of the index supporting
- *	the constraint, as well as the opclasses associated with the index
+ *	(or primary key) constraint.  Or if this is a temporal foreign key
+ *	the primary key should be an exclusion constraint instead.
+ *	Return the OID of the index supporting the constraint,
+ *	as well as the opclasses associated with the index
  *	columns.
  */
 static Oid
 transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
 						Oid *opclasses) /* output parameter */
 {
 	Oid			indexoid = InvalidOid;
@@ -9463,6 +9573,10 @@ transformFkeyCheckAttrs(Relation pkrel,
 						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 						 errmsg("foreign key referenced-columns list must not contain duplicates")));
 		}
+		if (is_temporal && attnums[i] == periodattnums[0])
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+					 errmsg("foreign key referenced-columns list must not contain duplicates")));
 	}
 
 	/*
@@ -9484,12 +9598,16 @@ transformFkeyCheckAttrs(Relation pkrel,
 		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		/*
-		 * Must have the right number of columns; must be unique and not a
+		 * Must have the right number of columns; must be unique
+		 * (or if temporal then exclusion instead) and not a
 		 * partial index; forget it if there are any expressions, too. Invalid
 		 * indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
-			indexStruct->indisunique &&
+		if ((is_temporal
+			  ? (indexStruct->indnkeyatts == numattrs + 1 &&
+				 indexStruct->indisexclusion)
+			  : (indexStruct->indnkeyatts == numattrs &&
+				 indexStruct->indisunique)) &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
 			heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
@@ -9529,6 +9647,19 @@ transformFkeyCheckAttrs(Relation pkrel,
 				if (!found)
 					break;
 			}
+			if (is_temporal)
+			{
+				found = false;
+				for (j = 0; j < numattrs + 1; j++)
+				{
+					if (periodattnums[0] == indexStruct->indkey.values[j])
+					{
+						opclasses[numattrs] = indclass->values[j];
+						found = true;
+						break;
+					}
+				}
+			}
 
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
@@ -9718,7 +9849,8 @@ validateForeignKeyConstraint(char *conname,
 							 Relation rel,
 							 Relation pkrel,
 							 Oid pkindOid,
-							 Oid constraintOid)
+							 Oid constraintOid,
+							 bool temporal)
 {
 	TupleTableSlot *slot;
 	TableScanDesc scan;
@@ -9748,8 +9880,10 @@ validateForeignKeyConstraint(char *conname,
 	/*
 	 * See if we can do it with a single LEFT JOIN query.  A false result
 	 * indicates we must proceed with the fire-the-trigger method.
+	 * We can't do a LEFT JOIN for temporal FKs yet,
+	 * but we can once we support temporal left joins.
 	 */
-	if (RI_Initial_Check(&trig, rel, pkrel))
+	if (!temporal && RI_Initial_Check(&trig, rel, pkrel))
 		return;
 
 	/*
@@ -9811,6 +9945,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 					 Oid constraintOid, Oid indexOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
+	bool is_temporal = fkconstraint->fk_period;
 
 	/*
 	 * Note: for a self-referential FK (referencing and referenced tables are
@@ -9822,7 +9957,10 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = "RI_ConstraintTrigger_c";
+	if (is_temporal)
+		fk_trigger->trigname = "TRI_ConstraintTrigger_c";
+	else
+		fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
 	fk_trigger->timing = TRIGGER_TYPE_AFTER;
@@ -9830,12 +9968,18 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	/* Either ON INSERT or ON UPDATE */
 	if (on_insert)
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_ins");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
 		fk_trigger->events = TRIGGER_TYPE_INSERT;
 	}
 	else
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_upd");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
 		fk_trigger->events = TRIGGER_TYPE_UPDATE;
 	}
 
@@ -9881,37 +10025,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_del_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_del");
+				break;
+				/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_del");
+				break;
+				*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
@@ -9937,37 +10122,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_upd_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_upd");
+				break;
+			/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_upd");
+				break;
+			*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 2d9a8e9d54..2ff3d8b81b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -776,6 +776,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* noinherit */
+											  false, /* contemporal */
 											  isInternal);	/* is_internal */
 	}
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 89887b8fd7..04f92fee95 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3174,6 +3174,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
+							  false,	/* contemporal */
 							  false);	/* is_internal */
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6414aded0e..15705b7653 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2921,12 +2921,15 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(where_clause);
 	COPY_NODE_FIELD(pktable);
 	COPY_NODE_FIELD(fk_attrs);
+	COPY_NODE_FIELD(fk_period);
 	COPY_NODE_FIELD(pk_attrs);
+	COPY_NODE_FIELD(pk_period);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb367f8..f5ecbeb445 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -473,10 +473,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause optionalPeriodName
+				where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
+%type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
@@ -667,7 +669,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
 
@@ -3462,6 +3464,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3678,18 +3681,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3700,6 +3704,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3728,19 +3733,21 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
-				opt_column_list key_match key_actions ConstraintAttributeSpec
+			| FOREIGN KEY '(' columnList optionalPeriodName ')' REFERENCES qualified_name
+				opt_column_and_period_list key_match key_actions ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_FOREIGN;
 					n->location = @1;
-					n->pktable			= $7;
+					n->pktable			= $8;
 					n->fk_attrs			= $4;
-					n->pk_attrs			= $8;
-					n->fk_matchtype		= $9;
-					n->fk_upd_action	= (char) ($10 >> 8);
-					n->fk_del_action	= (char) ($10 & 0xFF);
-					processCASbits($11, @11, "FOREIGN KEY",
+					n->fk_period		= $5;
+					n->pk_attrs			= linitial($9);
+					n->pk_period		= lsecond($9);
+					n->fk_matchtype		= $10;
+					n->fk_upd_action	= (char) ($11 >> 8);
+					n->fk_del_action	= (char) ($11 & 0xFF);
+					processCASbits($12, @12, "FOREIGN KEY",
 								   &n->deferrable, &n->initdeferred,
 								   &n->skip_validation, NULL,
 								   yyscanner);
@@ -3763,6 +3770,21 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+optionalPeriodName:
+			',' PERIOD columnElem { $$ = $3; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+opt_column_and_period_list:
+			'(' columnList optionalPeriodName ')'			{ $$ = list_make2($2, $3); }
+			| /*EMPTY*/								{ $$ = list_make2(NIL, NULL); }
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
@@ -15473,6 +15495,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 a9b2f8bacd..6f0a53295f 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1962,7 +1962,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2351,6 +2351,151 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				notnullcmd->name = pstrdup(key);
 				notnullcmds = lappend(notnullcmds, notnullcmd);
 			}
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					// ereport(NOTICE, (errmsg("range %s vs column %s of type %d", without_overlaps_str, column->colname, column->typeName->typeOid)));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				iparam->name = without_overlaps_str;	// TODO: pstrdup here?
+				iparam->expr = NULL;
+
+				/*
+				 * Force the column to NOT NULL since it is part of the primary key.
+				 */
+				AlterTableCmd *notnullcmd = makeNode(AlterTableCmd);
+
+				notnullcmd->subtype = AT_SetNotNull;
+				notnullcmd->name = pstrdup(without_overlaps_str);
+				notnullcmds = lappend(notnullcmds, notnullcmd);
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+
+			iparam->indexcolname = NULL;
+			iparam->collation = NIL;
+			iparam->opclass = NIL;
+			iparam->ordering = SORTBY_DEFAULT;
+			iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+			index->indexParams = lappend(index->indexParams, iparam);
+
+			List *opname = list_make1(makeString("&&"));
+			index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			index->accessMethod = "gist";
+			constraint->access_method = "gist";
 		}
 	}
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 8c895459c3..9cf3c99d78 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -109,6 +109,7 @@ typedef struct RI_ConstraintInfo
 	char		confupdtype;	/* foreign key's ON UPDATE action */
 	char		confdeltype;	/* foreign key's ON DELETE action */
 	char		confmatchtype;	/* foreign key's match type */
+	bool		temporal;		/* if the foreign key is temporal */
 	int			nkeys;			/* number of key columns */
 	int16		pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
 	int16		fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
@@ -192,7 +193,7 @@ static int	ri_NullCheck(TupleDesc tupdesc, TupleTableSlot *slot,
 static void ri_BuildQueryKey(RI_QueryKey *key,
 							 const RI_ConstraintInfo *riinfo,
 							 int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+static bool ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
 							   Datum oldvalue, Datum newvalue);
@@ -353,18 +354,46 @@ RI_FKey_check(TriggerData *trigdata)
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
-		 *		   FOR KEY SHARE OF x
+		 *	SELECT 1
+		 *	FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *	FOR KEY SHARE OF x
 		 * The type id's for the $ parameters are those of the
 		 * corresponding FK attributes.
+		 *
+		 * But for temporal FKs we need to make sure
+		 * the FK's range is completely covered.
+		 * So we use this query instead:
+		 *  SELECT 1
+		 *  FROM (
+		 *	    SELECT	range_agg(r, true, true) AS r
+		 *	    FROM	(
+		 *			SELECT pkperiodatt AS r
+		 *			FROM   [ONLY] pktable x
+		 *			WHERE  pkatt1 = $1 [AND ...]
+		 *			FOR KEY SHARE OF x
+		 *		) x1
+		 *  ) x2
+		 *  WHERE $n <@ x2.r[1]
+		 * Note if FOR KEY SHARE ever allows aggregate functions
+		 * we can make this a bit simpler.
 		 * ----------
 		 */
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
 		quoteRelationName(pkrelname, pk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 pk_only, pkrelname);
+		if (riinfo->temporal)
+		{
+			quoteOneName(attname,
+					RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			appendStringInfo(&querybuf,
+					"SELECT 1 FROM (SELECT range_agg(r, true, true) AS r FROM (SELECT %s AS r FROM %s%s x",
+					attname, pk_only, pkrelname);
+		}
+		else {
+			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
+							 pk_only, pkrelname);
+		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -382,6 +411,8 @@ RI_FKey_check(TriggerData *trigdata)
 			queryoids[i] = fk_type;
 		}
 		appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
+		if (riinfo->temporal)
+			appendStringInfo(&querybuf, ") x1) x2 WHERE $%d <@ x2.r[1]", riinfo->nkeys);
 
 		/* Prepare and save the plan */
 		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -1176,7 +1207,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
 		return false;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
+	if (newslot && ri_KeysStable(pk_rel, oldslot, newslot, riinfo, true))
 		return false;
 
 	/* Else we need to fire the trigger. */
@@ -1269,13 +1300,135 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 		return true;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
+	if (ri_KeysStable(fk_rel, oldslot, newslot, riinfo, false))
 		return false;
 
 	/* Else we need to fire the trigger. */
 	return true;
 }
 
+/* ----------
+ * TRI_FKey_check_ins -
+ *
+ *	Check temporal foreign key existence at insert event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_ins(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+	/*
+	 * Share code with UPDATE case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_check_upd -
+ *
+ *	Check temporal foreign key existence at update event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_upd(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+	/*
+	 * Share code with INSERT case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_noaction_del -
+ *
+ *	Give an error and roll back the current transaction if the
+ *	delete has resulted in a violation of the given temporal
+ *	referential integrity constraint.
+ * ----------
+ */
+Datum
+TRI_FKey_noaction_del(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
+
+	/*
+	 * Share code with RESTRICT/UPDATE cases.
+	 */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_del -
+ *
+ * Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the delete is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with NO ACTION/UPDATE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+/*
+ * TRI_FKey_noaction_upd -
+ *
+ * Give an error and roll back the current transaction if the
+ * update has resulted in a violation of the given referential
+ * integrity constraint.
+ */
+Datum
+TRI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with RESTRICT/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_upd -
+ *
+ * Restrict update of PK to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the update is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with NO ACTION/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+
 /*
  * RI_Initial_Check -
  *
@@ -2051,6 +2204,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo->confupdtype = conForm->confupdtype;
 	riinfo->confdeltype = conForm->confdeltype;
 	riinfo->confmatchtype = conForm->confmatchtype;
+	riinfo->temporal = conForm->contemporal;
 
 	DeconstructFkConstraintRow(tup,
 							   &riinfo->nkeys,
@@ -2649,9 +2803,12 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 
 
 /*
- * ri_KeysEqual -
+ * ri_KeysStable -
  *
- * Check if all key values in OLD and NEW are equal.
+ * Check if all key values in OLD and NEW are "equivalent":
+ * For normal FKs we check for equality.
+ * For temporal FKs we check that the PK side is a superset of its old value,
+ * or the FK side is a subset.
  *
  * Note: at some point we might wish to redefine this as checking for
  * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
@@ -2659,7 +2816,7 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
  * previously found at least one of the rows to contain no nulls.
  */
 static bool
-ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -2692,29 +2849,43 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 
 		if (rel_is_pk)
 		{
-			/*
-			 * If we are looking at the PK table, then do a bytewise
-			 * comparison.  We must propagate PK changes if the value is
-			 * changed to one that "looks" different but would compare as
-			 * equal using the equality operator.  This only makes a
-			 * difference for ON UPDATE CASCADE, but for consistency we treat
-			 * all changes to the PK the same.
-			 */
-			Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, newvalue, oldvalue));
+			}
+			else
+			{
+				/*
+				 * If we are looking at the PK table, then do a bytewise
+				 * comparison.  We must propagate PK changes if the value is
+				 * changed to one that "looks" different but would compare as
+				 * equal using the equality operator.  This only makes a
+				 * difference for ON UPDATE CASCADE, but for consistency we treat
+				 * all changes to the PK the same.
+				 */
+				Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
 
-			if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
-				return false;
+				if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
+					return false;
+			}
 		}
 		else
 		{
-			/*
-			 * For the FK table, compare with the appropriate equality
-			 * operator.  Changes that compare equal will still satisfy the
-			 * constraint after the update.
-			 */
-			if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
-									oldvalue, newvalue))
-				return false;
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, oldvalue, newvalue));
+			}
+			else
+			{
+				/*
+				 * For the FK table, compare with the appropriate equality
+				 * operator.  Changes that compare equal will still satisfy the
+				 * constraint after the update.
+				 */
+				if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
+										oldvalue, newvalue))
+					return false;
+			}
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0c58f1f109..473b0d1aba 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -317,7 +317,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 								   int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int	decompile_column_index_array(Datum column_index_array, Oid relId,
-										 StringInfo buf);
+										 bool withoutOverlaps, bool withPeriod, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									const Oid *excludeOps,
@@ -1985,6 +1985,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			{
 				Datum		val;
 				bool		isnull;
+				bool		hasperiod;
 				const char *string;
 
 				/* Start off the constraint definition */
@@ -1997,7 +1998,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it is a temporal foreign key
+				 * then it uses PERIOD.
+				 */
+				hasperiod = DatumGetBool(SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_contemporal, &isnull));
+				decompile_column_index_array(val, conForm->conrelid, false, hasperiod, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2011,7 +2018,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, hasperiod, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2112,7 +2119,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2314,7 +2327,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, bool withPeriod, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2332,9 +2345,21 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
+		else if (withPeriod && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", PERIOD %s", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7aa5d7c7fa..4d863d0715 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4949,8 +4949,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5016,7 +5017,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8a31672247..039635a705 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6844,7 +6844,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6905,7 +6906,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -6944,7 +6946,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6979,7 +6982,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7010,7 +7014,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7044,7 +7049,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7084,6 +7090,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7143,6 +7150,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16591,9 +16599,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index ccf2153fac..71fdb7b35e 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -449,6 +449,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+	bool		withoutoverlaps;	/* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 7cbccee103..fe5081270e 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -632,6 +632,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 774cc764ff..161db48f2c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2388,6 +2388,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c1e60c7dfd..0baa22c0e5 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -103,6 +103,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	/* Has a local definition and cannot be inherited */
 	bool		connoinherit;
 
+	/*
+	 * For primary and foreign keys, signifies the last column is a range 
+	 * and should use overlaps instead of equals.
+	 */
+	bool		contemporal;
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 
 	/*
@@ -117,26 +123,26 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	int16		confkey[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * If a foreign key, the OIDs of the PK = FK comparison operators for each
 	 * column of the constraint
 	 */
 	Oid			conpfeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * If a foreign key, the OIDs of the PK = PK comparison operators for each
 	 * column of the constraint (i.e., equality for the referenced columns)
 	 */
 	Oid			conppeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * If a foreign key, the OIDs of the FK = FK comparison operators for each
 	 * column of the constraint (i.e., equality for the referencing columns)
 	 */
 	Oid			conffeqop[1];
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
@@ -211,6 +217,7 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conIsLocal,
 								  int conInhCount,
 								  bool conNoInherit,
+								  bool conTemporal,
 								  bool is_internal);
 
 extern void RemoveConstraintById(Oid conId);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0902dce5f1..1bca4e417e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3703,6 +3703,50 @@
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'RI_FKey_noaction_upd' },
 
+# Temporal referential integrity constraint triggers
+{ oid => '6122', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_ins' },
+{ oid => '6123', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_upd' },
+# { oid => '6124', descr => 'temporal referential integrity ON DELETE CASCADE',
+#   proname => 'TRI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_del' },
+# { oid => '6125', descr => 'temporal referential integrity ON UPDATE CASCADE',
+#   proname => 'TRI_FKey_cascade_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_upd' },
+{ oid => '6126', descr => 'temporal referential integrity ON DELETE RESTRICT',
+  proname => 'TRI_FKey_restrict_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_del' },
+{ oid => '6127', descr => 'temporal referential integrity ON UPDATE RESTRICT',
+  proname => 'TRI_FKey_restrict_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_upd' },
+# { oid => '6128', descr => 'temporal referential integrity ON DELETE SET NULL',
+#   proname => 'TRI_FKey_setnull_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_del' },
+# { oid => '6129', descr => 'temporal referential integrity ON UPDATE SET NULL',
+#   proname => 'TRI_FKey_setnull_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_upd' },
+# { oid => '6130', descr => 'temporal referential integrity ON DELETE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_del', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_del' },
+# { oid => '6131', descr => 'temporal referential integrity ON UPDATE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_upd', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_upd' },
+{ oid => '6132', descr => 'temporal referential integrity ON DELETE NO ACTION',
+  proname => 'TRI_FKey_noaction_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_del' },
+{ oid => '6133', descr => 'temporal referential integrity ON UPDATE NO ACTION',
+  proname => 'TRI_FKey_noaction_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_upd' },
+
 { oid => '1666',
   proname => 'varbiteq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'varbit varbit', prosrc => 'biteq' },
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..5eb49738c8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2162,7 +2162,9 @@ typedef struct Constraint
 	/* Fields used for FOREIGN KEY constraints: */
 	RangeVar   *pktable;		/* Primary key table */
 	List	   *fk_attrs;		/* Attributes of foreign key */
+	Node	   *fk_period;		/* String node naming Period or range column */
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
+	Node	   *pk_period;		/* String node naming Period or range column */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
 	char		fk_del_action;	/* ON DELETE action */
@@ -2170,6 +2172,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+	Node	   *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..92628f390a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -299,6 +299,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, RESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d6e75ffce6..7f1bd6f75e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -172,6 +172,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+referencing_period_test|t
 road|t
 shighway|t
 slow_emp4000|f
@@ -212,6 +213,7 @@ timetz_tbl|f
 tmp|f
 varchar_tbl|f
 view_base_table|t
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fb55f045e..39d6362aed 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info without_overlaps
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a39ca1012a..cdc8e717a3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -19,6 +19,7 @@ test: uuid
 test: enum
 test: money
 test: rangetypes
+test: without_overlaps
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..83824a55d3
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,369 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
+
+--
+-- test FK parser
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+-- TODO
+--
+-- test FK parent updates SET NULL
+--
+-- TODO
+--
+-- test FK parent updates SET DEFAULT
+--
+-- TODO
+
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+-- TODO
+--
+-- test FK parent deletes SET NULL
+--
+-- TODO
+--
+-- test FK parent deletes SET DEFAULT
+--
+-- TODO
#17Thomas Munro
thomas.munro@gmail.com
In reply to: Ibrar Ahmed (#16)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Wed, Jul 31, 2019 at 1:01 AM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

I have rebased the patch to master (1e2fddfa33d3c7cc93ca3ee0f32852699bd3e012) and fixed some compilation warning. Now I am reviewing the actual code.

Thanks for doing that Ibrar. I think the right status for this CF
entry is now: Needs review. I have set it that way, in the September
CF. By the way, there are some test failures:

https://ci.appveyor.com/project/postgresql-cfbot/postgresql/build/1.0.50280

--
Thomas Munro
https://enterprisedb.com

#18Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Thomas Munro (#17)
Re: SQL:2011 PERIODS vs Postgres Ranges?

The patch does not work.

postgres=# CREATE TABLE foo (id int,r int4range, valid_at tsrange, CONSTRAINT bar_pk PRIMARY KEY (r, valid_at WITHOUT OVERLAPS));
CREATE TABLE
postgres=# CREATE TABLE bar (id int,r int4range, valid_at tsrange, CONSTRAINT bar_fk FOREIGN KEY (r, PERIOD valid_at) REFERENCES foo);
ERROR: cache lookup failed for type 0

The new status of this patch is: Waiting on Author

#19Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Ibrar Ahmed (#18)
6 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Hi Paul,
I did some clean-up on this patch. I have also refactored a small portion
of the code
to reduce the footprint of the patch. For simplicity, I have divided the
patch into 6
patches, now it is easy to review and debug.

001_temporal_table_grammer_v006.patch

002_temporal_table_doc_v006.patch

003_temporal_table_backend_v006.patch

004_temporal_table_pgdump_v006.patch

005_temporal_table_regression_v006.patch

006_temporal_table_psql_v006.patch

Please follow the PostgreSQL coding guidelines. I have found places where
you missed that, secondly code even in WIP stage must not have WARNING
because it looks ugly.

On Sat, Aug 3, 2019 at 1:29 AM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

The patch does not work.

postgres=# CREATE TABLE foo (id int,r int4range, valid_at tsrange,
CONSTRAINT bar_pk PRIMARY KEY (r, valid_at WITHOUT OVERLAPS));
CREATE TABLE
postgres=# CREATE TABLE bar (id int,r int4range, valid_at tsrange,
CONSTRAINT bar_fk FOREIGN KEY (r, PERIOD valid_at) REFERENCES foo);
ERROR: cache lookup failed for type 0

The new status of this patch is: Waiting on Author

--
Ibrar Ahmed

Attachments:

006_temporal_table_psql_v006.patchapplication/octet-stream; name=006_temporal_table_psql_v006.patchDownload
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 774cc764ff..161db48f2c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2388,6 +2388,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
005_temporal_table_regression_v006.patchapplication/octet-stream; name=005_temporal_table_regression_v006.patchDownload
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d6e75ffce6..7f1bd6f75e 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -172,6 +172,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+referencing_period_test|t
 road|t
 shighway|t
 slow_emp4000|f
@@ -212,6 +213,7 @@ timetz_tbl|f
 tmp|f
 varchar_tbl|f
 view_base_table|t
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
deleted file mode 100644
index a3c6f1ff45..0000000000
--- a/src/test/regress/expected/without_overlaps.out
+++ /dev/null
@@ -1,363 +0,0 @@
--- Tests for WITHOUT OVERLAPS.
---
--- test input parser
---
--- PK with no columns just WITHOUT OVERLAPS:
-CREATE TABLE without_overlaps_test (
-	valid_at tsrange,
-	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
-);
-ERROR:  syntax error at or near "WITHOUT"
-LINE 3: ...STRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OV...
-                                                             ^
--- PK with a range column that isn't there:
-CREATE TABLE without_overlaps_test (
-	id INTEGER,
-	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-);
-ERROR:  range or PERIOD "valid_at" named in WITHOUT OVERLAPS does not exist
--- PK with a PERIOD that isn't there:
--- PK with a non-range column:
-CREATE TABLE without_overlaps_test (
-	id INTEGER,
-	valid_at TEXT,
-	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-);
-ERROR:  column "valid_at" named in WITHOUT OVERLAPS is not a range type
--- PK with one column plus a range:
-CREATE TABLE without_overlaps_test (
-	-- Since we can't depend on having btree_gist here,
-	-- use an int4range instead of an int.
-	-- (The rangetypes regression test uses the same trick.)
-	id int4range,
-	valid_at tsrange,
-	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-);
--- PK with two columns plus a range:
-CREATE TABLE without_overlaps_test2 (
-	id1 int4range,
-	id2 int4range,
-	valid_at tsrange,
-	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
-);
-DROP TABLE without_overlaps_test2;
--- PK with one column plus a PERIOD:
--- PK with two columns plus a PERIOD:
--- PK with a custom range type:
-CREATE TYPE textrange2 AS range (subtype=text, collation="C");
-CREATE TABLE without_overlaps_test2 (
-	id int4range,
-	valid_at textrange2,
-	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-);
-ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
-DROP TABLE without_overlaps_test2;
-DROP TYPE textrange2;
---
--- test ALTER TABLE ADD CONSTRAINT
---
-DROP TABLE without_overlaps_test;
-CREATE TABLE without_overlaps_test (
-	id int4range,
-	valid_at tsrange
-);
-ALTER TABLE without_overlaps_test
-	ADD CONSTRAINT without_overlaps_pk
-	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
---
--- test pg_get_constraintdef
---
-SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
-            pg_get_constraintdef             
----------------------------------------------
- PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-(1 row)
-
-DROP TABLE without_overlaps_test;
-CREATE TABLE without_overlaps_test (
-	id int4range,
-	valid_at tsrange
-);
-ALTER TABLE without_overlaps_test
-	ADD CONSTRAINT without_overlaps_pk
-	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
---
--- test pg_get_constraintdef
---
-SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
-            pg_get_constraintdef             
----------------------------------------------
- PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-(1 row)
-
---
--- test PK inserts
---
--- okay:
-INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
-INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
-INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
-INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
--- should fail:
-INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
-ERROR:  conflicting key value violates exclusion constraint "without_overlaps_pk"
-DETAIL:  Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
-INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
-ERROR:  null value in column "id" violates not-null constraint
-DETAIL:  Failing row contains (null, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
-INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
-ERROR:  null value in column "valid_at" violates not-null constraint
-DETAIL:  Failing row contains ([3,4), null).
---
--- test changing the PK's dependencies
---
-CREATE TABLE without_overlaps_test2 (
-	id int4range,
-	valid_at tsrange,
-	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-);
-ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
-ERROR:  column "valid_at" is in a primary key
-ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
-ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
-ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
-DROP TABLE without_overlaps_test2;
---
--- test FK parser
---
-CREATE TABLE referencing_period_test (
-	id int4range,
-	valid_at tsrange,
-	parent_id int4range,
-	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
-	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
-		REFERENCES without_overlaps_test (id, PERIOD valid_at)
-);
-DROP TABLE referencing_period_test;
--- with inferred PK on the referenced table:
-CREATE TABLE referencing_period_test (
-	id int4range,
-	valid_at tsrange,
-	parent_id int4range,
-	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
-	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
-		REFERENCES without_overlaps_test
-);
-DROP TABLE referencing_period_test;
--- should fail because of duplicate referenced columns:
-CREATE TABLE referencing_period_test (
-	id int4range,
-	valid_at tsrange,
-	parent_id int4range,
-	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
-	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
-		REFERENCES without_overlaps_test (id, PERIOD id)
-);
-ERROR:  foreign key referenced-columns list must not contain duplicates
---
--- test ALTER TABLE ADD CONSTRAINT
---
-CREATE TABLE referencing_period_test (
-	id int4range,
-	valid_at tsrange,
-	parent_id int4range,
-	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
-);
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk
-  FOREIGN KEY (parent_id, PERIOD valid_at)
-  REFERENCES without_overlaps_test (id, PERIOD valid_at);
-ALTER TABLE referencing_period_test
-  DROP CONSTRAINT referencing_period_fk;
--- with inferred PK on the referenced table:
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk
-  FOREIGN KEY (parent_id, PERIOD valid_at)
-  REFERENCES without_overlaps_test;
--- should fail because of duplicate referenced columns:
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk2
-  FOREIGN KEY (parent_id, PERIOD parent_id)
-  REFERENCES without_overlaps_test (id, PERIOD id);
-ERROR:  foreign key referenced-columns list must not contain duplicates
---
--- test with rows already
---
-DELETE FROM referencing_period_test;
-ALTER TABLE referencing_period_test
-  DROP CONSTRAINT referencing_period_fk;
-INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk
-  FOREIGN KEY (parent_id, PERIOD valid_at)
-  REFERENCES without_overlaps_test;
-ALTER TABLE referencing_period_test
-  DROP CONSTRAINT referencing_period_fk;
-INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
--- should fail:
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk
-  FOREIGN KEY (parent_id, PERIOD valid_at)
-  REFERENCES without_overlaps_test;
-ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
-DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
--- okay again:
-DELETE FROM referencing_period_test;
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk
-  FOREIGN KEY (parent_id, PERIOD valid_at)
-  REFERENCES without_overlaps_test;
---
--- test pg_get_constraintdef
---
-SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
-                                      pg_get_constraintdef                                      
-------------------------------------------------------------------------------------------------
- FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
-(1 row)
-
---
--- test FK child inserts
---
-INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
--- should fail:
-INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
-ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
-DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
--- now it should work:
-INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
-INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
---
--- test FK child updates
---
-UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
--- should fail:
-UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
-ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
-DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Tue May 01 00:00:00 2018")) is not present in table "without_overlaps_test".
-UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
-ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
-DETAIL:  Key (parent_id, valid_at)=([8,9), ["Tue Jan 02 00:00:00 2018","Thu Mar 01 00:00:00 2018")) is not present in table "without_overlaps_test".
---
--- test FK parent updates NO ACTION
---
--- a PK update that succeeds because the numeric id isn't referenced:
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
-UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
--- a PK update that succeeds even though the numeric id is referenced because the range isn't:
-DELETE FROM without_overlaps_test WHERE id = '[5,5]';
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
-INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
-UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
-  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
--- a PK update that fails because both are referenced:
-UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
-  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
-DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
--- then delete the objecting FK record and the same PK update succeeds:
-DELETE FROM referencing_period_test WHERE id = '[3,3]';
-UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
-  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
--- clean up:
-DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
-DELETE FROM without_overlaps_test WHERE id = '[5,5]';
---
--- test FK parent updates RESTRICT
---
-ALTER TABLE referencing_period_test
-  DROP CONSTRAINT referencing_period_fk;
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk
-  FOREIGN KEY (parent_id, PERIOD valid_at)
-  REFERENCES without_overlaps_test
-  ON DELETE RESTRICT;
--- a PK update that succeeds because the numeric id isn't referenced:
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
-UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
--- a PK update that succeeds even though the numeric id is referenced because the range isn't:
-DELETE FROM without_overlaps_test WHERE id = '[5,5]';
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
-INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
-UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
-  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
--- a PK update that fails because both are referenced:
-UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
-  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
-DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
--- then delete the objecting FK record and the same PK update succeeds:
-DELETE FROM referencing_period_test WHERE id = '[3,3]';
-UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
-  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
--- clean up:
-DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
-DELETE FROM without_overlaps_test WHERE id = '[5,5]';
---
--- test FK parent updates CASCADE
---
---
--- test FK parent updates SET NULL
---
---
--- test FK parent updates SET DEFAULT
---
---
--- test FK parent deletes NO ACTION
---
-ALTER TABLE referencing_period_test
-  DROP CONSTRAINT referencing_period_fk;
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk
-  FOREIGN KEY (parent_id, PERIOD valid_at)
-  REFERENCES without_overlaps_test;
--- a PK delete that succeeds because the numeric id isn't referenced:
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
-DELETE FROM without_overlaps_test WHERE id = '[5,5]';
--- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
-INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
-DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
--- a PK delete that fails because both are referenced:
-DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
-DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
--- then delete the objecting FK record and the same PK delete succeeds:
-DELETE FROM referencing_period_test WHERE id = '[3,3]';
-DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
---
--- test FK parent deletes RESTRICT
---
-ALTER TABLE referencing_period_test
-  DROP CONSTRAINT referencing_period_fk;
-ALTER TABLE referencing_period_test
-  ADD CONSTRAINT referencing_period_fk
-  FOREIGN KEY (parent_id, PERIOD valid_at)
-  REFERENCES without_overlaps_test
-  ON DELETE RESTRICT;
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
-DELETE FROM without_overlaps_test WHERE id = '[5,5]';
--- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
-INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
-INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
-DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
--- a PK delete that fails because both are referenced:
-DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
-ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
-DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
--- then delete the objecting FK record and the same PK delete succeeds:
-DELETE FROM referencing_period_test WHERE id = '[3,3]';
-DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
---
--- test FK parent deletes CASCADE
---
---
--- test FK parent deletes SET NULL
---
---
--- test FK parent deletes SET DEFAULT
---
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8fb55f045e..39d6362aed 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info without_overlaps
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index a39ca1012a..cdc8e717a3 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -19,6 +19,7 @@ test: uuid
 test: enum
 test: money
 test: rangetypes
+test: without_overlaps
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..83824a55d3
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,369 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
+
+--
+-- test FK parser
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+-- TODO
+--
+-- test FK parent updates SET NULL
+--
+-- TODO
+--
+-- test FK parent updates SET DEFAULT
+--
+-- TODO
+
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+-- TODO
+--
+-- test FK parent deletes SET NULL
+--
+-- TODO
+--
+-- test FK parent deletes SET DEFAULT
+--
+-- TODO
004_temporal_table_pgdump_v006.patchapplication/octet-stream; name=004_temporal_table_pgdump_v006.patchDownload
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8a31672247..039635a705 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6844,7 +6844,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6905,7 +6906,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -6944,7 +6946,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6979,7 +6982,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7010,7 +7014,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7044,7 +7049,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7084,6 +7090,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7143,6 +7150,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16591,9 +16599,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index ccf2153fac..71fdb7b35e 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -449,6 +449,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+	bool		withoutoverlaps;	/* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 7cbccee103..fe5081270e 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -632,6 +632,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
002_temporal_table_doc_v006.patchapplication/octet-stream; name=002_temporal_table_doc_v006.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 9009addb9c..dfbc65d327 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -78,9 +78,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
   UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
-  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
+  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_inverval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
-  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
 class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 <phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">temporal_interval</replaceable> in a <literal>PRIMARY KEY</literal> or <literal>FOREIGN KEY</literal> constraint is:</phrase>
+
+<replaceable class="parameter">range_column_name</replaceable>
+
 </synopsis>
 
  </refsynopsisdiv>
@@ -909,7 +914,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term><literal>PRIMARY KEY</literal> (column constraint)</term>
-    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ]
+    [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] )</literal>
     <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
     <listitem>
      <para>
@@ -942,7 +948,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
      <para>
       Adding a <literal>PRIMARY KEY</literal> constraint will automatically
-      create a unique btree index on the column or group of columns used in the
+      create a unique btree (or GiST if temporal) index on the column or group of columns used in the
       constraint.  The optional <literal>INCLUDE</literal> clause allows a list
       of columns to be specified which will be included in the non-key portion
       of the index.  Although uniqueness is not enforced on the included columns,
@@ -950,6 +956,24 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       included columns (e.g. <literal>DROP COLUMN</literal>) can cause cascaded
       constraint and index deletion.
      </para>
+
+     <para>
+      A <literal>PRIMARY KEY</literal> with a <literal>WITHOUT OVERLAPS</literal> option
+      is a <emphasis>temporal</emphasis> primary key.
+      The <literal>WITHOUT OVERLAPS</literal> column
+      must be a range type and is used to constrain the record's applicability
+      to just that range (usually a range of dates or timestamps).
+      The main part of the primary key may be repeated elsewhere in the table,
+      as long as records with the same key don't overlap in the
+      <literal>WITHOUT OVERLAPS</literal> column.
+     </para>
+
+     <para>
+      A temporal <literal>PRIMARY KEY</literal> is enforced with an
+      <literal>EXCLUDE</literal> constraint rather than a <literal>UNIQUE</literal>
+      constraint, backed by a GiST index. You may need to install the
+      <xref linkend="btree-gist"/> extension to create temporal primary keys.
+     </para>
     </listitem>
    </varlistentry>
 
@@ -1006,8 +1030,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    <varlistentry>
     <term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal> (column constraint)</term>
 
-   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
-    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] )
+    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH <replaceable class="parameter">matchtype</replaceable> ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ]
     [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal>
@@ -1018,11 +1042,29 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       These clauses specify a foreign key constraint, which requires
       that a group of one or more columns of the new table must only
       contain values that match values in the referenced
-      column(s) of some row of the referenced table.  If the <replaceable
+      column(s) of some row of the referenced table.
+      If the <replaceable
       class="parameter">refcolumn</replaceable> list is omitted, the
       primary key of the <replaceable class="parameter">reftable</replaceable>
       is used.  The referenced columns must be the columns of a non-deferrable
-      unique or primary key constraint in the referenced table.  The user
+      unique or primary key constraint in the referenced table.
+     </para>
+
+     <para>
+      If the last column is marked with <literal>PERIOD</literal>,
+      it must be a range column, and the referenced table
+      must have a temporal primary key.
+      The non-<literal>PERIOD</literal> columns are treated normally
+      (and there must be at least one of them),
+      but the <literal>PERIOD</literal> column is not compared for equality.
+      Instead the constraint is considered satisfied
+      if the referenced table has matching records whose combined ranges completely cover
+      the referencing record.
+      In other words, the reference must have a referent for its entire duration.
+     </para>
+
+     <para>
+      The user
       must have <literal>REFERENCES</literal> permission on the referenced table
       (either the whole table, or the specific referenced columns).  The
       addition of a foreign key constraint requires a
003_temporal_table_backend_v006.patchapplication/octet-stream; name=003_temporal_table_backend_v006.patchDownload
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index 368b1dea3e..f6cfbb68f6 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -237,6 +237,7 @@ sub ParseData
 	# Scan the input file.
 	while (<$ifd>)
 	{
+    next if /^#/;
 		my $hash_ref;
 
 		if (/{/)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9d0f..c1346d765c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2399,6 +2399,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
+							  false,	/* contemporal */
 							  is_internal); /* internally constructed? */
 
 	pfree(ccbin);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 99ae159f98..41645ec692 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1832,6 +1832,7 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
+								   false,	/* contemporal */
 								   is_internal);
 
 	/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index b6145593a3..e913741103 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -76,6 +76,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
+					  bool conTemporal,
 					  bool is_internal)
 {
 	Relation	conDesc;
@@ -184,6 +185,7 @@ CreateConstraintEntry(const char *constraintName,
 	values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
 	values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
 	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+	values[Anum_pg_constraint_contemporal - 1] = BoolGetDatum(conTemporal);
 
 	if (conkeyArray)
 		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fb2be10794..217d7f385c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -328,9 +328,12 @@ static int	transformColumnNameList(Oid relId, List *colList,
 static int	transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 									   List **attnamelist,
 									   int16 *attnums, Oid *atttypids,
+										 Node **periodattname,
+										 int16 *periodattnums, Oid *periodatttypids,
 									   Oid *opclasses);
 static Oid	transformFkeyCheckAttrs(Relation pkrel,
 									int numattrs, int16 *attnums,
+									bool is_temporal, int16 *periodattnums,
 									Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
@@ -338,7 +341,7 @@ static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
 static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
 static void validateForeignKeyConstraint(char *conname,
 										 Relation rel, Relation pkrel,
-										 Oid pkindOid, Oid constraintOid);
+										 Oid pkindOid, Oid constraintOid, bool temporal);
 static void ATController(AlterTableStmt *parsetree,
 						 Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -424,12 +427,12 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 											Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 											int numfks, int16 *pkattnum, int16 *fkattnum,
 											Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-											bool old_check_ok);
+											bool old_check_ok, bool is_temporal);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
 									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 									int numfks, int16 *pkattnum, int16 *fkattnum,
 									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-									bool old_check_ok, LOCKMODE lockmode);
+									bool old_check_ok, bool is_temporal, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 									   Relation partitionRel);
 static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
@@ -446,6 +449,12 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
 										 Oid parentConstrOid, int numfks,
 										 AttrNumber *mapped_conkey, AttrNumber *confkey,
 										 Oid *conpfeqop);
+static void FindFKComparisonOperators(Constraint *fkconstraint,
+					AlteredTableInfo *tab, int i, int16 *fkattnum,
+					bool *old_check_ok, ListCell **old_pfeqop_item,
+					Oid pktype, Oid fktype, Oid opclass,
+					bool is_temporal, bool for_overlaps,
+					Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 								 DropBehavior behavior,
 								 bool recurse, bool recursing,
@@ -4709,7 +4718,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 
 				validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
 											 con->refindid,
-											 con->conid);
+											 con->conid,
+											 fkconstraint->fk_period != NULL);
 
 				/*
 				 * No need to mark the constraint row as validated, we did
@@ -7604,6 +7614,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	bool		is_temporal = (fkconstraint->fk_period != NULL);
+	int16		pkperiodattnum[1];
+	int16		fkperiodattnum[1];
+	Oid			pkperiodtypoid[1];
+	Oid			fkperiodtypoid[1];
 	int			i;
 	int			numfks,
 				numpks;
@@ -7706,6 +7721,18 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
+	if (is_temporal)
+	{
+	  List *fk_period;
+	  MemSet(pkperiodattnum, 0, sizeof(pkperiodattnum));
+	  MemSet(fkperiodattnum, 0, sizeof(fkperiodattnum));
+	  MemSet(pkperiodtypoid, 0, sizeof(pkperiodtypoid));
+	  MemSet(fkperiodtypoid, 0, sizeof(fkperiodtypoid));
+	  fk_period = list_make1(fkconstraint->fk_period);
+	  transformColumnNameList(RelationGetRelid(rel),
+							  fk_period,
+							  fkperiodattnum, fkperiodtypoid);
+	}
 
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
@@ -7718,6 +7745,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
 											&fkconstraint->pk_attrs,
 											pkattnum, pktypoid,
+											&fkconstraint->pk_period,
+											pkperiodattnum, pkperiodtypoid,
 											opclasses);
 	}
 	else
@@ -7725,8 +7754,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformColumnNameList(RelationGetRelid(pkrel),
 										 fkconstraint->pk_attrs,
 										 pkattnum, pktypoid);
+		if (is_temporal) {
+			List *pk_period = list_make1(fkconstraint->pk_period);
+			transformColumnNameList(RelationGetRelid(pkrel),
+									pk_period,
+									pkperiodattnum, pkperiodtypoid);
+		}
 		/* Look for an index matching the column list */
 		indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
+										   is_temporal, pkperiodattnum,
 										   opclasses);
 	}
 
@@ -7776,6 +7812,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
+	// TODO: Need a check that if one side has a PERIOD the other does too
+
 	/*
 	 * On the strength of a previous constraint, we might avoid scanning
 	 * tables to validate this one.  See below.
@@ -7785,188 +7823,29 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		Oid			pktype = pktypoid[i];
-		Oid			fktype = fktypoid[i];
-		Oid			fktyped;
-		HeapTuple	cla_ht;
-		Form_pg_opclass cla_tup;
-		Oid			amid;
-		Oid			opfamily;
-		Oid			opcintype;
-		Oid			pfeqop;
-		Oid			ppeqop;
-		Oid			ffeqop;
-		int16		eqstrategy;
-		Oid			pfeqop_right;
-
-		/* We need several fields out of the pg_opclass entry */
-		cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
-		if (!HeapTupleIsValid(cla_ht))
-			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
-		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
-		amid = cla_tup->opcmethod;
-		opfamily = cla_tup->opcfamily;
-		opcintype = cla_tup->opcintype;
-		ReleaseSysCache(cla_ht);
-
-		/*
-		 * Check it's a btree; currently this can never fail since no other
-		 * index AMs support unique indexes.  If we ever did have other types
-		 * of unique indexes, we'd need a way to determine which operator
-		 * strategy number is equality.  (Is it reasonable to insist that
-		 * every such index AM use btree's number for equality?)
-		 */
-		if (amid != BTREE_AM_OID)
-			elog(ERROR, "only b-tree indexes are supported for foreign keys");
-		eqstrategy = BTEqualStrategyNumber;
-
-		/*
-		 * There had better be a primary equality operator for the index.
-		 * We'll use it for PK = PK comparisons.
-		 */
-		ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
-									 eqstrategy);
-
-		if (!OidIsValid(ppeqop))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 eqstrategy, opcintype, opcintype, opfamily);
-
-		/*
-		 * Are there equality operators that take exactly the FK type? Assume
-		 * we should look through any domain here.
-		 */
-		fktyped = getBaseType(fktype);
-
-		pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
-									 eqstrategy);
-		if (OidIsValid(pfeqop))
-		{
-			pfeqop_right = fktyped;
-			ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
-										 eqstrategy);
-		}
-		else
-		{
-			/* keep compiler quiet */
-			pfeqop_right = InvalidOid;
-			ffeqop = InvalidOid;
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-		{
-			/*
-			 * Otherwise, look for an implicit cast from the FK type to the
-			 * opcintype, and if found, use the primary equality operator.
-			 * This is a bit tricky because opcintype might be a polymorphic
-			 * type such as ANYARRAY or ANYENUM; so what we have to test is
-			 * whether the two actual column types can be concurrently cast to
-			 * that type.  (Otherwise, we'd fail to reject combinations such
-			 * as int[] and point[].)
-			 */
-			Oid			input_typeids[2];
-			Oid			target_typeids[2];
-
-			input_typeids[0] = pktype;
-			input_typeids[1] = fktype;
-			target_typeids[0] = opcintype;
-			target_typeids[1] = opcintype;
-			if (can_coerce_type(2, input_typeids, target_typeids,
-								COERCION_IMPLICIT))
-			{
-				pfeqop = ffeqop = ppeqop;
-				pfeqop_right = opcintype;
-			}
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("foreign key constraint \"%s\" cannot be implemented",
-							fkconstraint->conname),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of incompatible types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktype),
-							   format_type_be(pktype))));
-
-		if (old_check_ok)
-		{
-			/*
-			 * When a pfeqop changes, revalidate the constraint.  We could
-			 * permit intra-opfamily changes, but that adds subtle complexity
-			 * without any concrete benefit for core types.  We need not
-			 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
-			 */
-			old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
-			old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
-									old_pfeqop_item);
-		}
-		if (old_check_ok)
-		{
-			Oid			old_fktype;
-			Oid			new_fktype;
-			CoercionPathType old_pathtype;
-			CoercionPathType new_pathtype;
-			Oid			old_castfunc;
-			Oid			new_castfunc;
-			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
-												   fkattnum[i] - 1);
-
-			/*
-			 * Identify coercion pathways from each of the old and new FK-side
-			 * column types to the right (foreign) operand type of the pfeqop.
-			 * We may assume that pg_constraint.conkey is not changing.
-			 */
-			old_fktype = attr->atttypid;
-			new_fktype = fktype;
-			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
-										&old_castfunc);
-			new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
-										&new_castfunc);
-
-			/*
-			 * Upon a change to the cast from the FK column to its pfeqop
-			 * operand, revalidate the constraint.  For this evaluation, a
-			 * binary coercion cast is equivalent to no cast at all.  While
-			 * type implementors should design implicit casts with an eye
-			 * toward consistency of operations like equality, we cannot
-			 * assume here that they have done so.
-			 *
-			 * A function with a polymorphic argument could change behavior
-			 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
-			 * when the cast destination is polymorphic, we only avoid
-			 * revalidation if the input type has not changed at all.  Given
-			 * just the core data types and operator classes, this requirement
-			 * prevents no would-be optimizations.
-			 *
-			 * If the cast converts from a base type to a domain thereon, then
-			 * that domain type must be the opcintype of the unique index.
-			 * Necessarily, the primary key column must then be of the domain
-			 * type.  Since the constraint was previously valid, all values on
-			 * the foreign side necessarily exist on the primary side and in
-			 * turn conform to the domain.  Consequently, we need not treat
-			 * domains specially here.
-			 *
-			 * Since we require that all collations share the same notion of
-			 * equality (which they do, because texteq reduces to bitwise
-			 * equality), we don't compare collation here.
-			 *
-			 * We need not directly consider the PK type.  It's necessarily
-			 * binary coercible to the opcintype of the unique index column,
-			 * and ri_triggers.c will only deal with PK datums in terms of
-			 * that opcintype.  Changing the opcintype also changes pfeqop.
-			 */
-			old_check_ok = (new_pathtype == old_pathtype &&
-							new_castfunc == old_castfunc &&
-							(!IsPolymorphicType(pfeqop_right) ||
-							 new_fktype == old_fktype));
-		}
-
-		pfeqoperators[i] = pfeqop;
-		ppeqoperators[i] = ppeqop;
-		ffeqoperators[i] = ffeqop;
+		FindFKComparisonOperators(
+				fkconstraint, tab, i, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pktypoid[i], fktypoid[i], opclasses[i],
+				is_temporal, false,
+				&pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]);
 	}
+	if (is_temporal)
+	{
+		pkattnum[numpks] = pkperiodattnum[0];
+		fkattnum[numpks] = fkperiodattnum[0];
+		pktypoid[numpks] = pkperiodtypoid[0];
+		fktypoid[numpks] = fkperiodtypoid[0];
+
+		FindFKComparisonOperators(
+				fkconstraint, tab, numpks, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pkperiodtypoid[0], fkperiodtypoid[0], opclasses[numpks],
+				is_temporal, true,
+				&pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]);
+		numfks += 1;
+		numpks += 1;
+ 	}
 
 	/*
 	 * Create all the constraint and trigger objects, recursing to partitions
@@ -7981,7 +7860,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
-									 old_check_ok);
+									 old_check_ok,
+									 is_temporal);
 
 	/* Now handle the referencing side. */
 	addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
@@ -7994,6 +7874,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							ppeqoperators,
 							ffeqoperators,
 							old_check_ok,
+							is_temporal,
 							lockmode);
 
 	/*
@@ -8034,7 +7915,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 					   Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks,
 					   int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok,
+					   bool is_temporal)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -8110,12 +7992,13 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  fkconstraint->fk_upd_action,
 									  fkconstraint->fk_del_action,
 									  fkconstraint->fk_matchtype,
-									  NULL, /* no exclusion constraint */
+									  NULL,
 									  NULL, /* no check constraint */
 									  NULL,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
+									  is_temporal,
 									  false);	/* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -8191,7 +8074,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
-								   old_check_ok);
+								   old_check_ok, is_temporal);
 
 			/* Done -- clean up (but keep the lock) */
 			table_close(partRel, NoLock);
@@ -8240,7 +8123,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 						Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode)
+						bool old_check_ok, bool is_temporal, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
 
@@ -8386,6 +8269,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
+									  is_temporal,
 									  false);
 
 			/*
@@ -8412,6 +8296,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									ppeqoperators,
 									ffeqoperators,
 									old_check_ok,
+									is_temporal,
 									lockmode);
 
 			table_close(partition, NoLock);
@@ -8544,6 +8429,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 								   conpfeqop,
 								   conppeqop,
 								   conffeqop);
+
 		for (int i = 0; i < numfks; i++)
 			mapped_confkey[i] = attmap[confkey[i] - 1];
 
@@ -8589,7 +8475,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
-							   true);
+							   true,
+							   constrForm->contemporal);
 
 		table_close(fkRel, NoLock);
 		ReleaseSysCache(tuple);
@@ -8783,6 +8670,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
+								  constrForm->contemporal,
 								  true);
 
 		/* Set up partition dependencies for the new constraint */
@@ -8812,11 +8700,214 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conppeqop,
 								conffeqop,
 								false,	/* no old check exists */
+								constrForm->contemporal,
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
 	}
 }
 
+static void
+FindFKComparisonOperators(Constraint *fkconstraint,
+		AlteredTableInfo *tab,
+		int i,
+		int16 *fkattnum,
+		bool *old_check_ok,
+		ListCell **old_pfeqop_item,
+		Oid pktype, Oid fktype, Oid opclass,
+		bool is_temporal, bool for_overlaps,
+		Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut)
+{
+	Oid			fktyped;
+	HeapTuple	cla_ht;
+	Form_pg_opclass cla_tup;
+	Oid			amid;
+	Oid			opfamily;
+	Oid			opcintype;
+	Oid			pfeqop;
+	Oid			ppeqop;
+	Oid			ffeqop;
+	int16		eqstrategy;
+	Oid			pfeqop_right;
+
+	/* We need several fields out of the pg_opclass entry */
+	cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(cla_ht))
+		elog(ERROR, "cache lookup failed for opclass %u", opclass);
+	cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+	amid = cla_tup->opcmethod;
+	opfamily = cla_tup->opcfamily;
+	opcintype = cla_tup->opcintype;
+	ReleaseSysCache(cla_ht);
+
+	if (is_temporal)
+	{
+		if (amid != GIST_AM_OID)
+			elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
+		eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
+	}
+	else
+	{
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other types
+		 * of unique indexes, we'd need a way to determine which operator
+		 * strategy number is equality.  (Is it reasonable to insist that
+		 * every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+	}
+
+	/*
+	 * There had better be a primary equality operator for the index.
+	 * We'll use it for PK = PK comparisons.
+	 */
+	ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
+								 eqstrategy);
+
+	if (!OidIsValid(ppeqop))
+		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+			 eqstrategy, opcintype, opcintype, opfamily);
+
+	/*
+	 * Are there equality operators that take exactly the FK type? Assume
+	 * we should look through any domain here.
+	 */
+	fktyped = getBaseType(fktype);
+
+	pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+								 eqstrategy);
+	if (OidIsValid(pfeqop))
+	{
+		pfeqop_right = fktyped;
+		ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+									 eqstrategy);
+	}
+	else
+	{
+		/* keep compiler quiet */
+		pfeqop_right = InvalidOid;
+		ffeqop = InvalidOid;
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		/*
+		 * Otherwise, look for an implicit cast from the FK type to the
+		 * opcintype, and if found, use the primary equality operator.
+		 * This is a bit tricky because opcintype might be a polymorphic
+		 * type such as ANYARRAY or ANYENUM; so what we have to test is
+		 * whether the two actual column types can be concurrently cast to
+		 * that type.  (Otherwise, we'd fail to reject combinations such
+		 * as int[] and point[].)
+		 */
+		Oid			input_typeids[2];
+		Oid			target_typeids[2];
+
+		input_typeids[0] = pktype;
+		input_typeids[1] = fktype;
+		target_typeids[0] = opcintype;
+		target_typeids[1] = opcintype;
+		if (can_coerce_type(2, input_typeids, target_typeids,
+							COERCION_IMPLICIT))
+		{
+			pfeqop = ffeqop = ppeqop;
+			pfeqop_right = opcintype;
+		}
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("foreign key constraint \"%s\" cannot be implemented",
+						fkconstraint->conname),
+				 errdetail("Key columns \"%s\" and \"%s\" "
+						   "are of incompatible types: %s and %s.",
+						   strVal(list_nth(fkconstraint->fk_attrs, i)),
+						   strVal(list_nth(fkconstraint->pk_attrs, i)),
+						   format_type_be(fktype),
+						   format_type_be(pktype))));
+
+	if (*old_check_ok)
+	{
+		/*
+		 * When a pfeqop changes, revalidate the constraint.  We could
+		 * permit intra-opfamily changes, but that adds subtle complexity
+		 * without any concrete benefit for core types.  We need not
+		 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
+		 */
+		*old_check_ok = (pfeqop == lfirst_oid(*old_pfeqop_item));
+		*old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
+								*old_pfeqop_item);
+	}
+	if (*old_check_ok)
+	{
+		Oid			old_fktype;
+		Oid			new_fktype;
+		CoercionPathType old_pathtype;
+		CoercionPathType new_pathtype;
+		Oid			old_castfunc;
+		Oid			new_castfunc;
+		Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+											   fkattnum[i] - 1);
+
+		/*
+		 * Identify coercion pathways from each of the old and new FK-side
+		 * column types to the right (foreign) operand type of the pfeqop.
+		 * We may assume that pg_constraint.conkey is not changing.
+		 */
+		old_fktype = attr->atttypid;
+		new_fktype = fktype;
+		old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
+									&old_castfunc);
+		new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
+									&new_castfunc);
+
+		/*
+		 * Upon a change to the cast from the FK column to its pfeqop
+		 * operand, revalidate the constraint.  For this evaluation, a
+		 * binary coercion cast is equivalent to no cast at all.  While
+		 * type implementors should design implicit casts with an eye
+		 * toward consistency of operations like equality, we cannot
+		 * assume here that they have done so.
+		 *
+		 * A function with a polymorphic argument could change behavior
+		 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
+		 * when the cast destination is polymorphic, we only avoid
+		 * revalidation if the input type has not changed at all.  Given
+		 * just the core data types and operator classes, this requirement
+		 * prevents no would-be optimizations.
+		 *
+		 * If the cast converts from a base type to a domain thereon, then
+		 * that domain type must be the opcintype of the unique index.
+		 * Necessarily, the primary key column must then be of the domain
+		 * type.  Since the constraint was previously valid, all values on
+		 * the foreign side necessarily exist on the primary side and in
+		 * turn conform to the domain.  Consequently, we need not treat
+		 * domains specially here.
+		 *
+		 * Since we require that all collations share the same notion of
+		 * equality (which they do, because texteq reduces to bitwise
+		 * equality), we don't compare collation here.
+		 *
+		 * We need not directly consider the PK type.  It's necessarily
+		 * binary coercible to the opcintype of the unique index column,
+		 * and ri_triggers.c will only deal with PK datums in terms of
+		 * that opcintype.  Changing the opcintype also changes pfeqop.
+		 */
+		*old_check_ok = (new_pathtype == old_pathtype &&
+						new_castfunc == old_castfunc &&
+						(!IsPolymorphicType(pfeqop_right) ||
+						 new_fktype == old_fktype));
+
+	}
+
+	*pfeqopOut = pfeqop;
+	*ppeqopOut = ppeqop;
+	*ffeqopOut = ffeqop;
+}
+
 /*
  * When the parent of a partition receives [the referencing side of] a foreign
  * key, we must propagate that foreign key to the partition.  However, the
@@ -9197,7 +9288,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 con->oid);
+										 con->oid, con->contemporal);
 			table_close(refrel, NoLock);
 
 			/*
@@ -9330,10 +9421,12 @@ transformColumnNameList(Oid relId, List *colList,
  *
  *	Look up the names, attnums, and types of the primary key attributes
  *	for the pkrel.  Also return the index OID and index opclasses of the
- *	index supporting the primary key.
+ *	index supporting the primary key.  If this is a temporal primary key,
+ *	also set the WITHOUT OVERLAPS attribute name, attnum, and atttypid.
  *
  *	All parameters except pkrel are output parameters.  Also, the function
- *	return value is the number of attributes in the primary key.
+ *	return value is the number of attributes in the primary key,
+ *	not including the WITHOUT OVERLAPS if any.
  *
  *	Used when the column list in the REFERENCES specification is omitted.
  */
@@ -9341,6 +9434,8 @@ static int
 transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses)
 {
 	List	   *indexoidlist;
@@ -9408,35 +9503,50 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 	/*
 	 * Now build the list of PK attributes from the indkey definition (we
 	 * assume a primary key cannot have expressional elements)
+	 * TODO: range expressions will be how we support PERIODs though.
 	 */
 	*attnamelist = NIL;
 	for (i = 0; i < indexStruct->indnkeyatts; i++)
 	{
 		int			pkattno = indexStruct->indkey.values[i];
 
-		attnums[i] = pkattno;
-		atttypids[i] = attnumTypeId(pkrel, pkattno);
-		opclasses[i] = indclass->values[i];
-		*attnamelist = lappend(*attnamelist,
-							   makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		if (i == indexStruct->indnkeyatts - 1 && indexStruct->indisexclusion)
+		{
+			periodattnums[i] = pkattno;
+			periodatttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*periodattname = (Node *)makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))));
+		}
+		else
+		{
+			attnums[i] = pkattno;
+			atttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*attnamelist = lappend(*attnamelist,
+								  makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		}
 	}
 
 	ReleaseSysCache(indexTuple);
 
-	return i;
+	if (indexStruct->indisexclusion) return i - 1;
+	else return i;
 }
 
 /*
  * transformFkeyCheckAttrs -
  *
  *	Make sure that the attributes of a referenced table belong to a unique
- *	(or primary key) constraint.  Return the OID of the index supporting
- *	the constraint, as well as the opclasses associated with the index
+ *	(or primary key) constraint.  Or if this is a temporal foreign key
+ *	the primary key should be an exclusion constraint instead.
+ *	Return the OID of the index supporting the constraint,
+ *	as well as the opclasses associated with the index
  *	columns.
  */
 static Oid
 transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
 						Oid *opclasses) /* output parameter */
 {
 	Oid			indexoid = InvalidOid;
@@ -9463,6 +9573,10 @@ transformFkeyCheckAttrs(Relation pkrel,
 						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 						 errmsg("foreign key referenced-columns list must not contain duplicates")));
 		}
+		if (is_temporal && attnums[i] == periodattnums[0])
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+					 errmsg("foreign key referenced-columns list must not contain duplicates")));
 	}
 
 	/*
@@ -9484,12 +9598,16 @@ transformFkeyCheckAttrs(Relation pkrel,
 		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		/*
-		 * Must have the right number of columns; must be unique and not a
+		 * Must have the right number of columns; must be unique
+		 * (or if temporal then exclusion instead) and not a
 		 * partial index; forget it if there are any expressions, too. Invalid
 		 * indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
-			indexStruct->indisunique &&
+		if ((is_temporal
+			  ? (indexStruct->indnkeyatts == numattrs + 1 &&
+				 indexStruct->indisexclusion)
+			  : (indexStruct->indnkeyatts == numattrs &&
+				 indexStruct->indisunique)) &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
 			heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
@@ -9529,6 +9647,19 @@ transformFkeyCheckAttrs(Relation pkrel,
 				if (!found)
 					break;
 			}
+			if (is_temporal)
+			{
+				found = false;
+				for (j = 0; j < numattrs + 1; j++)
+				{
+					if (periodattnums[0] == indexStruct->indkey.values[j])
+					{
+						opclasses[numattrs] = indclass->values[j];
+						found = true;
+						break;
+					}
+				}
+			}
 
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
@@ -9718,7 +9849,8 @@ validateForeignKeyConstraint(char *conname,
 							 Relation rel,
 							 Relation pkrel,
 							 Oid pkindOid,
-							 Oid constraintOid)
+							 Oid constraintOid,
+							 bool temporal)
 {
 	TupleTableSlot *slot;
 	TableScanDesc scan;
@@ -9748,8 +9880,10 @@ validateForeignKeyConstraint(char *conname,
 	/*
 	 * See if we can do it with a single LEFT JOIN query.  A false result
 	 * indicates we must proceed with the fire-the-trigger method.
+	 * We can't do a LEFT JOIN for temporal FKs yet,
+	 * but we can once we support temporal left joins.
 	 */
-	if (RI_Initial_Check(&trig, rel, pkrel))
+	if (!temporal && RI_Initial_Check(&trig, rel, pkrel))
 		return;
 
 	/*
@@ -9811,6 +9945,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 					 Oid constraintOid, Oid indexOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
+	bool is_temporal = fkconstraint->fk_period;
 
 	/*
 	 * Note: for a self-referential FK (referencing and referenced tables are
@@ -9822,7 +9957,10 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = "RI_ConstraintTrigger_c";
+	if (is_temporal)
+		fk_trigger->trigname = "TRI_ConstraintTrigger_c";
+	else
+		fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
 	fk_trigger->timing = TRIGGER_TYPE_AFTER;
@@ -9830,12 +9968,18 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	/* Either ON INSERT or ON UPDATE */
 	if (on_insert)
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_ins");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
 		fk_trigger->events = TRIGGER_TYPE_INSERT;
 	}
 	else
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_upd");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
 		fk_trigger->events = TRIGGER_TYPE_UPDATE;
 	}
 
@@ -9881,6 +10025,13 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
+	
+	if ((fkconstraint->fk_period != NULL) &&
+			((fkconstraint->fk_del_action != FKCONSTR_ACTION_NOACTION) || 
+			(fkconstraint->fk_del_action != FKCONSTR_ACTION_RESTRICT)))
+		elog(ERROR, "unrecognized FK action type: %d",
+				(int) fkconstraint->fk_del_action);
+	
 	switch (fkconstraint->fk_del_action)
 	{
 		case FKCONSTR_ACTION_NOACTION:
@@ -9910,7 +10061,7 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 			break;
 		default:
 			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
+					 (int) fkconstraint->fk_del_action);
 			break;
 	}
 	fk_trigger->args = NIL;
@@ -9937,37 +10088,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_upd_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_upd");
+				break;
+			/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_upd");
+				break;
+			*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 2d9a8e9d54..2ff3d8b81b 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -776,6 +776,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* noinherit */
+											  false, /* contemporal */
 											  isInternal);	/* is_internal */
 	}
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 89887b8fd7..04f92fee95 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3174,6 +3174,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
+							  false,	/* contemporal */
 							  false);	/* is_internal */
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6414aded0e..15705b7653 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2921,12 +2921,15 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(where_clause);
 	COPY_NODE_FIELD(pktable);
 	COPY_NODE_FIELD(fk_attrs);
+	COPY_NODE_FIELD(fk_period);
 	COPY_NODE_FIELD(pk_attrs);
+	COPY_NODE_FIELD(pk_period);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index a9b2f8bacd..33b7b0866c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1962,7 +1962,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2351,6 +2351,148 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				notnullcmd->name = pstrdup(key);
 				notnullcmds = lappend(notnullcmds, notnullcmd);
 			}
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL)
+		{
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool       found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				AlterTableCmd *notnullcmd;
+				iparam->name = pstrdup(without_overlaps_str);	
+				iparam->expr = NULL;
+
+				/*
+				 * Force the column to NOT NULL since it is part of the primary key.
+				 */
+				notnullcmd = makeNode(AlterTableCmd);
+
+				notnullcmd->subtype = AT_SetNotNull;
+				notnullcmd->name = pstrdup(without_overlaps_str);
+				notnullcmds = lappend(notnullcmds, notnullcmd);
+			}
+			else 
+			{
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+			{
+				List *opname;
+				iparam->indexcolname = NULL;
+				iparam->collation = NIL;
+				iparam->opclass = NIL;
+				iparam->ordering = SORTBY_DEFAULT;
+				iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+				index->indexParams = lappend(index->indexParams, iparam);
+
+				opname = list_make1(makeString("&&"));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+				index->accessMethod = "gist";
+				constraint->access_method = "gist";
+			}
 		}
 	}
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 8c895459c3..9cf3c99d78 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -109,6 +109,7 @@ typedef struct RI_ConstraintInfo
 	char		confupdtype;	/* foreign key's ON UPDATE action */
 	char		confdeltype;	/* foreign key's ON DELETE action */
 	char		confmatchtype;	/* foreign key's match type */
+	bool		temporal;		/* if the foreign key is temporal */
 	int			nkeys;			/* number of key columns */
 	int16		pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
 	int16		fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
@@ -192,7 +193,7 @@ static int	ri_NullCheck(TupleDesc tupdesc, TupleTableSlot *slot,
 static void ri_BuildQueryKey(RI_QueryKey *key,
 							 const RI_ConstraintInfo *riinfo,
 							 int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+static bool ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
 							   Datum oldvalue, Datum newvalue);
@@ -353,18 +354,46 @@ RI_FKey_check(TriggerData *trigdata)
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
-		 *		   FOR KEY SHARE OF x
+		 *	SELECT 1
+		 *	FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *	FOR KEY SHARE OF x
 		 * The type id's for the $ parameters are those of the
 		 * corresponding FK attributes.
+		 *
+		 * But for temporal FKs we need to make sure
+		 * the FK's range is completely covered.
+		 * So we use this query instead:
+		 *  SELECT 1
+		 *  FROM (
+		 *	    SELECT	range_agg(r, true, true) AS r
+		 *	    FROM	(
+		 *			SELECT pkperiodatt AS r
+		 *			FROM   [ONLY] pktable x
+		 *			WHERE  pkatt1 = $1 [AND ...]
+		 *			FOR KEY SHARE OF x
+		 *		) x1
+		 *  ) x2
+		 *  WHERE $n <@ x2.r[1]
+		 * Note if FOR KEY SHARE ever allows aggregate functions
+		 * we can make this a bit simpler.
 		 * ----------
 		 */
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
 		quoteRelationName(pkrelname, pk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 pk_only, pkrelname);
+		if (riinfo->temporal)
+		{
+			quoteOneName(attname,
+					RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			appendStringInfo(&querybuf,
+					"SELECT 1 FROM (SELECT range_agg(r, true, true) AS r FROM (SELECT %s AS r FROM %s%s x",
+					attname, pk_only, pkrelname);
+		}
+		else {
+			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
+							 pk_only, pkrelname);
+		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -382,6 +411,8 @@ RI_FKey_check(TriggerData *trigdata)
 			queryoids[i] = fk_type;
 		}
 		appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
+		if (riinfo->temporal)
+			appendStringInfo(&querybuf, ") x1) x2 WHERE $%d <@ x2.r[1]", riinfo->nkeys);
 
 		/* Prepare and save the plan */
 		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -1176,7 +1207,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
 		return false;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
+	if (newslot && ri_KeysStable(pk_rel, oldslot, newslot, riinfo, true))
 		return false;
 
 	/* Else we need to fire the trigger. */
@@ -1269,13 +1300,135 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 		return true;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
+	if (ri_KeysStable(fk_rel, oldslot, newslot, riinfo, false))
 		return false;
 
 	/* Else we need to fire the trigger. */
 	return true;
 }
 
+/* ----------
+ * TRI_FKey_check_ins -
+ *
+ *	Check temporal foreign key existence at insert event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_ins(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+	/*
+	 * Share code with UPDATE case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_check_upd -
+ *
+ *	Check temporal foreign key existence at update event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_upd(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+	/*
+	 * Share code with INSERT case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_noaction_del -
+ *
+ *	Give an error and roll back the current transaction if the
+ *	delete has resulted in a violation of the given temporal
+ *	referential integrity constraint.
+ * ----------
+ */
+Datum
+TRI_FKey_noaction_del(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
+
+	/*
+	 * Share code with RESTRICT/UPDATE cases.
+	 */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_del -
+ *
+ * Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the delete is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with NO ACTION/UPDATE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+/*
+ * TRI_FKey_noaction_upd -
+ *
+ * Give an error and roll back the current transaction if the
+ * update has resulted in a violation of the given referential
+ * integrity constraint.
+ */
+Datum
+TRI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with RESTRICT/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_upd -
+ *
+ * Restrict update of PK to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the update is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with NO ACTION/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+
 /*
  * RI_Initial_Check -
  *
@@ -2051,6 +2204,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo->confupdtype = conForm->confupdtype;
 	riinfo->confdeltype = conForm->confdeltype;
 	riinfo->confmatchtype = conForm->confmatchtype;
+	riinfo->temporal = conForm->contemporal;
 
 	DeconstructFkConstraintRow(tup,
 							   &riinfo->nkeys,
@@ -2649,9 +2803,12 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 
 
 /*
- * ri_KeysEqual -
+ * ri_KeysStable -
  *
- * Check if all key values in OLD and NEW are equal.
+ * Check if all key values in OLD and NEW are "equivalent":
+ * For normal FKs we check for equality.
+ * For temporal FKs we check that the PK side is a superset of its old value,
+ * or the FK side is a subset.
  *
  * Note: at some point we might wish to redefine this as checking for
  * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
@@ -2659,7 +2816,7 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
  * previously found at least one of the rows to contain no nulls.
  */
 static bool
-ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -2692,29 +2849,43 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 
 		if (rel_is_pk)
 		{
-			/*
-			 * If we are looking at the PK table, then do a bytewise
-			 * comparison.  We must propagate PK changes if the value is
-			 * changed to one that "looks" different but would compare as
-			 * equal using the equality operator.  This only makes a
-			 * difference for ON UPDATE CASCADE, but for consistency we treat
-			 * all changes to the PK the same.
-			 */
-			Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, newvalue, oldvalue));
+			}
+			else
+			{
+				/*
+				 * If we are looking at the PK table, then do a bytewise
+				 * comparison.  We must propagate PK changes if the value is
+				 * changed to one that "looks" different but would compare as
+				 * equal using the equality operator.  This only makes a
+				 * difference for ON UPDATE CASCADE, but for consistency we treat
+				 * all changes to the PK the same.
+				 */
+				Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
 
-			if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
-				return false;
+				if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
+					return false;
+			}
 		}
 		else
 		{
-			/*
-			 * For the FK table, compare with the appropriate equality
-			 * operator.  Changes that compare equal will still satisfy the
-			 * constraint after the update.
-			 */
-			if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
-									oldvalue, newvalue))
-				return false;
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, oldvalue, newvalue));
+			}
+			else
+			{
+				/*
+				 * For the FK table, compare with the appropriate equality
+				 * operator.  Changes that compare equal will still satisfy the
+				 * constraint after the update.
+				 */
+				if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
+										oldvalue, newvalue))
+					return false;
+			}
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 0c58f1f109..473b0d1aba 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -317,7 +317,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 								   int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int	decompile_column_index_array(Datum column_index_array, Oid relId,
-										 StringInfo buf);
+										 bool withoutOverlaps, bool withPeriod, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									const Oid *excludeOps,
@@ -1985,6 +1985,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			{
 				Datum		val;
 				bool		isnull;
+				bool		hasperiod;
 				const char *string;
 
 				/* Start off the constraint definition */
@@ -1997,7 +1998,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it is a temporal foreign key
+				 * then it uses PERIOD.
+				 */
+				hasperiod = DatumGetBool(SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_contemporal, &isnull));
+				decompile_column_index_array(val, conForm->conrelid, false, hasperiod, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2011,7 +2018,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, hasperiod, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2112,7 +2119,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2314,7 +2327,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, bool withPeriod, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2332,9 +2345,21 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
+		else if (withPeriod && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", PERIOD %s", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7aa5d7c7fa..4d863d0715 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4949,8 +4949,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5016,7 +5017,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c1e60c7dfd..0baa22c0e5 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -103,6 +103,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	/* Has a local definition and cannot be inherited */
 	bool		connoinherit;
 
+	/*
+	 * For primary and foreign keys, signifies the last column is a range 
+	 * and should use overlaps instead of equals.
+	 */
+	bool		contemporal;
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 
 	/*
@@ -117,26 +123,26 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	int16		confkey[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * If a foreign key, the OIDs of the PK = FK comparison operators for each
 	 * column of the constraint
 	 */
 	Oid			conpfeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * If a foreign key, the OIDs of the PK = PK comparison operators for each
 	 * column of the constraint (i.e., equality for the referenced columns)
 	 */
 	Oid			conppeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * If a foreign key, the OIDs of the FK = FK comparison operators for each
 	 * column of the constraint (i.e., equality for the referencing columns)
 	 */
 	Oid			conffeqop[1];
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
@@ -211,6 +217,7 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conIsLocal,
 								  int conInhCount,
 								  bool conNoInherit,
+								  bool conTemporal,
 								  bool is_internal);
 
 extern void RemoveConstraintById(Oid conId);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0902dce5f1..1bca4e417e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3703,6 +3703,50 @@
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'RI_FKey_noaction_upd' },
 
+# Temporal referential integrity constraint triggers
+{ oid => '6122', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_ins' },
+{ oid => '6123', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_upd' },
+# { oid => '6124', descr => 'temporal referential integrity ON DELETE CASCADE',
+#   proname => 'TRI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_del' },
+# { oid => '6125', descr => 'temporal referential integrity ON UPDATE CASCADE',
+#   proname => 'TRI_FKey_cascade_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_upd' },
+{ oid => '6126', descr => 'temporal referential integrity ON DELETE RESTRICT',
+  proname => 'TRI_FKey_restrict_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_del' },
+{ oid => '6127', descr => 'temporal referential integrity ON UPDATE RESTRICT',
+  proname => 'TRI_FKey_restrict_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_upd' },
+# { oid => '6128', descr => 'temporal referential integrity ON DELETE SET NULL',
+#   proname => 'TRI_FKey_setnull_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_del' },
+# { oid => '6129', descr => 'temporal referential integrity ON UPDATE SET NULL',
+#   proname => 'TRI_FKey_setnull_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_upd' },
+# { oid => '6130', descr => 'temporal referential integrity ON DELETE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_del', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_del' },
+# { oid => '6131', descr => 'temporal referential integrity ON UPDATE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_upd', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_upd' },
+{ oid => '6132', descr => 'temporal referential integrity ON DELETE NO ACTION',
+  proname => 'TRI_FKey_noaction_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_del' },
+{ oid => '6133', descr => 'temporal referential integrity ON UPDATE NO ACTION',
+  proname => 'TRI_FKey_noaction_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_upd' },
+
 { oid => '1666',
   proname => 'varbiteq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'varbit varbit', prosrc => 'biteq' },
001_temporal_table_grammer_v006.patchapplication/octet-stream; name=001_temporal_table_grammer_v006.patchDownload
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..5eb49738c8 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2162,7 +2162,9 @@ typedef struct Constraint
 	/* Fields used for FOREIGN KEY constraints: */
 	RangeVar   *pktable;		/* Primary key table */
 	List	   *fk_attrs;		/* Attributes of foreign key */
+	Node	   *fk_period;		/* String node naming Period or range column */
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
+	Node	   *pk_period;		/* String node naming Period or range column */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
 	char		fk_del_action;	/* ON DELETE action */
@@ -2170,6 +2172,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+	Node	   *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..92628f390a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -299,6 +299,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, RESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c97bb367f8..f5ecbeb445 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -473,10 +473,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause optionalPeriodName
+				where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
+%type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
@@ -667,7 +669,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
 
@@ -3462,6 +3464,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3678,18 +3681,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3700,6 +3704,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3728,19 +3733,21 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
-				opt_column_list key_match key_actions ConstraintAttributeSpec
+			| FOREIGN KEY '(' columnList optionalPeriodName ')' REFERENCES qualified_name
+				opt_column_and_period_list key_match key_actions ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_FOREIGN;
 					n->location = @1;
-					n->pktable			= $7;
+					n->pktable			= $8;
 					n->fk_attrs			= $4;
-					n->pk_attrs			= $8;
-					n->fk_matchtype		= $9;
-					n->fk_upd_action	= (char) ($10 >> 8);
-					n->fk_del_action	= (char) ($10 & 0xFF);
-					processCASbits($11, @11, "FOREIGN KEY",
+					n->fk_period		= $5;
+					n->pk_attrs			= linitial($9);
+					n->pk_period		= lsecond($9);
+					n->fk_matchtype		= $10;
+					n->fk_upd_action	= (char) ($11 >> 8);
+					n->fk_del_action	= (char) ($11 & 0xFF);
+					processCASbits($12, @12, "FOREIGN KEY",
 								   &n->deferrable, &n->initdeferred,
 								   &n->skip_validation, NULL,
 								   yyscanner);
@@ -3763,6 +3770,21 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+optionalPeriodName:
+			',' PERIOD columnElem { $$ = $3; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+opt_column_and_period_list:
+			'(' columnList optionalPeriodName ')'			{ $$ = list_make2($2, $3); }
+			| /*EMPTY*/								{ $$ = list_make2(NIL, NULL); }
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
@@ -15473,6 +15495,7 @@ reserved_keyword:
 			| ONLY
 			| OR
 			| ORDER
+			| PERIOD
 			| PLACING
 			| PRIMARY
 			| REFERENCES
#20Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Ibrar Ahmed (#19)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Fri, Aug 2, 2019 at 1:49 PM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

I did some clean-up on this patch. I have also refactored a small portion of the code
to reduce the footprint of the patch. For simplicity, I have divided the patch into 6
patches, now it is easy to review and debug.
Please follow the PostgreSQL coding guidelines. I have found places where you missed that, secondly code even in WIP stage must not have WARNING because it looks ugly.

Thank you for the cleanup Ibrar! I'll try to stick to the coding
standards more closely going forward. If you have any review comments
I would certainly appreciate them, especially about the overall
approach. I know that the implementation in its current form is not
very tasteful, but I wanted to get some feedback on the ideas.

Also just to reiterate: this patch depends on my other CF entry
(range_agg), whose scope has expanded considerably. Right now I'm
focusing on that. And if you're trying to make this code work, it's
important to apply the range_agg patch first, since the temporal
foreign key implementation calls that function.

Also: since this patch raises the question of how to conform to
SQL:2011 while still supporting Postgres range types, I wrote an
article that surveys SQL:2011 temporal features in MariaDB, DB2,
Oracle, and MS SQL Server:

https://illuminatedcomputing.com/posts/2019/08/sql2011-survey/

A few highlights are:

- Everyone lets you define PERIODs, but what you can do with them is
still *very* limited.
- No one treats PERIODs as first-class types or expressions; they are
more like table metadata.
- Oracle PERIODs do permit NULL start/end values, and it interprets
them as "unbounded". That goes against the standard but since it's
what Postgres does with ranges, it suggests to me that maybe we should
follow their lead. Anyway I think a NULL is nicer than a sentinel for
this purpose.

Regards,
Paul

#21Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Paul A Jungwirth (#20)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Hi Paul,

On Mon, Aug 5, 2019 at 3:11 AM Paul A Jungwirth <pj@illuminatedcomputing.com>
wrote:

On Fri, Aug 2, 2019 at 1:49 PM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

I did some clean-up on this patch. I have also refactored a small

portion of the code

to reduce the footprint of the patch. For simplicity, I have divided the

patch into 6

patches, now it is easy to review and debug.
Please follow the PostgreSQL coding guidelines. I have found places

where you missed that, secondly code even in WIP stage must not have
WARNING because it looks ugly.

Thank you for the cleanup Ibrar! I'll try to stick to the coding
standards more closely going forward. If you have any review comments
I would certainly appreciate them, especially about the overall
approach. I know that the implementation in its current form is not
very tasteful, but I wanted to get some feedback on the ideas.

I have reviewed the main design, and in my opinion, it is a good start.

- Why we are not allowing any other datatype other than ranges in the
primary key. Without that there is no purpose of a primary key.

- Thinking about some special token to differentiate between normal
primary key and temporal primary key

Also just to reiterate: this patch depends on my other CF entry

(range_agg), whose scope has expanded considerably. Right now I'm
focusing on that. And if you're trying to make this code work, it's
important to apply the range_agg patch first, since the temporal
foreign key implementation calls that function.

Also: since this patch raises the question of how to conform to
SQL:2011 while still supporting Postgres range types, I wrote an
article that surveys SQL:2011 temporal features in MariaDB, DB2,
Oracle, and MS SQL Server:

https://illuminatedcomputing.com/posts/2019/08/sql2011-survey/

A few highlights are:

- Everyone lets you define PERIODs, but what you can do with them is
still *very* limited.
- No one treats PERIODs as first-class types or expressions; they are
more like table metadata.

- Oracle PERIODs do permit NULL start/end values, and it interprets
them as "unbounded". That goes against the standard but since it's
what Postgres does with ranges, it suggests to me that maybe we should
follow their lead. Anyway I think a NULL is nicer than a sentinel for
this purpose.

That is an open debate, that we want to strictly follow the standard or map
that
to PostgreSQL range type which allows NULL. But how you will define a
primary
key on that?

Regards,
Paul

--
Ibrar Ahmed

#22Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: Ibrar Ahmed (#21)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Hi Ibrar,

On 8/6/19 3:26 AM, Ibrar Ahmed wrote:

- Why we are not allowing any other datatype other than ranges in the
primary key. Without that there is no purpose of a primary key.

A temporal primary key always has at least one ordinary column (of any
type), so it is just a traditional primary key *plus* a PERIOD and/or
range column to indicate when the record was true.

- Thinking about some special token to differentiate between normal
primary key and temporal primary key

There is already some extra syntax. For the time part of a PK, you say
`WITHOUT OVERLAPS`, like this:

CONSTRAINT pk_on_t PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)

In this example `id` is an ordinary column, and `valid_at` is either a
Postgres range or a SQL:2011 PERIOD. (The latter is not yet implemented
in my patch but there are some placeholder comments.)

Similarly a foreign key has one or more traditional columns *plus* a
range/PERIOD. It needs to have a range/PERIOD on both sides. It too has
some special syntax, but instead of `WITHOUT OVERLAPS` it is `PERIOD`.
(Don't blame me, I didn't write the standard.... :-) So here is an example:

CONSTRAINT fk_t2_to_t FOREIGN KEY (id, PERIOD valid_at)
REFERENCES t (id, PERIOD valid_at)

You should be able to see my changes to gram.y to support this new syntax.

I hope this clears up how it works! I'm happy to answer more questions
if you have any. Also if you want to read more:

- This paper by Kulkarni & Michels is a 10-page overview of SQL:2011:

https://sigmodrecord.org/publications/sigmodRecord/1209/pdfs/07.industry.kulkarni.pdf

- This is a talk I gave at PGCon 2019 going over the concepts, with a
lot of pictures. You can find text, slides, and a link to the video here:

https://github.com/pjungwir/postgres-temporal-talk

- This link is ostensibly an annotated bibliography but really tells a
story about how the research has developed:

https://illuminatedcomputing.com/posts/2017/12/temporal-databases-bibliography/

- There is also some discussion about PERIODs vs ranges upthread here,
as well as here:

https://www.postgresql-archive.org/Periods-td6022563.html

Yours,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#23Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Paul Jungwirth (#22)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Tue, Aug 6, 2019 at 8:28 PM Paul Jungwirth <pj@illuminatedcomputing.com>
wrote:

Hi Ibrar,

On 8/6/19 3:26 AM, Ibrar Ahmed wrote:

- Why we are not allowing any other datatype other than ranges in the
primary key. Without that there is no purpose of a primary key.

A temporal primary key always has at least one ordinary column (of any
type), so it is just a traditional primary key *plus* a PERIOD and/or
range column to indicate when the record was true.

- Thinking about some special token to differentiate between normal
primary key and temporal primary key

There is already some extra syntax. For the time part of a PK, you say
`WITHOUT OVERLAPS`, like this:

CONSTRAINT pk_on_t PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)

In this example `id` is an ordinary column, and `valid_at` is either a
Postgres range or a SQL:2011 PERIOD. (The latter is not yet implemented
in my patch but there are some placeholder comments.)

Similarly a foreign key has one or more traditional columns *plus* a
range/PERIOD. It needs to have a range/PERIOD on both sides. It too has
some special syntax, but instead of `WITHOUT OVERLAPS` it is `PERIOD`.
(Don't blame me, I didn't write the standard.... :-) So here is an example:

CONSTRAINT fk_t2_to_t FOREIGN KEY (id, PERIOD valid_at)
REFERENCES t (id, PERIOD valid_at)

You should be able to see my changes to gram.y to support this new syntax.

I hope this clears up how it works! I'm happy to answer more questions
if you have any. Also if you want to read more:

- This paper by Kulkarni & Michels is a 10-page overview of SQL:2011:

https://sigmodrecord.org/publications/sigmodRecord/1209/pdfs/07.industry.kulkarni.pdf

- This is a talk I gave at PGCon 2019 going over the concepts, with a
lot of pictures. You can find text, slides, and a link to the video here:

https://github.com/pjungwir/postgres-temporal-talk

- This link is ostensibly an annotated bibliography but really tells a
story about how the research has developed:

https://illuminatedcomputing.com/posts/2017/12/temporal-databases-bibliography/

- There is also some discussion about PERIODs vs ranges upthread here,
as well as here:

https://www.postgresql-archive.org/Periods-td6022563.html

Thanks, Paul for the explanation. I think its good start, now I am looking
at the
range_agg patch to integrate that with that and test that.

Yours,

--
Paul ~{:-)
pj@illuminatedcomputing.com

--
Ibrar Ahmed

#24Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Ibrar Ahmed (#23)
2 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Tue, Aug 6, 2019 at 11:07 AM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

Thanks, Paul for the explanation. I think its good start, now I am looking at the
range_agg patch to integrate that with that and test that.

Since we've started another commitfest, here is an updated version of
this patch. I've rebased it on the latest multirange patch (which is
rebased on the latest master). I've incorporated your feedback. I've
also added some progress on adding FOR PORTION OF to UPDATE and DELETE
(mostly UPDATE). That is even more WIP than the PK/FK work, because
I'm still working on the executor phase, so feel free to ignore it or
offer feedback. I've put the DML work in a separate patch file. (I'm
planning to roughly follow ON CONFLICT DO UPDATE for the ModifyTable
changes, since temporal DML is also an extra clause that transforms
your commands into something else. For example a temporal UPDATE could
become an UPDATE plus two INSERTs. I'm probably going to need some
help eventually getting the concurrency stuff right here though.) (I
think temporal DML makes sense to include in this patch because it is
required for cascading FKs. I think once everything is working I'll
give you a patch series that goes PKs - DML - FKs. Or even better
PERIODs - PKs - DML - FKs.)

Thanks!
Paul

Attachments:

v2-0002-temporal-update.patchapplication/octet-stream; name=v2-0002-temporal-update.patchDownload
From 7e80e3cb3486187a712e6235209c4aabb9a8a6ab Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Fri, 25 Oct 2019 20:16:50 -0700
Subject: [PATCH v2 2/2] Added FOR PORTION OF to gram.y

FOR PORTION OF: parse analysis start

Set contemporal for temporal PKs not just FKs

Add a few validations for the column in FOR PORTION OF

FOR PORTION OF: slightly more precise error message

FOR PORTION OF: filter rows based on FROM ... TO

Add forPortionOf to ModifyTablePath
---
 src/backend/catalog/index.c                  |   6 +-
 src/backend/commands/indexcmds.c             |   2 +
 src/backend/nodes/copyfuncs.c                |   1 +
 src/backend/optimizer/plan/planner.c         |   2 +
 src/backend/optimizer/util/pathnode.c        |   3 +-
 src/backend/parser/analyze.c                 | 162 ++++++++++++++++++++++++++-
 src/backend/parser/gram.y                    |  37 ++++--
 src/backend/parser/parse_expr.c              |   2 +
 src/backend/parser/parse_utilcmd.c           |   2 +
 src/backend/utils/cache/lsyscache.c          |  26 +++++
 src/backend/utils/cache/relcache.c           |  10 +-
 src/include/catalog/index.h                  |   1 +
 src/include/nodes/nodes.h                    |   2 +
 src/include/nodes/parsenodes.h               |  44 +++++---
 src/include/nodes/pathnodes.h                |   1 +
 src/include/nodes/plannodes.h                |   1 +
 src/include/nodes/primnodes.h                |  19 ++++
 src/include/optimizer/pathnode.h             |   2 +-
 src/include/parser/kwlist.h                  |   1 +
 src/include/parser/parse_node.h              |   1 +
 src/include/utils/lsyscache.h                |   1 +
 src/test/regress/expected/for_portion_of.out |   0
 src/test/regress/parallel_schedule           |   4 +-
 src/test/regress/serial_schedule             |   1 +
 src/test/regress/sql/for_portion_of.sql      |  16 +++
 25 files changed, 317 insertions(+), 30 deletions(-)
 create mode 100644 src/test/regress/expected/for_portion_of.out
 create mode 100644 src/test/regress/sql/for_portion_of.sql

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 6c055012d7..557c5e92b1 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1782,6 +1782,7 @@ index_concurrently_set_dead(Oid heapId, Oid indexId)
  *		INDEX_CONSTR_CREATE_UPDATE_INDEX: update the pg_index row
  *		INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS: remove existing dependencies
  *			of index on table's columns
+ *		INDEX_CONSTR_CREATE_TEMPORAL: constraint is for a temporal primary key
  * allow_system_table_mods: allow table to be a system catalog
  * is_internal: index is constructed due to internal process
  */
@@ -1805,11 +1806,13 @@ index_constraint_create(Relation heapRelation,
 	bool		mark_as_primary;
 	bool		islocal;
 	bool		noinherit;
+	bool		is_temporal;
 	int			inhcount;
 
 	deferrable = (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) != 0;
 	initdeferred = (constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) != 0;
 	mark_as_primary = (constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY) != 0;
+	is_temporal = (constr_flags & INDEX_CONSTR_CREATE_TEMPORAL) != 0;
 
 	/* constraint creation support doesn't work while bootstrapping */
 	Assert(!IsBootstrapProcessingMode());
@@ -1884,8 +1887,7 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
-									 // TODO: set this for PKs too
-								   false,	/* contemporal */
+								   is_temporal,	/* contemporal */
 								   is_internal);
 
 	/*
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 374e2d0efe..57e8f39e99 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -982,6 +982,8 @@ DefineIndex(Oid relationId,
 		constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;
 	if (stmt->initdeferred)
 		constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;
+	if (stmt->istemporal)
+		constr_flags |= INDEX_CONSTR_CREATE_TEMPORAL;
 
 	indexRelationId =
 		index_create(rel, indexRelationName, indexRelationId, parentIndexId,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index d629aa2339..b0fee3ac4e 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3475,6 +3475,7 @@ _copyIndexStmt(const IndexStmt *from)
 	COPY_SCALAR_FIELD(unique);
 	COPY_SCALAR_FIELD(primary);
 	COPY_SCALAR_FIELD(isconstraint);
+	COPY_SCALAR_FIELD(istemporal);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
 	COPY_SCALAR_FIELD(transformed);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 17c5f086fb..a6e011b720 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1794,6 +1794,7 @@ inheritance_planner(PlannerInfo *root)
 									 returningLists,
 									 rowMarks,
 									 NULL,
+									 parse->forPortionOf,
 									 assign_special_exec_param(root)));
 }
 
@@ -2377,6 +2378,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										returningLists,
 										rowMarks,
 										parse->onConflict,
+										parse->forPortionOf,
 										assign_special_exec_param(root));
 		}
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 34acb732ee..5e8a590d1d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3449,7 +3449,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						List *subroots,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
-						int epqParam)
+						ForPortionOfExpr *forPortionOf, int epqParam)
 {
 	ModifyTablePath *pathnode = makeNode(ModifyTablePath);
 	double		total_size;
@@ -3520,6 +3520,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
 	pathnode->onconflict = onconflict;
+	pathnode->forPortionOf = forPortionOf;
 	pathnode->epqParam = epqParam;
 
 	return pathnode;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 85d7a96406..8c5c785487 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -42,9 +42,12 @@
 #include "parser/parse_param.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
+#include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -58,6 +61,9 @@ static List *transformInsertRow(ParseState *pstate, List *exprlist,
 								bool strip_indirection);
 static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
 												 OnConflictClause *onConflictClause);
+static ForPortionOfExpr *transformForPortionOfClause(ParseState *pstate,
+													 int rtindex,
+													 ForPortionOfClause *forPortionOfClause);
 static int	count_rowexpr_columns(ParseState *pstate, Node *expr);
 static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
 static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
@@ -1074,6 +1080,149 @@ transformOnConflictClause(ParseState *pstate,
 	return result;
 }
 
+/*
+ * transformForPortionOfClause
+ *	  transforms a ForPortionOfClause in an UPDATE/DELETE statement
+ */
+static ForPortionOfExpr *
+transformForPortionOfClause(ParseState *pstate,
+							int rtindex,
+							ForPortionOfClause *forPortionOf)
+{
+	Relation targetrel = pstate->p_target_relation;
+	RangeTblEntry *target_rte = pstate->p_target_rangetblentry;
+	char *range_name = forPortionOf->range_name;
+	char *range_type_name;
+	int	range_attno;
+	ForPortionOfExpr *result;
+
+	result = makeNode(ForPortionOfExpr);
+
+	/*
+	 * First look for a range column, then look for a period.
+	 */
+	range_attno = attnameAttNum(targetrel, range_name, true);
+	if (range_attno != InvalidAttrNumber)
+	{
+		Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, range_attno - 1);
+
+		// TODO: check attr->attisdropped ?
+
+		/* Make sure it's a range column */
+		if (!type_is_range(attr->atttypid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("column \"%s\" of relation \"%s\" is not a range type",
+							range_name,
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+		/* Make sure the table has a primary key */
+		Oid pkoid = RelationGetPrimaryKeyIndex(targetrel);
+		if (pkoid == InvalidOid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("relation \"%s\" does not have a temporal primary key",
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+		/* Make sure the primary key is a temporal key */
+		// TODO: need a lock here?
+		HeapTuple indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(pkoid));
+		if (!HeapTupleIsValid(indexTuple))	/* should not happen */
+			elog(ERROR, "cache lookup failed for index %u", pkoid);
+		Form_pg_index pk = (Form_pg_index) GETSTRUCT(indexTuple);
+		ReleaseSysCache(indexTuple);
+
+		/*
+		 * Only temporal pkey indexes have both isprimary and isexclusion.
+		 * Checking those saves us from scanning pg_constraint
+		 * like in RelationGetExclusionInfo.
+		 */
+		if (!(pk->indisprimary && pk->indisexclusion))
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("relation \"%s\" does not have a temporal primary key",
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+		}
+
+		/* Make sure the range attribute is the last part of the pkey. */
+		if (range_attno != pk->indkey.values[pk->indnkeyatts - 1])
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("column \"%s\" is not the temporal part of the primary key for relation \"%s\"",
+							range_name,
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+		}
+
+		Var *v = makeVar(
+				rtindex,
+				range_attno,		// TODO: 0-indexed or 1-indexed?
+				attr->atttypid,
+				attr->atttypmod,
+				attr->attcollation,
+				0);
+		v->location = forPortionOf->range_name_location;
+		result->range = (Expr *) v;
+		range_type_name = get_typname(attr->atttypid);
+	} else {
+		// TODO: Try to find a period,
+		// and set result->range to an Expr like tsrange(period->start_col, period->end_col)
+		// Probably we can make an A_Expr and call transformExpr on it, right?
+
+		/*
+		 * We need to choose a range type based on the period's columns' type.
+		 * Normally inferring a range type from an element type is not allowed,
+		 * because there might be more than one.
+		 * In this case SQL:2011 only has periods for timestamp, timestamptz, and date,
+		 * which all have built-in range types.
+		 * Let's just take the first range we have for that type,
+		 * ordering by oid, so that we get built-in range types first.
+		 */
+
+		// TODO: set result->range
+		// TODO: set range_type_name
+	}
+
+	if (range_attno == InvalidAttrNumber)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("column or period \"%s\" of relation \"%s\" does not exist",
+						range_name,
+						RelationGetRelationName(pstate->p_target_relation)),
+				 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+	/*
+	 * targetStart and End are literal strings
+	 * that we'll coerce to the range's element type later.
+	 */
+	result->targetStart = forPortionOf->target_start;
+	result->targetEnd = forPortionOf->target_end;
+
+	FuncCall *fc = makeFuncCall(SystemFuncName(range_type_name),
+								list_make2(forPortionOf->target_start,
+										   forPortionOf->target_end),
+								// TODO: FROM...TO... location instead?:
+								forPortionOf->range_name_location);
+	result->targetRange = (Node *) fc;
+
+	/* overlapsExpr is something we can add to the whereClause */
+	result->overlapsExpr = (Node *) makeSimpleA_Expr(AEXPR_OP, "&&",
+			// TODO: Maybe need a copy here?:
+			(Node *) result->range, result->targetRange,
+			forPortionOf->range_name_location);
+
+	// TODO: if it's a period, mark its start/end columns instead:
+	/* Mark the range column as requiring update permissions */
+	target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
+											 range_attno - FirstLowInvalidHeapAttributeNumber);
+
+	return result;
+}
 
 /*
  * BuildOnConflictExcludedTargetlist
@@ -2219,6 +2368,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
 	ParseNamespaceItem *nsitem;
+	Node	   *whereClause;
 	Node	   *qual;
 
 	qry->commandType = CMD_UPDATE;
@@ -2237,6 +2387,9 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 										 true,
 										 ACL_UPDATE);
 
+	if (stmt->forPortionOf)
+		qry->forPortionOf = transformForPortionOfClause(pstate, qry->resultRelation, stmt->forPortionOf);
+
 	/* grab the namespace item made by setTargetTable */
 	nsitem = (ParseNamespaceItem *) llast(pstate->p_namespace);
 
@@ -2254,7 +2407,12 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	nsitem->p_lateral_only = false;
 	nsitem->p_lateral_ok = true;
 
-	qual = transformWhereClause(pstate, stmt->whereClause,
+	if (stmt->forPortionOf)
+		// TODO: Is this messing up cnf? Or does that come later?:
+		whereClause = (Node *) makeBoolExpr(AND_EXPR, list_make2(qry->forPortionOf->overlapsExpr, stmt->whereClause), -1);
+	else
+		whereClause = stmt->whereClause;
+	qual = transformWhereClause(pstate, whereClause,
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -2332,6 +2490,8 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							RelationGetRelationName(pstate->p_target_relation)),
 					 parser_errposition(pstate, origTarget->location)));
 
+		// TODO: If the column is also used in FOR PORTION OF, forbid SETing it:
+
 		updateTargetListEntry(pstate, tle, origTarget->name,
 							  attrno,
 							  origTarget->indirection,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 081757d841..520ed0d500 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RangeVar			*range;
 	IntoClause			*into;
 	WithClause			*with;
+	ForPortionOfClause	*forportionof;
 	InferClause			*infer;
 	OnConflictClause	*onconflict;
 	A_Indices			*aind;
@@ -498,6 +499,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <jexpr>	joined_table
 %type <range>	relation_expr
 %type <range>	relation_expr_opt_alias
+%type <forportionof> for_portion_of_clause
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
 
@@ -670,7 +672,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
 	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PERIOD PLACING PLANS POLICY
-	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PORTION POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
 	QUOTE
@@ -11038,13 +11040,15 @@ returning_clause:
  *****************************************************************************/
 
 DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
+			for_portion_of_clause
 			using_clause where_or_current_clause returning_clause
 				{
 					DeleteStmt *n = makeNode(DeleteStmt);
 					n->relation = $4;
-					n->usingClause = $5;
-					n->whereClause = $6;
-					n->returningList = $7;
+					n->forPortionOf = $5;
+					n->usingClause = $6;
+					n->whereClause = $7;
+					n->returningList = $8;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
@@ -11107,6 +11111,7 @@ opt_nowait_or_skip:
  *****************************************************************************/
 
 UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
+			for_portion_of_clause
 			SET set_clause_list
 			from_clause
 			where_or_current_clause
@@ -11114,10 +11119,11 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
 				{
 					UpdateStmt *n = makeNode(UpdateStmt);
 					n->relation = $3;
-					n->targetList = $5;
-					n->fromClause = $6;
-					n->whereClause = $7;
-					n->returningList = $8;
+					n->forPortionOf = $4;
+					n->targetList = $6;
+					n->fromClause = $7;
+					n->whereClause = $8;
+					n->returningList = $9;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
@@ -12222,6 +12228,20 @@ relation_expr_opt_alias: relation_expr					%prec UMINUS
 				}
 		;
 
+for_portion_of_clause:
+			FOR PORTION OF ColId FROM Sconst TO Sconst
+				{
+					ForPortionOfClause *n = makeNode(ForPortionOfClause);
+					n->range_name = $4;
+					n->range_name_location = @4;
+					n->target_start = makeStringConst($6, @6);
+					n->target_end = makeStringConst($8, @8);
+					$$ = n;
+				}
+			| /*EMPTY*/					{ $$ = NULL; }
+		;
+
+
 /*
  * TABLESAMPLE decoration in a FROM item
  */
@@ -15245,6 +15265,7 @@ unreserved_keyword:
 			| PASSWORD
 			| PLANS
 			| POLICY
+			| PORTION
 			| PRECEDING
 			| PREPARE
 			| PREPARED
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 76f3dd7076..54bf2aaf46 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -3520,6 +3520,8 @@ ParseExprKindName(ParseExprKind exprKind)
 		case EXPR_KIND_UPDATE_SOURCE:
 		case EXPR_KIND_UPDATE_TARGET:
 			return "UPDATE";
+		case EXPR_KIND_UPDATE_PORTION:
+			return "FOR PORTION OF";
 		case EXPR_KIND_GROUP_BY:
 			return "GROUP BY";
 		case EXPR_KIND_ORDER_BY:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 50a5a36e41..d5d6166964 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1403,6 +1403,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
 	index->oldNode = InvalidOid;
 	index->unique = idxrec->indisunique;
 	index->primary = idxrec->indisprimary;
+	index->istemporal = idxrec->indisprimary && idxrec->indisexclusion;
 	index->transformed = true;	/* don't need transformIndexStmt */
 	index->concurrent = false;
 	index->if_not_exists = false;
@@ -1980,6 +1981,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 		 */
 	}
 	index->isconstraint = true;
+	index->istemporal = constraint->without_overlaps != NULL;
 	index->deferrable = constraint->deferrable;
 	index->initdeferred = constraint->initdeferred;
 
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 360a71427e..3fd7d79cdb 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1967,6 +1967,32 @@ get_typisdefined(Oid typid)
 }
 
 /*
+ * get_typname
+ *
+ *		Returns the name of a given type
+ *
+ * Returns a palloc'd copy of the string, or NULL if no such type.
+ */
+char *
+get_typname(Oid typid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
+		char	   *result;
+
+		result = pstrdup(NameStr(typtup->typname));
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return NULL;
+}
+
+/*
  * get_typlen
  *
  *		Given the type OID, return the length of the type.
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index af1d91fbc9..c05620a83b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4401,11 +4401,17 @@ RelationGetIndexList(Relation relation)
 		 * interesting for either oid indexes or replication identity indexes,
 		 * so don't check them.
 		 */
-		if (!index->indisvalid || !index->indisunique ||
-			!index->indimmediate ||
+		if (!index->indisvalid || !index->indimmediate ||
 			!heap_attisnull(htup, Anum_pg_index_indpred, NULL))
 			continue;
 
+		/*
+		 * Non-unique indexes aren't interesting either,
+		 * except when they are temporal primary keys.
+		 */
+		if (!index->indisunique && !index->indisprimary)
+			continue;
+
 		/* remember primary key index if any */
 		if (index->indisprimary)
 			pkeyIndex = index->indexrelid;
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 1113d25b2d..b3a116e412 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -77,6 +77,7 @@ extern Oid	index_create(Relation heapRelation,
 #define	INDEX_CONSTR_CREATE_INIT_DEFERRED	(1 << 2)
 #define	INDEX_CONSTR_CREATE_UPDATE_INDEX	(1 << 3)
 #define	INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS	(1 << 4)
+#define	INDEX_CONSTR_CREATE_TEMPORAL		(1 << 5)
 
 extern Oid	index_concurrently_create_copy(Relation heapRelation,
 										   Oid oldIndexId,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index bce2d59b0d..3cf1916c7a 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -195,6 +195,7 @@ typedef enum NodeTag
 	T_JoinExpr,
 	T_FromExpr,
 	T_OnConflictExpr,
+	T_ForPortionOfExpr,
 	T_IntoClause,
 
 	/*
@@ -468,6 +469,7 @@ typedef enum NodeTag
 	T_WithClause,
 	T_InferClause,
 	T_OnConflictClause,
+	T_ForPortionOfClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index aa7da0c79f..832f923109 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -122,6 +122,8 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
 
+	ForPortionOfExpr *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
+
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
@@ -1363,6 +1365,19 @@ typedef struct RowMarkClause
 } RowMarkClause;
 
 /*
+ * ForPortionOfClause
+ *		representation of FOR PORTION OF <period-name> FROM <t1> TO <t2>
+ */
+typedef struct ForPortionOfClause
+{
+	NodeTag		type;
+	char	   *range_name;
+	int			range_name_location;
+	Node	   *target_start;
+	Node	   *target_end;
+} ForPortionOfClause;
+
+/*
  * WithClause -
  *	   representation of WITH clause
  *
@@ -1517,12 +1532,13 @@ typedef struct InsertStmt
  */
 typedef struct DeleteStmt
 {
-	NodeTag		type;
-	RangeVar   *relation;		/* relation to delete from */
-	List	   *usingClause;	/* optional using clause for more tables */
-	Node	   *whereClause;	/* qualifications */
-	List	   *returningList;	/* list of expressions to return */
-	WithClause *withClause;		/* WITH clause */
+	NodeTag				type;
+	RangeVar		   *relation;		/* relation to delete from */
+	ForPortionOfClause *forPortionOf;	/* FOR PORTION OF clause */
+	List			   *usingClause;	/* optional using clause for more tables */
+	Node			   *whereClause;	/* qualifications */
+	List			   *returningList;	/* list of expressions to return */
+	WithClause		   *withClause;		/* WITH clause */
 } DeleteStmt;
 
 /* ----------------------
@@ -1531,13 +1547,14 @@ typedef struct DeleteStmt
  */
 typedef struct UpdateStmt
 {
-	NodeTag		type;
-	RangeVar   *relation;		/* relation to update */
-	List	   *targetList;		/* the target list (of ResTarget) */
-	Node	   *whereClause;	/* qualifications */
-	List	   *fromClause;		/* optional from clause for more tables */
-	List	   *returningList;	/* list of expressions to return */
-	WithClause *withClause;		/* WITH clause */
+	NodeTag				type;
+	RangeVar		   *relation;		/* relation to update */
+	ForPortionOfClause *forPortionOf;	/* FOR PORTION OF clause */
+	List			   *targetList;		/* the target list (of ResTarget) */
+	Node			   *whereClause;	/* qualifications */
+	List			   *fromClause;		/* optional from clause for more tables */
+	List			   *returningList;	/* list of expressions to return */
+	WithClause		   *withClause;		/* WITH clause */
 } UpdateStmt;
 
 /* ----------------------
@@ -2770,6 +2787,7 @@ typedef struct IndexStmt
 	bool		unique;			/* is index unique? */
 	bool		primary;		/* is index a primary key? */
 	bool		isconstraint;	/* is it for a pkey/unique constraint? */
+	bool		istemporal;		/* is it for a temporal pkey? */
 	bool		deferrable;		/* is the constraint DEFERRABLE? */
 	bool		initdeferred;	/* is the constraint INITIALLY DEFERRED? */
 	bool		transformed;	/* true when transformIndexStmt is finished */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 23a06d718e..75191d1bb8 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1781,6 +1781,7 @@ typedef struct ModifyTablePath
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
+	ForPortionOfExpr *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 } ModifyTablePath;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 8e6594e355..8f94519582 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -232,6 +232,7 @@ typedef struct ModifyTable
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
+	ForPortionOfExpr *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
 	List	   *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs  */
 	List	   *onConflictSet;	/* SET for INSERT ON CONFLICT DO UPDATE */
 	Node	   *onConflictWhere;	/* WHERE for ON CONFLICT UPDATE */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 860a84de7c..d6f3c40db9 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1524,4 +1524,23 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * ForPortionOfExpr - represents a FOR PORTION OF ... expression
+ *
+ * TODO: more notes as needed
+ *----------
+ */
+typedef struct ForPortionOfExpr
+{
+	NodeTag		type;
+	// TODO: either a Var or a FuncExpr:
+	Expr	   *range;			/* Range column or expression */
+	Node	   *startCol;		/* Start column if using a PERIOD */
+	Node	   *endCol;			/* End column if using a PERIOD */
+	Node	   *targetStart;	/* Same type as the range's elements */
+	Node	   *targetEnd;		/* Same type as the range's elements */
+	Node	   *targetRange;	/* A range from targetStart to targetEnd */
+	Node	   *overlapsExpr;	/* range && targetRange */
+} ForPortionOfExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index a12af54971..c5bdd3f3f8 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -258,7 +258,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												List *subroots,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
-												int epqParam);
+												ForPortionOfExpr *forPortionOf, int epqParam);
 extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
 									Path *subpath,
 									Node *limitOffset, Node *limitCount,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 92628f390a..fb05dc47ca 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -303,6 +303,7 @@ PG_KEYWORD("period", PERIOD, RESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
+PG_KEYWORD("portion", PORTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 7c099e7084..ea153b678f 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -50,6 +50,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_INSERT_TARGET,	/* INSERT target list item */
 	EXPR_KIND_UPDATE_SOURCE,	/* UPDATE assignment source item */
 	EXPR_KIND_UPDATE_TARGET,	/* UPDATE assignment target item */
+	EXPR_KIND_UPDATE_PORTION,	/* UPDATE FOR PORTION OF item */
 	EXPR_KIND_GROUP_BY,			/* GROUP BY */
 	EXPR_KIND_ORDER_BY,			/* ORDER BY */
 	EXPR_KIND_DISTINCT_ON,		/* DISTINCT ON */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index ffdd90d69c..eafa21b6fc 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -134,6 +134,7 @@ extern char get_rel_persistence(Oid relid);
 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 int16 get_typlen(Oid typid);
 extern bool get_typbyval(Oid typid);
 extern void get_typlenbyval(Oid typid, int16 *typlen, bool *typbyval);
diff --git a/src/test/regress/expected/for_portion_of.out b/src/test/regress/expected/for_portion_of.out
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index b3e72673bf..17934bed2f 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -108,12 +108,12 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml without_overlaps
 
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info without_overlaps
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info for_portion_of
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index dbcd919a84..78ab872bc8 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -21,6 +21,7 @@ test: money
 test: rangetypes
 test: multirangetypes
 test: without_overlaps
+test: for_portion_of
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/for_portion_of.sql b/src/test/regress/sql/for_portion_of.sql
new file mode 100644
index 0000000000..b18f7377c8
--- /dev/null
+++ b/src/test/regress/sql/for_portion_of.sql
@@ -0,0 +1,16 @@
+-- Tests for UPDATE/DELETE FOR PORTION OF
+
+--
+-- test input parser
+--
+
+-- tables are without_overlaps_test
+SELECT * FROM without_overlaps_test;
+
+explain analyze UPDATE without_overlaps_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO 'infinity'
+-- too add a non PK column so we have something to update:
+SET id = '[5,6)'
+WHERE id = '[3,4)';
+
+SELECT * FROM without_overlaps_test;
-- 
2.11.0

v2-0001-temporal-pks-and-fks.patchapplication/octet-stream; name=v2-0001-temporal-pks-and-fks.patchDownload
From 784c5ea030173f2e956939d8dce5028fe33f7c60 Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Mon, 19 Nov 2018 20:11:33 -0800
Subject: [PATCH v2 1/2] Added WITHOUT OVERLAPS to bison grammar for PKs

Progress defining an exclusion index behind the temporal PK constraint:

- Create table tests pass.
- Inserts fail: I think Postgres is confused how a PK can look like an
  exclusion constraint.
- No PERIOD support yet since they don't exist.

Another WITHOUT OVERLAPS + range test

Teach pg_get_constraintdef about WITH OVERLAPS in primary keys

Let exclusion indexes find a PK WITHOUT OVERLAPS so INSERTs work

Fix sanity_check test that sees without_overlaps_test

Added test for dropping a WITHOUT OVERLAPS PK

pg_dump sql: PK WITHOUT OVERLAPS

Temporal PKs for ALTER TABLE ADD CONSTRAINT

Added PK WITHOUT OVERLAPS tests re constraint dependencies; added TODO comment; fixed whitespace

Fixed some whitespace; added psql \d comment-question

Put WITHOUT OVERLAPS in the right place

Add without_overlaps regression test to the test schedule

Another tabs/spaces fix

Parser for temporal FKs, no impl yet

Add temporal foreign key to the catalog

Force the range in a temporal PK to NOT NULL

Working temporal FK checks when INSERTing in foreign table

Working temporal FK checks when UPDATing a foreign table

Validate temporal FK when it is created

temporal FK check ON DELETE RESTRICT

temporal FK check ON UPDATE NO ACTION/RESTRICT

Removed some out-of-date TODO comments

Document temporal PKs and FKs

Minor cleanup

Add FK optimization: sometimes skip check even when ranges change

Fix to failing tests

Fix typo and line lengths

Incorporate reviewer feedback

Post-merge fixes

Don't use single-element arrays for period attnum/typoids
---
 doc/src/sgml/ref/create_table.sgml             |  70 ++-
 src/backend/catalog/Catalog.pm                 |   1 +
 src/backend/catalog/heap.c                     |   1 +
 src/backend/catalog/index.c                    |   2 +
 src/backend/catalog/pg_constraint.c            |   2 +
 src/backend/commands/tablecmds.c               | 759 ++++++++++++++++---------
 src/backend/commands/trigger.c                 |   1 +
 src/backend/commands/typecmds.c                |   1 +
 src/backend/nodes/copyfuncs.c                  |   3 +
 src/backend/parser/gram.y                      |  53 +-
 src/backend/parser/parse_utilcmd.c             | 149 ++++-
 src/backend/utils/adt/ri_triggers.c            | 229 +++++++-
 src/backend/utils/adt/ruleutils.c              |  35 +-
 src/backend/utils/cache/relcache.c             |  12 +-
 src/bin/pg_dump/pg_dump.c                      |  39 +-
 src/bin/pg_dump/pg_dump.h                      |   1 +
 src/bin/pg_dump/t/002_pg_dump.pl               |  22 +
 src/bin/psql/describe.c                        |   2 +
 src/include/catalog/pg_constraint.h            |  15 +-
 src/include/catalog/pg_proc.dat                |  44 ++
 src/include/nodes/parsenodes.h                 |   5 +
 src/include/parser/kwlist.h                    |   1 +
 src/test/regress/expected/sanity_check.out     |   2 +
 src/test/regress/expected/without_overlaps.out | 363 ++++++++++++
 src/test/regress/parallel_schedule             |   2 +-
 src/test/regress/serial_schedule               |   1 +
 src/test/regress/sql/without_overlaps.sql      | 369 ++++++++++++
 27 files changed, 1834 insertions(+), 350 deletions(-)
 create mode 100644 src/test/regress/expected/without_overlaps.out
 create mode 100644 src/test/regress/sql/without_overlaps.sql

diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index e9a31aa257..894471c444 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -78,9 +78,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
   UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
-  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
+  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
-  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
 class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 <phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">temporal_interval</replaceable> in a <literal>PRIMARY KEY</literal> or <literal>FOREIGN KEY</literal> constraint is:</phrase>
+
+<replaceable class="parameter">range_column_name</replaceable>
+
 </synopsis>
 
  </refsynopsisdiv>
@@ -907,7 +912,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term><literal>PRIMARY KEY</literal> (column constraint)</term>
-    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ]
+    [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] )</literal>
     <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
     <listitem>
      <para>
@@ -940,13 +946,31 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
      <para>
       Adding a <literal>PRIMARY KEY</literal> constraint will automatically
-      create a unique btree index on the column or group of columns used in the
-      constraint.  The optional <literal>INCLUDE</literal> clause allows a list
-      of columns to be specified which will be included in the non-key portion
-      of the index.  Although uniqueness is not enforced on the included columns,
-      the constraint still depends on them. Consequently, some operations on the
-      included columns (e.g. <literal>DROP COLUMN</literal>) can cause cascaded
-      constraint and index deletion.
+      create a unique btree (or GiST if temporal) index on the column or group of
+      columns used in the constraint.  The optional <literal>INCLUDE</literal> clause
+      allows a list of columns to be specified which will be included in the non-key
+      portion of the index.  Although uniqueness is not enforced on the included
+      columns, the constraint still depends on them. Consequently, some operations
+      on the included columns (e.g. <literal>DROP COLUMN</literal>) can cause
+      cascaded constraint and index deletion.
+     </para>
+
+     <para>
+      A <literal>PRIMARY KEY</literal> with a <literal>WITHOUT OVERLAPS</literal> option
+      is a <emphasis>temporal</emphasis> primary key.
+      The <literal>WITHOUT OVERLAPS</literal> column
+      must be a range type and is used to constrain the record's applicability
+      to just that range (usually a range of dates or timestamps).
+      The main part of the primary key may be repeated elsewhere in the table,
+      as long as records with the same key don't overlap in the
+      <literal>WITHOUT OVERLAPS</literal> column.
+     </para>
+
+     <para>
+      A temporal <literal>PRIMARY KEY</literal> is enforced with an
+      <literal>EXCLUDE</literal> constraint rather than a <literal>UNIQUE</literal>
+      constraint, backed by a GiST index. You may need to install the
+      <xref linkend="btree-gist"/> extension to create temporal primary keys.
      </para>
     </listitem>
    </varlistentry>
@@ -1004,8 +1028,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    <varlistentry>
     <term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal> (column constraint)</term>
 
-   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
-    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] )
+    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH <replaceable class="parameter">matchtype</replaceable> ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ]
     [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal>
@@ -1016,11 +1040,29 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       These clauses specify a foreign key constraint, which requires
       that a group of one or more columns of the new table must only
       contain values that match values in the referenced
-      column(s) of some row of the referenced table.  If the <replaceable
+      column(s) of some row of the referenced table.
+      If the <replaceable
       class="parameter">refcolumn</replaceable> list is omitted, the
       primary key of the <replaceable class="parameter">reftable</replaceable>
       is used.  The referenced columns must be the columns of a non-deferrable
-      unique or primary key constraint in the referenced table.  The user
+      unique or primary key constraint in the referenced table.
+     </para>
+
+     <para>
+      If the last column is marked with <literal>PERIOD</literal>,
+      it must be a range column, and the referenced table
+      must have a temporal primary key.
+      The non-<literal>PERIOD</literal> columns are treated normally
+      (and there must be at least one of them),
+      but the <literal>PERIOD</literal> column is not compared for equality.
+      Instead the constraint is considered satisfied
+      if the referenced table has matching records whose combined ranges completely cover
+      the referencing record.
+      In other words, the reference must have a referent for its entire duration.
+     </para>
+
+     <para>
+      The user
       must have <literal>REFERENCES</literal> permission on the referenced table
       (either the whole table, or the specific referenced columns).  The
       addition of a foreign key constraint requires a
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index 368b1dea3e..f6cfbb68f6 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -237,6 +237,7 @@ sub ParseData
 	# Scan the input file.
 	while (<$ifd>)
 	{
+    next if /^#/;
 		my $hash_ref;
 
 		if (/{/)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9d0f..c1346d765c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2399,6 +2399,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
+							  false,	/* contemporal */
 							  is_internal); /* internally constructed? */
 
 	pfree(ccbin);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7c34509696..6c055012d7 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1884,6 +1884,8 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
+									 // TODO: set this for PKs too
+								   false,	/* contemporal */
 								   is_internal);
 
 	/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 56568b0105..ca37836ee0 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -76,6 +76,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
+					  bool conTemporal,
 					  bool is_internal)
 {
 	Relation	conDesc;
@@ -184,6 +185,7 @@ CreateConstraintEntry(const char *constraintName,
 	values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
 	values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
 	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+	values[Anum_pg_constraint_contemporal - 1] = BoolGetDatum(conTemporal);
 
 	if (conkeyArray)
 		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5597be6e3d..987bdc49ff 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -328,9 +328,12 @@ static int	transformColumnNameList(Oid relId, List *colList,
 static int	transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 									   List **attnamelist,
 									   int16 *attnums, Oid *atttypids,
+										 Node **periodattname,
+										 int16 *periodattnums, Oid *periodatttypids,
 									   Oid *opclasses);
 static Oid	transformFkeyCheckAttrs(Relation pkrel,
 									int numattrs, int16 *attnums,
+									bool is_temporal, int16 *periodattnums,
 									Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
@@ -338,7 +341,7 @@ static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
 static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
 static void validateForeignKeyConstraint(char *conname,
 										 Relation rel, Relation pkrel,
-										 Oid pkindOid, Oid constraintOid);
+										 Oid pkindOid, Oid constraintOid, bool temporal);
 static void ATController(AlterTableStmt *parsetree,
 						 Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
 static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
@@ -425,12 +428,12 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 											Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 											int numfks, int16 *pkattnum, int16 *fkattnum,
 											Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-											bool old_check_ok);
+											bool old_check_ok, bool is_temporal);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
 									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 									int numfks, int16 *pkattnum, int16 *fkattnum,
 									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-									bool old_check_ok, LOCKMODE lockmode);
+									bool old_check_ok, bool is_temporal, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 									   Relation partitionRel);
 static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
@@ -447,6 +450,12 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
 										 Oid parentConstrOid, int numfks,
 										 AttrNumber *mapped_conkey, AttrNumber *confkey,
 										 Oid *conpfeqop);
+static void FindFKComparisonOperators(Constraint *fkconstraint,
+					AlteredTableInfo *tab, int i, int16 *fkattnum,
+					bool *old_check_ok, ListCell **old_pfeqop_item,
+					Oid pktype, Oid fktype, Oid opclass,
+					bool is_temporal, bool for_overlaps,
+					Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 								 DropBehavior behavior,
 								 bool recurse, bool recursing,
@@ -4711,7 +4720,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
 
 				validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
 											 con->refindid,
-											 con->conid);
+											 con->conid,
+											 fkconstraint->fk_period != NULL);
 
 				/*
 				 * No need to mark the constraint row as validated, we did
@@ -7634,6 +7644,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	bool		is_temporal = (fkconstraint->fk_period != NULL);
+	int16		pkperiodattnum = 0;
+	int16		fkperiodattnum = 0;
+	Oid			pkperiodtypoid = 0;
+	Oid			fkperiodtypoid = 0;
 	int			i;
 	int			numfks,
 				numpks;
@@ -7736,6 +7751,14 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
+	if (is_temporal)
+	{
+	  List *fk_period;
+	  fk_period = list_make1(fkconstraint->fk_period);
+	  transformColumnNameList(RelationGetRelid(rel),
+							  fk_period,
+							  &fkperiodattnum, &fkperiodtypoid);
+	}
 
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
@@ -7748,6 +7771,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
 											&fkconstraint->pk_attrs,
 											pkattnum, pktypoid,
+											&fkconstraint->pk_period,
+											&pkperiodattnum, &pkperiodtypoid,
 											opclasses);
 	}
 	else
@@ -7755,8 +7780,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformColumnNameList(RelationGetRelid(pkrel),
 										 fkconstraint->pk_attrs,
 										 pkattnum, pktypoid);
+		if (is_temporal) {
+			List *pk_period = list_make1(fkconstraint->pk_period);
+			transformColumnNameList(RelationGetRelid(pkrel),
+									pk_period,
+									&pkperiodattnum, &pkperiodtypoid);
+		}
 		/* Look for an index matching the column list */
 		indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
+										   is_temporal, &pkperiodattnum,
 										   opclasses);
 	}
 
@@ -7806,6 +7838,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
+	// TODO: Need a check that if one side has a PERIOD the other does too
+
 	/*
 	 * On the strength of a previous constraint, we might avoid scanning
 	 * tables to validate this one.  See below.
@@ -7815,187 +7849,27 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		Oid			pktype = pktypoid[i];
-		Oid			fktype = fktypoid[i];
-		Oid			fktyped;
-		HeapTuple	cla_ht;
-		Form_pg_opclass cla_tup;
-		Oid			amid;
-		Oid			opfamily;
-		Oid			opcintype;
-		Oid			pfeqop;
-		Oid			ppeqop;
-		Oid			ffeqop;
-		int16		eqstrategy;
-		Oid			pfeqop_right;
-
-		/* We need several fields out of the pg_opclass entry */
-		cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
-		if (!HeapTupleIsValid(cla_ht))
-			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
-		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
-		amid = cla_tup->opcmethod;
-		opfamily = cla_tup->opcfamily;
-		opcintype = cla_tup->opcintype;
-		ReleaseSysCache(cla_ht);
-
-		/*
-		 * Check it's a btree; currently this can never fail since no other
-		 * index AMs support unique indexes.  If we ever did have other types
-		 * of unique indexes, we'd need a way to determine which operator
-		 * strategy number is equality.  (Is it reasonable to insist that
-		 * every such index AM use btree's number for equality?)
-		 */
-		if (amid != BTREE_AM_OID)
-			elog(ERROR, "only b-tree indexes are supported for foreign keys");
-		eqstrategy = BTEqualStrategyNumber;
-
-		/*
-		 * There had better be a primary equality operator for the index.
-		 * We'll use it for PK = PK comparisons.
-		 */
-		ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
-									 eqstrategy);
-
-		if (!OidIsValid(ppeqop))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 eqstrategy, opcintype, opcintype, opfamily);
-
-		/*
-		 * Are there equality operators that take exactly the FK type? Assume
-		 * we should look through any domain here.
-		 */
-		fktyped = getBaseType(fktype);
-
-		pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
-									 eqstrategy);
-		if (OidIsValid(pfeqop))
-		{
-			pfeqop_right = fktyped;
-			ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
-										 eqstrategy);
-		}
-		else
-		{
-			/* keep compiler quiet */
-			pfeqop_right = InvalidOid;
-			ffeqop = InvalidOid;
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-		{
-			/*
-			 * Otherwise, look for an implicit cast from the FK type to the
-			 * opcintype, and if found, use the primary equality operator.
-			 * This is a bit tricky because opcintype might be a polymorphic
-			 * type such as ANYARRAY or ANYENUM; so what we have to test is
-			 * whether the two actual column types can be concurrently cast to
-			 * that type.  (Otherwise, we'd fail to reject combinations such
-			 * as int[] and point[].)
-			 */
-			Oid			input_typeids[2];
-			Oid			target_typeids[2];
-
-			input_typeids[0] = pktype;
-			input_typeids[1] = fktype;
-			target_typeids[0] = opcintype;
-			target_typeids[1] = opcintype;
-			if (can_coerce_type(2, input_typeids, target_typeids,
-								COERCION_IMPLICIT))
-			{
-				pfeqop = ffeqop = ppeqop;
-				pfeqop_right = opcintype;
-			}
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("foreign key constraint \"%s\" cannot be implemented",
-							fkconstraint->conname),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of incompatible types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktype),
-							   format_type_be(pktype))));
-
-		if (old_check_ok)
-		{
-			/*
-			 * When a pfeqop changes, revalidate the constraint.  We could
-			 * permit intra-opfamily changes, but that adds subtle complexity
-			 * without any concrete benefit for core types.  We need not
-			 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
-			 */
-			old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
-			old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
-									old_pfeqop_item);
-		}
-		if (old_check_ok)
-		{
-			Oid			old_fktype;
-			Oid			new_fktype;
-			CoercionPathType old_pathtype;
-			CoercionPathType new_pathtype;
-			Oid			old_castfunc;
-			Oid			new_castfunc;
-			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
-												   fkattnum[i] - 1);
-
-			/*
-			 * Identify coercion pathways from each of the old and new FK-side
-			 * column types to the right (foreign) operand type of the pfeqop.
-			 * We may assume that pg_constraint.conkey is not changing.
-			 */
-			old_fktype = attr->atttypid;
-			new_fktype = fktype;
-			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
-										&old_castfunc);
-			new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
-										&new_castfunc);
-
-			/*
-			 * Upon a change to the cast from the FK column to its pfeqop
-			 * operand, revalidate the constraint.  For this evaluation, a
-			 * binary coercion cast is equivalent to no cast at all.  While
-			 * type implementors should design implicit casts with an eye
-			 * toward consistency of operations like equality, we cannot
-			 * assume here that they have done so.
-			 *
-			 * A function with a polymorphic argument could change behavior
-			 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
-			 * when the cast destination is polymorphic, we only avoid
-			 * revalidation if the input type has not changed at all.  Given
-			 * just the core data types and operator classes, this requirement
-			 * prevents no would-be optimizations.
-			 *
-			 * If the cast converts from a base type to a domain thereon, then
-			 * that domain type must be the opcintype of the unique index.
-			 * Necessarily, the primary key column must then be of the domain
-			 * type.  Since the constraint was previously valid, all values on
-			 * the foreign side necessarily exist on the primary side and in
-			 * turn conform to the domain.  Consequently, we need not treat
-			 * domains specially here.
-			 *
-			 * Since we require that all collations share the same notion of
-			 * equality (which they do, because texteq reduces to bitwise
-			 * equality), we don't compare collation here.
-			 *
-			 * We need not directly consider the PK type.  It's necessarily
-			 * binary coercible to the opcintype of the unique index column,
-			 * and ri_triggers.c will only deal with PK datums in terms of
-			 * that opcintype.  Changing the opcintype also changes pfeqop.
-			 */
-			old_check_ok = (new_pathtype == old_pathtype &&
-							new_castfunc == old_castfunc &&
-							(!IsPolymorphicType(pfeqop_right) ||
-							 new_fktype == old_fktype));
-		}
-
-		pfeqoperators[i] = pfeqop;
-		ppeqoperators[i] = ppeqop;
-		ffeqoperators[i] = ffeqop;
+		FindFKComparisonOperators(
+				fkconstraint, tab, i, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pktypoid[i], fktypoid[i], opclasses[i],
+				is_temporal, false,
+				&pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]);
+	}
+	if (is_temporal) {
+		pkattnum[numpks] = pkperiodattnum;
+		fkattnum[numpks] = fkperiodattnum;
+		pktypoid[numpks] = pkperiodtypoid;
+		fktypoid[numpks] = fkperiodtypoid;
+
+		FindFKComparisonOperators(
+				fkconstraint, tab, numpks, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pkperiodtypoid, fkperiodtypoid, opclasses[numpks],
+				is_temporal, true,
+				&pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]);
+		numfks += 1;
+		numpks += 1;
 	}
 
 	/*
@@ -8011,7 +7885,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
-									 old_check_ok);
+									 old_check_ok,
+									 is_temporal);
 
 	/* Now handle the referencing side. */
 	addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
@@ -8024,6 +7899,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							ppeqoperators,
 							ffeqoperators,
 							old_check_ok,
+							is_temporal,
 							lockmode);
 
 	/*
@@ -8064,7 +7940,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 					   Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks,
 					   int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok,
+					   bool is_temporal)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -8146,6 +8023,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
+									  is_temporal,
 									  false);	/* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -8220,7 +8098,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
-								   old_check_ok);
+								   old_check_ok, is_temporal);
 
 			/* Done -- clean up (but keep the lock) */
 			table_close(partRel, NoLock);
@@ -8269,7 +8147,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 						Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode)
+						bool old_check_ok, bool is_temporal, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
 
@@ -8414,6 +8292,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
+									  is_temporal,
 									  false);
 
 			/*
@@ -8440,6 +8319,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									ppeqoperators,
 									ffeqoperators,
 									old_check_ok,
+									is_temporal,
 									lockmode);
 
 			table_close(partition, NoLock);
@@ -8571,6 +8451,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 								   conpfeqop,
 								   conppeqop,
 								   conffeqop);
+
 		for (int i = 0; i < numfks; i++)
 			mapped_confkey[i] = attmap[confkey[i] - 1];
 
@@ -8616,7 +8497,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
-							   true);
+							   true,
+							   constrForm->contemporal);
 
 		table_close(fkRel, NoLock);
 		ReleaseSysCache(tuple);
@@ -8809,6 +8691,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
+								  constrForm->contemporal,
 								  true);
 
 		/* Set up partition dependencies for the new constraint */
@@ -8838,11 +8721,214 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conppeqop,
 								conffeqop,
 								false,	/* no old check exists */
+								constrForm->contemporal,
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
 	}
 }
 
+static void
+FindFKComparisonOperators(Constraint *fkconstraint,
+		AlteredTableInfo *tab,
+		int i,
+		int16 *fkattnum,
+		bool *old_check_ok,
+		ListCell **old_pfeqop_item,
+		Oid pktype, Oid fktype, Oid opclass,
+		bool is_temporal, bool for_overlaps,
+		Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut)
+{
+	Oid			fktyped;
+	HeapTuple	cla_ht;
+	Form_pg_opclass cla_tup;
+	Oid			amid;
+	Oid			opfamily;
+	Oid			opcintype;
+	Oid			pfeqop;
+	Oid			ppeqop;
+	Oid			ffeqop;
+	int16		eqstrategy;
+	Oid			pfeqop_right;
+
+	/* We need several fields out of the pg_opclass entry */
+	cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(cla_ht))
+		elog(ERROR, "cache lookup failed for opclass %u", opclass);
+	cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+	amid = cla_tup->opcmethod;
+	opfamily = cla_tup->opcfamily;
+	opcintype = cla_tup->opcintype;
+	ReleaseSysCache(cla_ht);
+
+	if (is_temporal)
+	{
+		if (amid != GIST_AM_OID)
+			elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
+		eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
+	}
+	else
+	{
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other types
+		 * of unique indexes, we'd need a way to determine which operator
+		 * strategy number is equality.  (Is it reasonable to insist that
+		 * every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+	}
+
+	/*
+	 * There had better be a primary equality operator for the index.
+	 * We'll use it for PK = PK comparisons.
+	 */
+	ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
+								 eqstrategy);
+
+	if (!OidIsValid(ppeqop))
+		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+			 eqstrategy, opcintype, opcintype, opfamily);
+
+	/*
+	 * Are there equality operators that take exactly the FK type? Assume
+	 * we should look through any domain here.
+	 */
+	fktyped = getBaseType(fktype);
+
+	pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+								 eqstrategy);
+	if (OidIsValid(pfeqop))
+	{
+		pfeqop_right = fktyped;
+		ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+									 eqstrategy);
+	}
+	else
+	{
+		/* keep compiler quiet */
+		pfeqop_right = InvalidOid;
+		ffeqop = InvalidOid;
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		/*
+		 * Otherwise, look for an implicit cast from the FK type to the
+		 * opcintype, and if found, use the primary equality operator.
+		 * This is a bit tricky because opcintype might be a polymorphic
+		 * type such as ANYARRAY or ANYENUM; so what we have to test is
+		 * whether the two actual column types can be concurrently cast to
+		 * that type.  (Otherwise, we'd fail to reject combinations such
+		 * as int[] and point[].)
+		 */
+		Oid			input_typeids[2];
+		Oid			target_typeids[2];
+
+		input_typeids[0] = pktype;
+		input_typeids[1] = fktype;
+		target_typeids[0] = opcintype;
+		target_typeids[1] = opcintype;
+		if (can_coerce_type(2, input_typeids, target_typeids,
+							COERCION_IMPLICIT))
+		{
+			pfeqop = ffeqop = ppeqop;
+			pfeqop_right = opcintype;
+		}
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("foreign key constraint \"%s\" cannot be implemented",
+						fkconstraint->conname),
+				 errdetail("Key columns \"%s\" and \"%s\" "
+						   "are of incompatible types: %s and %s.",
+						   strVal(list_nth(fkconstraint->fk_attrs, i)),
+						   strVal(list_nth(fkconstraint->pk_attrs, i)),
+						   format_type_be(fktype),
+						   format_type_be(pktype))));
+
+	if (*old_check_ok)
+	{
+		/*
+		 * When a pfeqop changes, revalidate the constraint.  We could
+		 * permit intra-opfamily changes, but that adds subtle complexity
+		 * without any concrete benefit for core types.  We need not
+		 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
+		 */
+		*old_check_ok = (pfeqop == lfirst_oid(*old_pfeqop_item));
+		*old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
+								*old_pfeqop_item);
+	}
+	if (*old_check_ok)
+	{
+		Oid			old_fktype;
+		Oid			new_fktype;
+		CoercionPathType old_pathtype;
+		CoercionPathType new_pathtype;
+		Oid			old_castfunc;
+		Oid			new_castfunc;
+		Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+											   fkattnum[i] - 1);
+
+		/*
+		 * Identify coercion pathways from each of the old and new FK-side
+		 * column types to the right (foreign) operand type of the pfeqop.
+		 * We may assume that pg_constraint.conkey is not changing.
+		 */
+		old_fktype = attr->atttypid;
+		new_fktype = fktype;
+		old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
+									&old_castfunc);
+		new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
+									&new_castfunc);
+
+		/*
+		 * Upon a change to the cast from the FK column to its pfeqop
+		 * operand, revalidate the constraint.  For this evaluation, a
+		 * binary coercion cast is equivalent to no cast at all.  While
+		 * type implementors should design implicit casts with an eye
+		 * toward consistency of operations like equality, we cannot
+		 * assume here that they have done so.
+		 *
+		 * A function with a polymorphic argument could change behavior
+		 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
+		 * when the cast destination is polymorphic, we only avoid
+		 * revalidation if the input type has not changed at all.  Given
+		 * just the core data types and operator classes, this requirement
+		 * prevents no would-be optimizations.
+		 *
+		 * If the cast converts from a base type to a domain thereon, then
+		 * that domain type must be the opcintype of the unique index.
+		 * Necessarily, the primary key column must then be of the domain
+		 * type.  Since the constraint was previously valid, all values on
+		 * the foreign side necessarily exist on the primary side and in
+		 * turn conform to the domain.  Consequently, we need not treat
+		 * domains specially here.
+		 *
+		 * Since we require that all collations share the same notion of
+		 * equality (which they do, because texteq reduces to bitwise
+		 * equality), we don't compare collation here.
+		 *
+		 * We need not directly consider the PK type.  It's necessarily
+		 * binary coercible to the opcintype of the unique index column,
+		 * and ri_triggers.c will only deal with PK datums in terms of
+		 * that opcintype.  Changing the opcintype also changes pfeqop.
+		 */
+		*old_check_ok = (new_pathtype == old_pathtype &&
+						new_castfunc == old_castfunc &&
+						(!IsPolymorphicType(pfeqop_right) ||
+						 new_fktype == old_fktype));
+
+	}
+
+	*pfeqopOut = pfeqop;
+	*ppeqopOut = ppeqop;
+	*ffeqopOut = ffeqop;
+}
+
 /*
  * When the parent of a partition receives [the referencing side of] a foreign
  * key, we must propagate that foreign key to the partition.  However, the
@@ -9223,7 +9309,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 con->oid);
+										 con->oid, con->contemporal);
 			table_close(refrel, NoLock);
 
 			/*
@@ -9356,10 +9442,12 @@ transformColumnNameList(Oid relId, List *colList,
  *
  *	Look up the names, attnums, and types of the primary key attributes
  *	for the pkrel.  Also return the index OID and index opclasses of the
- *	index supporting the primary key.
+ *	index supporting the primary key.  If this is a temporal primary key,
+ *	also set the WITHOUT OVERLAPS attribute name, attnum, and atttypid.
  *
  *	All parameters except pkrel are output parameters.  Also, the function
- *	return value is the number of attributes in the primary key.
+ *	return value is the number of attributes in the primary key,
+ *	not including the WITHOUT OVERLAPS if any.
  *
  *	Used when the column list in the REFERENCES specification is omitted.
  */
@@ -9367,6 +9455,8 @@ static int
 transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses)
 {
 	List	   *indexoidlist;
@@ -9434,35 +9524,50 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 	/*
 	 * Now build the list of PK attributes from the indkey definition (we
 	 * assume a primary key cannot have expressional elements)
+	 * TODO: range expressions will be how we support PERIODs though.
 	 */
 	*attnamelist = NIL;
 	for (i = 0; i < indexStruct->indnkeyatts; i++)
 	{
 		int			pkattno = indexStruct->indkey.values[i];
 
-		attnums[i] = pkattno;
-		atttypids[i] = attnumTypeId(pkrel, pkattno);
-		opclasses[i] = indclass->values[i];
-		*attnamelist = lappend(*attnamelist,
-							   makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		if (i == indexStruct->indnkeyatts - 1 && indexStruct->indisexclusion)
+		{
+			periodattnums[0] = pkattno;
+			periodatttypids[0] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*periodattname = (Node *)makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))));
+		}
+		else
+		{
+			attnums[i] = pkattno;
+			atttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*attnamelist = lappend(*attnamelist,
+								  makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		}
 	}
 
 	ReleaseSysCache(indexTuple);
 
-	return i;
+	if (indexStruct->indisexclusion) return i - 1;
+	else return i;
 }
 
 /*
  * transformFkeyCheckAttrs -
  *
  *	Make sure that the attributes of a referenced table belong to a unique
- *	(or primary key) constraint.  Return the OID of the index supporting
- *	the constraint, as well as the opclasses associated with the index
+ *	(or primary key) constraint.  Or if this is a temporal foreign key
+ *	the primary key should be an exclusion constraint instead.
+ *	Return the OID of the index supporting the constraint,
+ *	as well as the opclasses associated with the index
  *	columns.
  */
 static Oid
 transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
 						Oid *opclasses) /* output parameter */
 {
 	Oid			indexoid = InvalidOid;
@@ -9489,6 +9594,10 @@ transformFkeyCheckAttrs(Relation pkrel,
 						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 						 errmsg("foreign key referenced-columns list must not contain duplicates")));
 		}
+		if (is_temporal && attnums[i] == periodattnums[0])
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+					 errmsg("foreign key referenced-columns list must not contain duplicates")));
 	}
 
 	/*
@@ -9510,12 +9619,16 @@ transformFkeyCheckAttrs(Relation pkrel,
 		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		/*
-		 * Must have the right number of columns; must be unique and not a
+		 * Must have the right number of columns; must be unique
+		 * (or if temporal then exclusion instead) and not a
 		 * partial index; forget it if there are any expressions, too. Invalid
 		 * indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
-			indexStruct->indisunique &&
+		if ((is_temporal
+			  ? (indexStruct->indnkeyatts == numattrs + 1 &&
+				 indexStruct->indisexclusion)
+			  : (indexStruct->indnkeyatts == numattrs &&
+				 indexStruct->indisunique)) &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
 			heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
@@ -9555,6 +9668,19 @@ transformFkeyCheckAttrs(Relation pkrel,
 				if (!found)
 					break;
 			}
+			if (is_temporal)
+			{
+				found = false;
+				for (j = 0; j < numattrs + 1; j++)
+				{
+					if (periodattnums[0] == indexStruct->indkey.values[j])
+					{
+						opclasses[numattrs] = indclass->values[j];
+						found = true;
+						break;
+					}
+				}
+			}
 
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
@@ -9744,7 +9870,8 @@ validateForeignKeyConstraint(char *conname,
 							 Relation rel,
 							 Relation pkrel,
 							 Oid pkindOid,
-							 Oid constraintOid)
+							 Oid constraintOid,
+							 bool temporal)
 {
 	TupleTableSlot *slot;
 	TableScanDesc scan;
@@ -9774,8 +9901,10 @@ validateForeignKeyConstraint(char *conname,
 	/*
 	 * See if we can do it with a single LEFT JOIN query.  A false result
 	 * indicates we must proceed with the fire-the-trigger method.
+	 * We can't do a LEFT JOIN for temporal FKs yet,
+	 * but we can once we support temporal left joins.
 	 */
-	if (RI_Initial_Check(&trig, rel, pkrel))
+	if (!temporal && RI_Initial_Check(&trig, rel, pkrel))
 		return;
 
 	/*
@@ -9837,6 +9966,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 					 Oid constraintOid, Oid indexOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
+	bool is_temporal = fkconstraint->fk_period;
 
 	/*
 	 * Note: for a self-referential FK (referencing and referenced tables are
@@ -9848,7 +9978,10 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = "RI_ConstraintTrigger_c";
+	if (is_temporal)
+		fk_trigger->trigname = "TRI_ConstraintTrigger_c";
+	else
+		fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
 	fk_trigger->timing = TRIGGER_TYPE_AFTER;
@@ -9856,12 +9989,18 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	/* Either ON INSERT or ON UPDATE */
 	if (on_insert)
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_ins");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
 		fk_trigger->events = TRIGGER_TYPE_INSERT;
 	}
 	else
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_upd");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
 		fk_trigger->events = TRIGGER_TYPE_UPDATE;
 	}
 
@@ -9907,37 +10046,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_del_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_del");
+				break;
+				/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_del");
+				break;
+				*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
@@ -9963,37 +10143,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_upd_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_upd");
+				break;
+			/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_upd");
+				break;
+			*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 0b84de5943..271591bafb 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -776,6 +776,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* noinherit */
+											  false, /* contemporal */
 											  isInternal);	/* is_internal */
 	}
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7a440cafd1..0a1ceaa3b3 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3339,6 +3339,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
+							  false,	/* contemporal */
 							  false);	/* is_internal */
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3432bb921d..d629aa2339 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2925,12 +2925,15 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(where_clause);
 	COPY_NODE_FIELD(pktable);
 	COPY_NODE_FIELD(fk_attrs);
+	COPY_NODE_FIELD(fk_period);
 	COPY_NODE_FIELD(pk_attrs);
+	COPY_NODE_FIELD(pk_period);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf30e..081757d841 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -473,10 +473,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause optionalPeriodName
+				where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
+%type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
@@ -667,7 +669,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
 
@@ -3463,6 +3465,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3679,18 +3682,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3701,6 +3705,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3729,19 +3734,21 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
-				opt_column_list key_match key_actions ConstraintAttributeSpec
+			| FOREIGN KEY '(' columnList optionalPeriodName ')' REFERENCES qualified_name
+				opt_column_and_period_list key_match key_actions ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_FOREIGN;
 					n->location = @1;
-					n->pktable			= $7;
+					n->pktable			= $8;
 					n->fk_attrs			= $4;
-					n->pk_attrs			= $8;
-					n->fk_matchtype		= $9;
-					n->fk_upd_action	= (char) ($10 >> 8);
-					n->fk_del_action	= (char) ($10 & 0xFF);
-					processCASbits($11, @11, "FOREIGN KEY",
+					n->fk_period		= $5;
+					n->pk_attrs			= linitial($9);
+					n->pk_period		= lsecond($9);
+					n->fk_matchtype		= $10;
+					n->fk_upd_action	= (char) ($11 >> 8);
+					n->fk_del_action	= (char) ($11 & 0xFF);
+					processCASbits($12, @12, "FOREIGN KEY",
 								   &n->deferrable, &n->initdeferred,
 								   &n->skip_validation, NULL,
 								   yyscanner);
@@ -3764,6 +3771,21 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+optionalPeriodName:
+			',' PERIOD columnElem { $$ = $3; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+opt_column_and_period_list:
+			'(' columnList optionalPeriodName ')'			{ $$ = list_make2($2, $3); }
+			| /*EMPTY*/								{ $$ = list_make2(NIL, NULL); }
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
@@ -15502,6 +15524,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 ee47547624..50a5a36e41 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1962,7 +1962,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2351,6 +2351,153 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				notnullcmd->name = pstrdup(key);
 				notnullcmds = lappend(notnullcmds, notnullcmd);
 			}
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				AlterTableCmd *notnullcmd;
+				iparam->name = pstrdup(without_overlaps_str);
+				iparam->expr = NULL;
+
+				/*
+				 * Force the column to NOT NULL since it is part of the primary key.
+				 */
+				notnullcmd = makeNode(AlterTableCmd);
+
+				notnullcmd->subtype = AT_SetNotNull;
+				notnullcmd->name = pstrdup(without_overlaps_str);
+				notnullcmds = lappend(notnullcmds, notnullcmd);
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+			{
+				List *opname;
+				iparam->indexcolname = NULL;
+				iparam->collation = NIL;
+				iparam->opclass = NIL;
+				iparam->ordering = SORTBY_DEFAULT;
+				iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+				index->indexParams = lappend(index->indexParams, iparam);
+
+				opname = list_make1(makeString("&&"));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+				index->accessMethod = "gist";
+				constraint->access_method = "gist";
+			}
 		}
 	}
 
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index caf27f8bcb..75205c8c08 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -109,6 +109,7 @@ typedef struct RI_ConstraintInfo
 	char		confupdtype;	/* foreign key's ON UPDATE action */
 	char		confdeltype;	/* foreign key's ON DELETE action */
 	char		confmatchtype;	/* foreign key's match type */
+	bool		temporal;		/* if the foreign key is temporal */
 	int			nkeys;			/* number of key columns */
 	int16		pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
 	int16		fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
@@ -192,7 +193,7 @@ static int	ri_NullCheck(TupleDesc tupdesc, TupleTableSlot *slot,
 static void ri_BuildQueryKey(RI_QueryKey *key,
 							 const RI_ConstraintInfo *riinfo,
 							 int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+static bool ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
 							   Datum oldvalue, Datum newvalue);
@@ -352,18 +353,46 @@ RI_FKey_check(TriggerData *trigdata)
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
-		 *		   FOR KEY SHARE OF x
+		 *	SELECT 1
+		 *	FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *	FOR KEY SHARE OF x
 		 * The type id's for the $ parameters are those of the
 		 * corresponding FK attributes.
+		 *
+		 * But for temporal FKs we need to make sure
+		 * the FK's range is completely covered.
+		 * So we use this query instead:
+		 *  SELECT 1
+		 *  FROM (
+		 *	    SELECT	range_agg(r) AS r
+		 *	    FROM	(
+		 *			SELECT pkperiodatt AS r
+		 *			FROM   [ONLY] pktable x
+		 *			WHERE  pkatt1 = $1 [AND ...]
+		 *			FOR KEY SHARE OF x
+		 *		) x1
+		 *  ) x2
+		 *  WHERE $n <@ x2.r
+		 * Note if FOR KEY SHARE ever allows aggregate functions
+		 * we can make this a bit simpler.
 		 * ----------
 		 */
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
 		quoteRelationName(pkrelname, pk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 pk_only, pkrelname);
+		if (riinfo->temporal)
+		{
+			quoteOneName(attname,
+					RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			appendStringInfo(&querybuf,
+					"SELECT 1 FROM (SELECT range_agg(r) AS r FROM (SELECT %s AS r FROM %s%s x",
+					attname, pk_only, pkrelname);
+		}
+		else {
+			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
+							 pk_only, pkrelname);
+		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -381,6 +410,8 @@ RI_FKey_check(TriggerData *trigdata)
 			queryoids[i] = fk_type;
 		}
 		appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
+		if (riinfo->temporal)
+			appendStringInfo(&querybuf, ") x1) x2 WHERE $%d <@ x2.r", riinfo->nkeys);
 
 		/* Prepare and save the plan */
 		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -1175,7 +1206,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
 		return false;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
+	if (newslot && ri_KeysStable(pk_rel, oldslot, newslot, riinfo, true))
 		return false;
 
 	/* Else we need to fire the trigger. */
@@ -1268,13 +1299,135 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 		return true;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
+	if (ri_KeysStable(fk_rel, oldslot, newslot, riinfo, false))
 		return false;
 
 	/* Else we need to fire the trigger. */
 	return true;
 }
 
+/* ----------
+ * TRI_FKey_check_ins -
+ *
+ *	Check temporal foreign key existence at insert event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_ins(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+	/*
+	 * Share code with UPDATE case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_check_upd -
+ *
+ *	Check temporal foreign key existence at update event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_upd(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+	/*
+	 * Share code with INSERT case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_noaction_del -
+ *
+ *	Give an error and roll back the current transaction if the
+ *	delete has resulted in a violation of the given temporal
+ *	referential integrity constraint.
+ * ----------
+ */
+Datum
+TRI_FKey_noaction_del(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
+
+	/*
+	 * Share code with RESTRICT/UPDATE cases.
+	 */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_del -
+ *
+ * Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the delete is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with NO ACTION/UPDATE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+/*
+ * TRI_FKey_noaction_upd -
+ *
+ * Give an error and roll back the current transaction if the
+ * update has resulted in a violation of the given referential
+ * integrity constraint.
+ */
+Datum
+TRI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with RESTRICT/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_upd -
+ *
+ * Restrict update of PK to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the update is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with NO ACTION/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+
 /*
  * RI_Initial_Check -
  *
@@ -2050,6 +2203,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo->confupdtype = conForm->confupdtype;
 	riinfo->confdeltype = conForm->confdeltype;
 	riinfo->confmatchtype = conForm->confmatchtype;
+	riinfo->temporal = conForm->contemporal;
 
 	DeconstructFkConstraintRow(tup,
 							   &riinfo->nkeys,
@@ -2644,9 +2798,12 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 
 
 /*
- * ri_KeysEqual -
+ * ri_KeysStable -
  *
- * Check if all key values in OLD and NEW are equal.
+ * Check if all key values in OLD and NEW are "equivalent":
+ * For normal FKs we check for equality.
+ * For temporal FKs we check that the PK side is a superset of its old value,
+ * or the FK side is a subset.
  *
  * Note: at some point we might wish to redefine this as checking for
  * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
@@ -2654,7 +2811,7 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
  * previously found at least one of the rows to contain no nulls.
  */
 static bool
-ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -2687,29 +2844,43 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 
 		if (rel_is_pk)
 		{
-			/*
-			 * If we are looking at the PK table, then do a bytewise
-			 * comparison.  We must propagate PK changes if the value is
-			 * changed to one that "looks" different but would compare as
-			 * equal using the equality operator.  This only makes a
-			 * difference for ON UPDATE CASCADE, but for consistency we treat
-			 * all changes to the PK the same.
-			 */
-			Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, newvalue, oldvalue));
+			}
+			else
+			{
+				/*
+				 * If we are looking at the PK table, then do a bytewise
+				 * comparison.  We must propagate PK changes if the value is
+				 * changed to one that "looks" different but would compare as
+				 * equal using the equality operator.  This only makes a
+				 * difference for ON UPDATE CASCADE, but for consistency we treat
+				 * all changes to the PK the same.
+				 */
+				Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
 
-			if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
-				return false;
+				if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
+					return false;
+			}
 		}
 		else
 		{
-			/*
-			 * For the FK table, compare with the appropriate equality
-			 * operator.  Changes that compare equal will still satisfy the
-			 * constraint after the update.
-			 */
-			if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
-									oldvalue, newvalue))
-				return false;
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, oldvalue, newvalue));
+			}
+			else
+			{
+				/*
+				 * For the FK table, compare with the appropriate equality
+				 * operator.  Changes that compare equal will still satisfy the
+				 * constraint after the update.
+				 */
+				if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
+										oldvalue, newvalue))
+					return false;
+			}
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3e64390d81..55fa0be809 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -317,7 +317,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 								   int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int	decompile_column_index_array(Datum column_index_array, Oid relId,
-										 StringInfo buf);
+										 bool withoutOverlaps, bool withPeriod, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									const Oid *excludeOps,
@@ -1985,6 +1985,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			{
 				Datum		val;
 				bool		isnull;
+				bool		hasperiod;
 				const char *string;
 
 				/* Start off the constraint definition */
@@ -1997,7 +1998,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it is a temporal foreign key
+				 * then it uses PERIOD.
+				 */
+				hasperiod = DatumGetBool(SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_contemporal, &isnull));
+				decompile_column_index_array(val, conForm->conrelid, false, hasperiod, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2011,7 +2018,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, hasperiod, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2112,7 +2119,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2314,7 +2327,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, bool withPeriod, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2332,9 +2345,21 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
+		else if (withPeriod && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", PERIOD %s", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 585dcee5db..af1d91fbc9 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4956,8 +4956,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5023,7 +5024,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index db84e79e11..afe4233079 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6846,7 +6846,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6907,7 +6908,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -6946,7 +6948,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -6981,7 +6984,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7012,7 +7016,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7046,7 +7051,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7086,6 +7092,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7146,6 +7153,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16653,9 +16661,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e27a752b6d..7b6a1f38a4 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -449,6 +449,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+	bool		withoutoverlaps;	/* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 1163420491..317c149f2a 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -632,6 +632,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index b3b9313b36..1cb03100ac 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2386,6 +2386,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index c1e60c7dfd..0baa22c0e5 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -103,6 +103,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	/* Has a local definition and cannot be inherited */
 	bool		connoinherit;
 
+	/*
+	 * For primary and foreign keys, signifies the last column is a range 
+	 * and should use overlaps instead of equals.
+	 */
+	bool		contemporal;
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 
 	/*
@@ -117,26 +123,26 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	int16		confkey[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * If a foreign key, the OIDs of the PK = FK comparison operators for each
 	 * column of the constraint
 	 */
 	Oid			conpfeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * If a foreign key, the OIDs of the PK = PK comparison operators for each
 	 * column of the constraint (i.e., equality for the referenced columns)
 	 */
 	Oid			conppeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * If a foreign key, the OIDs of the FK = FK comparison operators for each
 	 * column of the constraint (i.e., equality for the referencing columns)
 	 */
 	Oid			conffeqop[1];
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
@@ -211,6 +217,7 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conIsLocal,
 								  int conInhCount,
 								  bool conNoInherit,
+								  bool conTemporal,
 								  bool is_internal);
 
 extern void RemoveConstraintById(Oid conId);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 13ea94c024..7d0cd41edd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3709,6 +3709,50 @@
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'RI_FKey_noaction_upd' },
 
+# Temporal referential integrity constraint triggers
+{ oid => '6122', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_ins' },
+{ oid => '6123', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_upd' },
+# { oid => '6124', descr => 'temporal referential integrity ON DELETE CASCADE',
+#   proname => 'TRI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_del' },
+# { oid => '6125', descr => 'temporal referential integrity ON UPDATE CASCADE',
+#   proname => 'TRI_FKey_cascade_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_upd' },
+{ oid => '6126', descr => 'temporal referential integrity ON DELETE RESTRICT',
+  proname => 'TRI_FKey_restrict_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_del' },
+{ oid => '6127', descr => 'temporal referential integrity ON UPDATE RESTRICT',
+  proname => 'TRI_FKey_restrict_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_upd' },
+# { oid => '6128', descr => 'temporal referential integrity ON DELETE SET NULL',
+#   proname => 'TRI_FKey_setnull_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_del' },
+# { oid => '6129', descr => 'temporal referential integrity ON UPDATE SET NULL',
+#   proname => 'TRI_FKey_setnull_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_upd' },
+# { oid => '6130', descr => 'temporal referential integrity ON DELETE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_del', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_del' },
+# { oid => '6131', descr => 'temporal referential integrity ON UPDATE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_upd', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_upd' },
+{ oid => '6132', descr => 'temporal referential integrity ON DELETE NO ACTION',
+  proname => 'TRI_FKey_noaction_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_del' },
+{ oid => '6133', descr => 'temporal referential integrity ON UPDATE NO ACTION',
+  proname => 'TRI_FKey_noaction_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_upd' },
+
 { oid => '1666',
   proname => 'varbiteq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'varbit varbit', prosrc => 'biteq' },
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d93a79a554..aa7da0c79f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2162,7 +2162,9 @@ typedef struct Constraint
 	/* Fields used for FOREIGN KEY constraints: */
 	RangeVar   *pktable;		/* Primary key table */
 	List	   *fk_attrs;		/* Attributes of foreign key */
+	Node	   *fk_period;		/* String node naming Period or range column */
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
+	Node	   *pk_period;		/* String node naming Period or range column */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
 	char		fk_del_action;	/* ON DELETE action */
@@ -2170,6 +2172,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+	Node	   *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 00ace8425e..92628f390a 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -299,6 +299,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD)
+PG_KEYWORD("period", PERIOD, RESERVED_KEYWORD)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD)
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 6f7fcf6326..9d0194e839 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -173,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+referencing_period_test|t
 reservations|f
 road|t
 shighway|t
@@ -211,6 +212,7 @@ timetz_tbl|f
 tmp|f
 varchar_tbl|f
 view_base_table|t
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
new file mode 100644
index 0000000000..a3c6f1ff45
--- /dev/null
+++ b/src/test/regress/expected/without_overlaps.out
@@ -0,0 +1,363 @@
+-- Tests for WITHOUT OVERLAPS.
+--
+-- test input parser
+--
+-- PK with no columns just WITHOUT OVERLAPS:
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+ERROR:  syntax error at or near "WITHOUT"
+LINE 3: ...STRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OV...
+                                                             ^
+-- PK with a range column that isn't there:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  range or PERIOD "valid_at" named in WITHOUT OVERLAPS does not exist
+-- PK with a PERIOD that isn't there:
+-- PK with a non-range column:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  column "valid_at" named in WITHOUT OVERLAPS is not a range type
+-- PK with one column plus a range:
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+-- PK with one column plus a PERIOD:
+-- PK with two columns plus a PERIOD:
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
+--
+-- test PK inserts
+--
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+ERROR:  conflicting key value violates exclusion constraint "without_overlaps_pk"
+DETAIL:  Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+ERROR:  null value in column "id" violates not-null constraint
+DETAIL:  Failing row contains (null, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+ERROR:  null value in column "valid_at" violates not-null constraint
+DETAIL:  Failing row contains ([3,4), null).
+--
+-- test changing the PK's dependencies
+--
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ERROR:  column "valid_at" is in a primary key
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
+--
+-- test FK parser
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+                                      pg_get_constraintdef                                      
+------------------------------------------------------------------------------------------------
+ FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
+(1 row)
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Tue May 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([8,9), ["Tue Jan 02 00:00:00 2018","Thu Mar 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+--
+-- test FK parent updates SET NULL
+--
+--
+-- test FK parent updates SET DEFAULT
+--
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+--
+-- test FK parent deletes SET NULL
+--
+--
+-- test FK parent deletes SET DEFAULT
+--
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index c8c1cd0671..b3e72673bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -113,7 +113,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion tr
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info without_overlaps
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 35155436f0..dbcd919a84 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,7 @@ test: enum
 test: money
 test: rangetypes
 test: multirangetypes
+test: without_overlaps
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..83824a55d3
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,369 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
+
+--
+-- test FK parser
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+-- TODO
+--
+-- test FK parent updates SET NULL
+--
+-- TODO
+--
+-- test FK parent updates SET DEFAULT
+--
+-- TODO
+
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+-- TODO
+--
+-- test FK parent deletes SET NULL
+--
+-- TODO
+--
+-- test FK parent deletes SET DEFAULT
+--
+-- TODO
-- 
2.11.0

#25Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Paul A Jungwirth (#24)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Wed, Nov 6, 2019 at 9:31 PM Paul A Jungwirth
<pj@illuminatedcomputing.com> wrote:

I've also added some progress on adding FOR PORTION OF to UPDATE and DELETE
(mostly UPDATE).

I could use some guidance on where in the query-processing pipeline I
should implement some things here. Basically if you say

UPDATE t FOR PORTION OF valid_at FROM t1 TO t2

then we need to do several things:

- Add a qual like `valid_at && tsrange(t1, t2)`. (I'll assume valid_at
is a tsrange column for example's sake, but really it can be any range
type. Also valid_at may be a PERIOD instead of a range, which means
the start/end are two concrete columns instead, but that doesn't
change anything notable here.)
- Add a target entry like `SET valid_at = valid_at * tsrange(t1, t2)`.
(* = intersection. Basically each bound should be truncated to fit
within t1/t2.)
- If either bound was "cut" then also do an INSERT to restore the
cut-off part, leaving all columns unchanged except for the time part.

(DELETE t FOR PORTION OF is very similar.)

I think I understand the ModifyTable executor node enough to be able
to add the optional INSERTs there when necessary. Adding the qual and
the target entry is where I want advice.

So far I've been able to add a ForPortionOfClause when parsing and a
ForPortionOfExpr when analyzing (much like how we handle ON CONFLICT).
I could use those to add a qual and a target list entry during
analysis (in fact I've tried that and it seems to work), but I'm
pretty sure that's wrong. I recall a long post to pgsql-hackers a
month or three back lamenting how new contributors often do work in
the analysis phase that should happen later. (I can't find that now,
but if anyone has a link I'd appreciate it!) Some considerations (not
an exhaustive list):

- FOR PORTION OF should work on partitioned tables.
- It should work on automatically-updateable views.
- It should work on views with CHECK OPTION.
- It should work on views with an UPDATE rule.
- It should do the right thing for EXPLAIN output (whatever that is).
- If a function does a FOR PORTION OF command, then printing the
function definition should show that clause (and nothing extra).
- Same for printing a rule definition.
- Probably if you give a FOR PORTION OF we should forbid you from
SETting the time column(s) at the same time, since we want to set them
automatically.
- Triggers should work normally. (We *should* fire ROW triggers for
the INSERTs of the "cut off" bits. Mariadb fires them in this order,
which seems correct to me: BEFORE UPDATE, BEFORE INSERT, AFTER INSERT,
BEFORE INSERT, AFTER INSERT, AFTER UPDATE. I guess we probably want to
fire STATEMENT triggers too, probably once for each INSERT. I'll check
what other systems do there.)

So I'm thinking the right place to add the quals & target entry is
either the end of the rewriting phase or the beginning of the planning
phase. (I can still build the expressions in the analysis phase, but I
need to keep them "off to the side" in a new forPortionOf attribute
until the right time.) We definitely want the extra qual soon enough
to help choose indexes. Perhaps we even want to see it in EXPLAIN
output (which happens if I add it during analysis); personally I kind
of find that helpful. Do we want to add it after processing rewrite
rules (and will that change EXPLAIN output)?

For adding the target entry, if we are forbidding the user from
SETting things, that check needs to happen after processing rewrite
rules, right? (Of course it doesn't hurt to check in several places if
there is some reason to do that.)

Btw I thought about whether we could implement this feature completely
on top of either triggers or rules, but I don't think it's quite that
simple. Basically: because you could also UPDATE/DELETE the table
*without* a FOR PORTION OF, sometimes we need to do the extra things
and sometimes not, and we need a way of knowing which is which. And
then supporting PERIODs requires a little extra "magic" beyond that.
But if someone has a great idea I'm open to hearing about it. :-)

Thanks,
Paul

#26Michael Paquier
michael@paquier.xyz
In reply to: Paul A Jungwirth (#25)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Mon, Nov 11, 2019 at 12:13:20PM -0800, Paul A Jungwirth wrote:

I could use some guidance on where in the query-processing pipeline I
should implement some things here. Basically if you say
[...]

Paul, please be careful to update correctly the entry of the patch in
the CF app. This was marked as waiting on author, but you are
obviously looking for reviews. I have updated the status of the patch
accordingly, then moved it again.
--
Michael

#27Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Michael Paquier (#26)
1 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

Here is a patch rebasing on master (meant to be applied on top of my
other multirange patch) and newly including UPDATE/DELETE FOR PORTION
OF. FOR PORTION OF works on any table with a temporal primary key. It
restricts the UPDATE/DELETE to the given time frame, and then if the
affected row(s) had any "leftovers" above or below the targeted range,
it INSERTs new rows to preserve the untouched intervals.

I put the implementation into execModifyTable.c (mostly), which I
think is the preferred approach. (There was a great message on
-hackers a year or two ago lamenting how new contributors want to do
all the work in the parse/analysis phase, so I tried to avoid that. I
wish I could find the thread. I want to say Robert Haas wrote it, but
it could have been anyone.)

The executor is new territory for me, so even though this is WIP I'd
love to have someone look at it now. I'm sure I'm doing all kinds of
bad things re locking, transaction isolation, snapshots, etc. (There
are some comments in the .patch for specific worries.)

Also, since I have to do range calculations to handle the leftovers,
this adds knowledge about a specific type (well a specific type of
types) to the executor. I felt like that was mixing abstraction layers
a bit, so perhaps someone will have an opinion/suggestion there. I
could probably build an Expr earlier in the pipeline if I had a way to
feed it the pre-UPDATE values of the row (suggestions anyone?), and
then the executor could just evaluate it without knowing anything
about ranges.

I'm also not yet handling FDW tables, updatable views, or partitioned
tables. Perhaps we don't support FDWs at all here, or leave it up to
the FDW implementation to decide.

I haven't thought much about updatable views yet....

For partitioned tables, I think I can add support without too much
trouble; I'll give it a try soon.

Possibly the temporal PK requirement rules out using this for all
three (FDWs, updatable views, partitioned tables), at least for now.

This is mostly "happy path" so there is probably some error handling to add.

Previously a DELETE never updated indexes, and now I *do* update
indexes if a DELETE has a FOR PORTION OF clause, so that they see the
potential INSERTs. I don't know if that adds any risks around deadlock
etc.

I have a test verifying that we do fire triggers on the implicit
INSERTs (which I think is what the spec requires and is what MariaDB
and IBM DB2 do). Right now my triggers are firing in this order:

BEFORE UPDATE/DELETE
BEFORE INSERT
BEFORE INSERT
AFTER INSERT
AFTER INSERT
AFTER UPDATE/DELETE

In MariaDB they fire in this order:

BEFORE UPDATE/DELETE
BEFORE INSERT
AFTER INSERT
BEFORE INSERT
AFTER INSERT
AFTER UPDATE/DELETE

I haven't yet tested DB2 to see which order it uses. (It does fire the
INSERT triggers though.) I don't know if the spec has an opinion (I've
never found anything explicit, but it talks about "primary" vs
"secondary" operations), and I'm not actually sure how to get
MariaDB's order if we wanted to.

Instead of implementing so much in the executor, I could *almost* have
built FOR PORTION OF based on hidden triggers (sort of like how we
implement FKs). Probably AFTER ROW triggers. Then I'd hardly have to
touch the executor at all, and I wouldn't need to worry as much about
locking/isolation/snapshots. I would just need to add something to the
TriggerData struct giving the FOR PORTION OF bounds. (For an UPDATE I
could pull this out of NEW.valid_at (or whatever you call your
column), but for a DELETE that is NULL.) Basically a trigger needs to
know (1) if the query had a FOR PORTION OF clause (2) what the bounds
were. My triggers would be in C, so I think adding a new field to the
struct is sufficient. Of course we could still expose the values to
user-defined triggers if we wanted with e.g. TG_TARGET_INTERVAL.

With a trigger-based implementation, I'd add the update/delete
triggers whenever someone adds a temporal primary key. That means you
can't use FOR PORTION OF on arbitrary ranges, which is a little sad,
but no worse than the spec. And for now I was only supporting it on PK
columns anyway.

Also with a trigger-based implementation I don't think there is a way
to force our hidden trigger to fire before user-defined triggers,
which might be nice. But if that's not a problem with FKs then it's
probably not a problem here.

A trigger-based implementation also is less flexible in how it
interacts with GENERATED columns, but I think an AFTER trigger would
still do the right thing there.

A trigger-based implementation should give us the same firing order as
MariaDB (IIUC), which might be nice.

So let me know if anyone thinks this would be better implemented as
triggers instead. I'm kind of leaning that way myself to be honest.

One weird issue I noticed concerns dealing with unbounded endpoints.
According to the spec a PERIOD's endpoints must be not-NULL, so you
have to use sentinel values like 3000-JAN-01. Oracle ignores this
though, and since our own ranges already interpret a null bound to
mean infinite, I think we should accept a non-sentinel way of saying
"from now on" or "since the beginning". Right now I accept 'Infinity'
and '-Infinity' in FOR PORTION OF, because those are familiar and
valid values for timestamps/dates/floats. But I translate them to
`NULL` to get proper range behavior. That's because to ranges,
'Infinity' is "right before" an upper null, and '-Infinity' is "right
after" a lower null. For example:

=# select tsrange('2020-01-01', null) - tsrange('2020-01-01', 'Infinity');
?column?
-------------
[infinity,)

That will leave a lot of ugly records in your table if you don't take
care to avoid it.

I guess alternately I could let people say FOR PORTION OF FROM
'2020-01-01' TO NULL. But that is less obvious, and it leaves the
footgun still there. So right now I'm translating +/-Infinity into
NULL to prevent the problem.

Anyway, now that FOR PORTION OF works, I want to add CASCADE support
to temporal FKs. I don't think that will take long.

Then I'd like to bring in Vik Fearing's old patch adding PERIODs, and
start supporting those too. Vik, do you have any objection or advice
about that?

Yours,

Attachments:

v0003-temporal-pk-fk-update-delete.patchapplication/octet-stream; name=v0003-temporal-pk-fk-update-delete.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index 4a2b6f0dae..1a50a7f235 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -78,9 +78,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
   UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
-  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
+  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
-  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
 class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 <phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">temporal_interval</replaceable> in a <literal>PRIMARY KEY</literal> or <literal>FOREIGN KEY</literal> constraint is:</phrase>
+
+<replaceable class="parameter">range_column_name</replaceable>
+
 </synopsis>
 
  </refsynopsisdiv>
@@ -909,7 +914,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term><literal>PRIMARY KEY</literal> (column constraint)</term>
-    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ]
+    [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] )</literal>
     <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
     <listitem>
      <para>
@@ -942,13 +948,31 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
      <para>
       Adding a <literal>PRIMARY KEY</literal> constraint will automatically
-      create a unique btree index on the column or group of columns used in the
-      constraint.  The optional <literal>INCLUDE</literal> clause allows a list
-      of columns to be specified which will be included in the non-key portion
-      of the index.  Although uniqueness is not enforced on the included columns,
-      the constraint still depends on them. Consequently, some operations on the
-      included columns (e.g. <literal>DROP COLUMN</literal>) can cause cascaded
-      constraint and index deletion.
+      create a unique btree (or GiST if temporal) index on the column or group of
+      columns used in the constraint.  The optional <literal>INCLUDE</literal> clause
+      allows a list of columns to be specified which will be included in the non-key
+      portion of the index.  Although uniqueness is not enforced on the included
+      columns, the constraint still depends on them. Consequently, some operations
+      on the included columns (e.g. <literal>DROP COLUMN</literal>) can cause
+      cascaded constraint and index deletion.
+     </para>
+
+     <para>
+      A <literal>PRIMARY KEY</literal> with a <literal>WITHOUT OVERLAPS</literal> option
+      is a <emphasis>temporal</emphasis> primary key.
+      The <literal>WITHOUT OVERLAPS</literal> column
+      must be a range type and is used to constrain the record's applicability
+      to just that range (usually a range of dates or timestamps).
+      The main part of the primary key may be repeated elsewhere in the table,
+      as long as records with the same key don't overlap in the
+      <literal>WITHOUT OVERLAPS</literal> column.
+     </para>
+
+     <para>
+      A temporal <literal>PRIMARY KEY</literal> is enforced with an
+      <literal>EXCLUDE</literal> constraint rather than a <literal>UNIQUE</literal>
+      constraint, backed by a GiST index. You may need to install the
+      <xref linkend="btree-gist"/> extension to create temporal primary keys.
      </para>
     </listitem>
    </varlistentry>
@@ -1006,8 +1030,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    <varlistentry>
     <term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal> (column constraint)</term>
 
-   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
-    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] )
+    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH <replaceable class="parameter">matchtype</replaceable> ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ]
     [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal>
@@ -1018,11 +1042,29 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       These clauses specify a foreign key constraint, which requires
       that a group of one or more columns of the new table must only
       contain values that match values in the referenced
-      column(s) of some row of the referenced table.  If the <replaceable
+      column(s) of some row of the referenced table.
+      If the <replaceable
       class="parameter">refcolumn</replaceable> list is omitted, the
       primary key of the <replaceable class="parameter">reftable</replaceable>
       is used.  The referenced columns must be the columns of a non-deferrable
-      unique or primary key constraint in the referenced table.  The user
+      unique or primary key constraint in the referenced table.
+     </para>
+
+     <para>
+      If the last column is marked with <literal>PERIOD</literal>,
+      it must be a range column, and the referenced table
+      must have a temporal primary key.
+      The non-<literal>PERIOD</literal> columns are treated normally
+      (and there must be at least one of them),
+      but the <literal>PERIOD</literal> column is not compared for equality.
+      Instead the constraint is considered satisfied
+      if the referenced table has matching records whose combined ranges completely cover
+      the referencing record.
+      In other words, the reference must have a referent for its entire duration.
+     </para>
+
+     <para>
+      The user
       must have <literal>REFERENCES</literal> permission on the referenced table
       (either the whole table, or the specific referenced columns).  The
       addition of a foreign key constraint requires a
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index dd39a086ce..47afc4ef7b 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -237,6 +237,7 @@ sub ParseData
 	# Scan the input file.
 	while (<$ifd>)
 	{
+    next if /^#/;
 		my $hash_ref;
 
 		if (/{/)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9d9e915979..1d0a26e146 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2430,6 +2430,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
+							  false,	/* contemporal */
 							  is_internal); /* internally constructed? */
 
 	pfree(ccbin);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7223679033..a6ddbcbfdf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1787,6 +1787,7 @@ index_concurrently_set_dead(Oid heapId, Oid indexId)
  *		INDEX_CONSTR_CREATE_UPDATE_INDEX: update the pg_index row
  *		INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS: remove existing dependencies
  *			of index on table's columns
+ *		INDEX_CONSTR_CREATE_TEMPORAL: constraint is for a temporal primary key
  * allow_system_table_mods: allow table to be a system catalog
  * is_internal: index is constructed due to internal process
  */
@@ -1810,11 +1811,13 @@ index_constraint_create(Relation heapRelation,
 	bool		mark_as_primary;
 	bool		islocal;
 	bool		noinherit;
+	bool		is_temporal;
 	int			inhcount;
 
 	deferrable = (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) != 0;
 	initdeferred = (constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) != 0;
 	mark_as_primary = (constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY) != 0;
+	is_temporal = (constr_flags & INDEX_CONSTR_CREATE_TEMPORAL) != 0;
 
 	/* constraint creation support doesn't work while bootstrapping */
 	Assert(!IsBootstrapProcessingMode());
@@ -1889,6 +1892,7 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
+								   is_temporal,	/* contemporal */
 								   is_internal);
 
 	/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 90932be831..f08b68d800 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -75,6 +75,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
+					  bool conTemporal,
 					  bool is_internal)
 {
 	Relation	conDesc;
@@ -183,6 +184,7 @@ CreateConstraintEntry(const char *constraintName,
 	values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
 	values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
 	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+	values[Anum_pg_constraint_contemporal - 1] = BoolGetDatum(conTemporal);
 
 	if (conkeyArray)
 		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89fe92..973de18201 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1001,6 +1001,8 @@ DefineIndex(Oid relationId,
 		constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;
 	if (stmt->initdeferred)
 		constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;
+	if (stmt->istemporal)
+		constr_flags |= INDEX_CONSTR_CREATE_TEMPORAL;
 
 	indexRelationId =
 		index_create(rel, indexRelationName, indexRelationId, parentIndexId,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a13b97164..779e89827b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -333,9 +333,12 @@ static int	transformColumnNameList(Oid relId, List *colList,
 static int	transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 									   List **attnamelist,
 									   int16 *attnums, Oid *atttypids,
+										 Node **periodattname,
+										 int16 *periodattnums, Oid *periodatttypids,
 									   Oid *opclasses);
 static Oid	transformFkeyCheckAttrs(Relation pkrel,
 									int numattrs, int16 *attnums,
+									bool is_temporal, int16 *periodattnums,
 									Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
@@ -343,7 +346,7 @@ static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
 static void validateCheckConstraint(Relation rel, HeapTuple constrtup);
 static void validateForeignKeyConstraint(char *conname,
 										 Relation rel, Relation pkrel,
-										 Oid pkindOid, Oid constraintOid);
+										 Oid pkindOid, Oid constraintOid, bool temporal);
 static void ATController(AlterTableStmt *parsetree,
 						 Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
 						 AlterTableUtilityContext *context);
@@ -446,12 +449,12 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 											Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 											int numfks, int16 *pkattnum, int16 *fkattnum,
 											Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-											bool old_check_ok);
+											bool old_check_ok, bool is_temporal);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
 									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 									int numfks, int16 *pkattnum, int16 *fkattnum,
 									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-									bool old_check_ok, LOCKMODE lockmode);
+									bool old_check_ok, bool is_temporal, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 									   Relation partitionRel);
 static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
@@ -468,6 +471,12 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
 										 Oid parentConstrOid, int numfks,
 										 AttrNumber *mapped_conkey, AttrNumber *confkey,
 										 Oid *conpfeqop);
+static void FindFKComparisonOperators(Constraint *fkconstraint,
+					AlteredTableInfo *tab, int i, int16 *fkattnum,
+					bool *old_check_ok, ListCell **old_pfeqop_item,
+					Oid pktype, Oid fktype, Oid opclass,
+					bool is_temporal, bool for_overlaps,
+					Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 								 DropBehavior behavior,
 								 bool recurse, bool recursing,
@@ -4973,7 +4982,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 				validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
 											 con->refindid,
-											 con->conid);
+											 con->conid,
+											 fkconstraint->fk_period != NULL);
 
 				/*
 				 * No need to mark the constraint row as validated, we did
@@ -8095,6 +8105,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	bool		is_temporal = (fkconstraint->fk_period != NULL);
+	int16		pkperiodattnum = 0;
+	int16		fkperiodattnum = 0;
+	Oid			pkperiodtypoid = 0;
+	Oid			fkperiodtypoid = 0;
 	int			i;
 	int			numfks,
 				numpks;
@@ -8197,6 +8212,14 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
+	if (is_temporal)
+	{
+	  List *fk_period;
+	  fk_period = list_make1(fkconstraint->fk_period);
+	  transformColumnNameList(RelationGetRelid(rel),
+							  fk_period,
+							  &fkperiodattnum, &fkperiodtypoid);
+	}
 
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
@@ -8209,6 +8232,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
 											&fkconstraint->pk_attrs,
 											pkattnum, pktypoid,
+											&fkconstraint->pk_period,
+											&pkperiodattnum, &pkperiodtypoid,
 											opclasses);
 	}
 	else
@@ -8216,8 +8241,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformColumnNameList(RelationGetRelid(pkrel),
 										 fkconstraint->pk_attrs,
 										 pkattnum, pktypoid);
+		if (is_temporal) {
+			List *pk_period = list_make1(fkconstraint->pk_period);
+			transformColumnNameList(RelationGetRelid(pkrel),
+									pk_period,
+									&pkperiodattnum, &pkperiodtypoid);
+		}
 		/* Look for an index matching the column list */
 		indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
+										   is_temporal, &pkperiodattnum,
 										   opclasses);
 	}
 
@@ -8267,6 +8299,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
+	// TODO: Need a check that if one side has a PERIOD the other does too
+
 	/*
 	 * On the strength of a previous constraint, we might avoid scanning
 	 * tables to validate this one.  See below.
@@ -8276,187 +8310,27 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		Oid			pktype = pktypoid[i];
-		Oid			fktype = fktypoid[i];
-		Oid			fktyped;
-		HeapTuple	cla_ht;
-		Form_pg_opclass cla_tup;
-		Oid			amid;
-		Oid			opfamily;
-		Oid			opcintype;
-		Oid			pfeqop;
-		Oid			ppeqop;
-		Oid			ffeqop;
-		int16		eqstrategy;
-		Oid			pfeqop_right;
-
-		/* We need several fields out of the pg_opclass entry */
-		cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
-		if (!HeapTupleIsValid(cla_ht))
-			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
-		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
-		amid = cla_tup->opcmethod;
-		opfamily = cla_tup->opcfamily;
-		opcintype = cla_tup->opcintype;
-		ReleaseSysCache(cla_ht);
-
-		/*
-		 * Check it's a btree; currently this can never fail since no other
-		 * index AMs support unique indexes.  If we ever did have other types
-		 * of unique indexes, we'd need a way to determine which operator
-		 * strategy number is equality.  (Is it reasonable to insist that
-		 * every such index AM use btree's number for equality?)
-		 */
-		if (amid != BTREE_AM_OID)
-			elog(ERROR, "only b-tree indexes are supported for foreign keys");
-		eqstrategy = BTEqualStrategyNumber;
-
-		/*
-		 * There had better be a primary equality operator for the index.
-		 * We'll use it for PK = PK comparisons.
-		 */
-		ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
-									 eqstrategy);
-
-		if (!OidIsValid(ppeqop))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 eqstrategy, opcintype, opcintype, opfamily);
-
-		/*
-		 * Are there equality operators that take exactly the FK type? Assume
-		 * we should look through any domain here.
-		 */
-		fktyped = getBaseType(fktype);
-
-		pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
-									 eqstrategy);
-		if (OidIsValid(pfeqop))
-		{
-			pfeqop_right = fktyped;
-			ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
-										 eqstrategy);
-		}
-		else
-		{
-			/* keep compiler quiet */
-			pfeqop_right = InvalidOid;
-			ffeqop = InvalidOid;
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-		{
-			/*
-			 * Otherwise, look for an implicit cast from the FK type to the
-			 * opcintype, and if found, use the primary equality operator.
-			 * This is a bit tricky because opcintype might be a polymorphic
-			 * type such as ANYARRAY or ANYENUM; so what we have to test is
-			 * whether the two actual column types can be concurrently cast to
-			 * that type.  (Otherwise, we'd fail to reject combinations such
-			 * as int[] and point[].)
-			 */
-			Oid			input_typeids[2];
-			Oid			target_typeids[2];
-
-			input_typeids[0] = pktype;
-			input_typeids[1] = fktype;
-			target_typeids[0] = opcintype;
-			target_typeids[1] = opcintype;
-			if (can_coerce_type(2, input_typeids, target_typeids,
-								COERCION_IMPLICIT))
-			{
-				pfeqop = ffeqop = ppeqop;
-				pfeqop_right = opcintype;
-			}
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("foreign key constraint \"%s\" cannot be implemented",
-							fkconstraint->conname),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of incompatible types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktype),
-							   format_type_be(pktype))));
-
-		if (old_check_ok)
-		{
-			/*
-			 * When a pfeqop changes, revalidate the constraint.  We could
-			 * permit intra-opfamily changes, but that adds subtle complexity
-			 * without any concrete benefit for core types.  We need not
-			 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
-			 */
-			old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
-			old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
-									old_pfeqop_item);
-		}
-		if (old_check_ok)
-		{
-			Oid			old_fktype;
-			Oid			new_fktype;
-			CoercionPathType old_pathtype;
-			CoercionPathType new_pathtype;
-			Oid			old_castfunc;
-			Oid			new_castfunc;
-			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
-												   fkattnum[i] - 1);
-
-			/*
-			 * Identify coercion pathways from each of the old and new FK-side
-			 * column types to the right (foreign) operand type of the pfeqop.
-			 * We may assume that pg_constraint.conkey is not changing.
-			 */
-			old_fktype = attr->atttypid;
-			new_fktype = fktype;
-			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
-										&old_castfunc);
-			new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
-										&new_castfunc);
-
-			/*
-			 * Upon a change to the cast from the FK column to its pfeqop
-			 * operand, revalidate the constraint.  For this evaluation, a
-			 * binary coercion cast is equivalent to no cast at all.  While
-			 * type implementors should design implicit casts with an eye
-			 * toward consistency of operations like equality, we cannot
-			 * assume here that they have done so.
-			 *
-			 * A function with a polymorphic argument could change behavior
-			 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
-			 * when the cast destination is polymorphic, we only avoid
-			 * revalidation if the input type has not changed at all.  Given
-			 * just the core data types and operator classes, this requirement
-			 * prevents no would-be optimizations.
-			 *
-			 * If the cast converts from a base type to a domain thereon, then
-			 * that domain type must be the opcintype of the unique index.
-			 * Necessarily, the primary key column must then be of the domain
-			 * type.  Since the constraint was previously valid, all values on
-			 * the foreign side necessarily exist on the primary side and in
-			 * turn conform to the domain.  Consequently, we need not treat
-			 * domains specially here.
-			 *
-			 * Since we require that all collations share the same notion of
-			 * equality (which they do, because texteq reduces to bitwise
-			 * equality), we don't compare collation here.
-			 *
-			 * We need not directly consider the PK type.  It's necessarily
-			 * binary coercible to the opcintype of the unique index column,
-			 * and ri_triggers.c will only deal with PK datums in terms of
-			 * that opcintype.  Changing the opcintype also changes pfeqop.
-			 */
-			old_check_ok = (new_pathtype == old_pathtype &&
-							new_castfunc == old_castfunc &&
-							(!IsPolymorphicType(pfeqop_right) ||
-							 new_fktype == old_fktype));
-		}
+		FindFKComparisonOperators(
+				fkconstraint, tab, i, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pktypoid[i], fktypoid[i], opclasses[i],
+				is_temporal, false,
+				&pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]);
+	}
+	if (is_temporal) {
+		pkattnum[numpks] = pkperiodattnum;
+		fkattnum[numpks] = fkperiodattnum;
+		pktypoid[numpks] = pkperiodtypoid;
+		fktypoid[numpks] = fkperiodtypoid;
 
-		pfeqoperators[i] = pfeqop;
-		ppeqoperators[i] = ppeqop;
-		ffeqoperators[i] = ffeqop;
+		FindFKComparisonOperators(
+				fkconstraint, tab, numpks, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pkperiodtypoid, fkperiodtypoid, opclasses[numpks],
+				is_temporal, true,
+				&pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]);
+		numfks += 1;
+		numpks += 1;
 	}
 
 	/*
@@ -8472,7 +8346,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
-									 old_check_ok);
+									 old_check_ok,
+									 is_temporal);
 
 	/* Now handle the referencing side. */
 	addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
@@ -8485,6 +8360,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							ppeqoperators,
 							ffeqoperators,
 							old_check_ok,
+							is_temporal,
 							lockmode);
 
 	/*
@@ -8525,7 +8401,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 					   Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks,
 					   int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok,
+					   bool is_temporal)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -8607,6 +8484,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
+									  is_temporal,
 									  false);	/* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -8681,7 +8559,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
-								   old_check_ok);
+								   old_check_ok, is_temporal);
 
 			/* Done -- clean up (but keep the lock) */
 			table_close(partRel, NoLock);
@@ -8730,7 +8608,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 						Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode)
+						bool old_check_ok, bool is_temporal, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
 
@@ -8875,6 +8753,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
+									  is_temporal,
 									  false);
 
 			/*
@@ -8901,6 +8780,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									ppeqoperators,
 									ffeqoperators,
 									old_check_ok,
+									is_temporal,
 									lockmode);
 
 			table_close(partition, NoLock);
@@ -9039,6 +8919,7 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 								   conppeqop,
 								   conffeqop);
 		Assert(numfks == attmap->maplen);
+
 		for (int i = 0; i < numfks; i++)
 			mapped_confkey[i] = attmap->attnums[confkey[i] - 1];
 
@@ -9084,7 +8965,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
-							   true);
+							   true,
+							   constrForm->contemporal);
 
 		table_close(fkRel, NoLock);
 		ReleaseSysCache(tuple);
@@ -9277,6 +9159,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
+								  constrForm->contemporal,
 								  true);
 
 		/* Set up partition dependencies for the new constraint */
@@ -9306,11 +9189,214 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conppeqop,
 								conffeqop,
 								false,	/* no old check exists */
+								constrForm->contemporal,
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
 	}
 }
 
+static void
+FindFKComparisonOperators(Constraint *fkconstraint,
+		AlteredTableInfo *tab,
+		int i,
+		int16 *fkattnum,
+		bool *old_check_ok,
+		ListCell **old_pfeqop_item,
+		Oid pktype, Oid fktype, Oid opclass,
+		bool is_temporal, bool for_overlaps,
+		Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut)
+{
+	Oid			fktyped;
+	HeapTuple	cla_ht;
+	Form_pg_opclass cla_tup;
+	Oid			amid;
+	Oid			opfamily;
+	Oid			opcintype;
+	Oid			pfeqop;
+	Oid			ppeqop;
+	Oid			ffeqop;
+	int16		eqstrategy;
+	Oid			pfeqop_right;
+
+	/* We need several fields out of the pg_opclass entry */
+	cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(cla_ht))
+		elog(ERROR, "cache lookup failed for opclass %u", opclass);
+	cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+	amid = cla_tup->opcmethod;
+	opfamily = cla_tup->opcfamily;
+	opcintype = cla_tup->opcintype;
+	ReleaseSysCache(cla_ht);
+
+	if (is_temporal)
+	{
+		if (amid != GIST_AM_OID)
+			elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
+		eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
+	}
+	else
+	{
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other types
+		 * of unique indexes, we'd need a way to determine which operator
+		 * strategy number is equality.  (Is it reasonable to insist that
+		 * every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+	}
+
+	/*
+	 * There had better be a primary equality operator for the index.
+	 * We'll use it for PK = PK comparisons.
+	 */
+	ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
+								 eqstrategy);
+
+	if (!OidIsValid(ppeqop))
+		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+			 eqstrategy, opcintype, opcintype, opfamily);
+
+	/*
+	 * Are there equality operators that take exactly the FK type? Assume
+	 * we should look through any domain here.
+	 */
+	fktyped = getBaseType(fktype);
+
+	pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+								 eqstrategy);
+	if (OidIsValid(pfeqop))
+	{
+		pfeqop_right = fktyped;
+		ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+									 eqstrategy);
+	}
+	else
+	{
+		/* keep compiler quiet */
+		pfeqop_right = InvalidOid;
+		ffeqop = InvalidOid;
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		/*
+		 * Otherwise, look for an implicit cast from the FK type to the
+		 * opcintype, and if found, use the primary equality operator.
+		 * This is a bit tricky because opcintype might be a polymorphic
+		 * type such as ANYARRAY or ANYENUM; so what we have to test is
+		 * whether the two actual column types can be concurrently cast to
+		 * that type.  (Otherwise, we'd fail to reject combinations such
+		 * as int[] and point[].)
+		 */
+		Oid			input_typeids[2];
+		Oid			target_typeids[2];
+
+		input_typeids[0] = pktype;
+		input_typeids[1] = fktype;
+		target_typeids[0] = opcintype;
+		target_typeids[1] = opcintype;
+		if (can_coerce_type(2, input_typeids, target_typeids,
+							COERCION_IMPLICIT))
+		{
+			pfeqop = ffeqop = ppeqop;
+			pfeqop_right = opcintype;
+		}
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("foreign key constraint \"%s\" cannot be implemented",
+						fkconstraint->conname),
+				 errdetail("Key columns \"%s\" and \"%s\" "
+						   "are of incompatible types: %s and %s.",
+						   strVal(list_nth(fkconstraint->fk_attrs, i)),
+						   strVal(list_nth(fkconstraint->pk_attrs, i)),
+						   format_type_be(fktype),
+						   format_type_be(pktype))));
+
+	if (*old_check_ok)
+	{
+		/*
+		 * When a pfeqop changes, revalidate the constraint.  We could
+		 * permit intra-opfamily changes, but that adds subtle complexity
+		 * without any concrete benefit for core types.  We need not
+		 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
+		 */
+		*old_check_ok = (pfeqop == lfirst_oid(*old_pfeqop_item));
+		*old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
+								*old_pfeqop_item);
+	}
+	if (*old_check_ok)
+	{
+		Oid			old_fktype;
+		Oid			new_fktype;
+		CoercionPathType old_pathtype;
+		CoercionPathType new_pathtype;
+		Oid			old_castfunc;
+		Oid			new_castfunc;
+		Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+											   fkattnum[i] - 1);
+
+		/*
+		 * Identify coercion pathways from each of the old and new FK-side
+		 * column types to the right (foreign) operand type of the pfeqop.
+		 * We may assume that pg_constraint.conkey is not changing.
+		 */
+		old_fktype = attr->atttypid;
+		new_fktype = fktype;
+		old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
+									&old_castfunc);
+		new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
+									&new_castfunc);
+
+		/*
+		 * Upon a change to the cast from the FK column to its pfeqop
+		 * operand, revalidate the constraint.  For this evaluation, a
+		 * binary coercion cast is equivalent to no cast at all.  While
+		 * type implementors should design implicit casts with an eye
+		 * toward consistency of operations like equality, we cannot
+		 * assume here that they have done so.
+		 *
+		 * A function with a polymorphic argument could change behavior
+		 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
+		 * when the cast destination is polymorphic, we only avoid
+		 * revalidation if the input type has not changed at all.  Given
+		 * just the core data types and operator classes, this requirement
+		 * prevents no would-be optimizations.
+		 *
+		 * If the cast converts from a base type to a domain thereon, then
+		 * that domain type must be the opcintype of the unique index.
+		 * Necessarily, the primary key column must then be of the domain
+		 * type.  Since the constraint was previously valid, all values on
+		 * the foreign side necessarily exist on the primary side and in
+		 * turn conform to the domain.  Consequently, we need not treat
+		 * domains specially here.
+		 *
+		 * Since we require that all collations share the same notion of
+		 * equality (which they do, because texteq reduces to bitwise
+		 * equality), we don't compare collation here.
+		 *
+		 * We need not directly consider the PK type.  It's necessarily
+		 * binary coercible to the opcintype of the unique index column,
+		 * and ri_triggers.c will only deal with PK datums in terms of
+		 * that opcintype.  Changing the opcintype also changes pfeqop.
+		 */
+		*old_check_ok = (new_pathtype == old_pathtype &&
+						new_castfunc == old_castfunc &&
+						(!IsPolymorphicType(pfeqop_right) ||
+						 new_fktype == old_fktype));
+
+	}
+
+	*pfeqopOut = pfeqop;
+	*ppeqopOut = ppeqop;
+	*ffeqopOut = ffeqop;
+}
+
 /*
  * When the parent of a partition receives [the referencing side of] a foreign
  * key, we must propagate that foreign key to the partition.  However, the
@@ -9691,7 +9777,7 @@ ATExecValidateConstraint(Relation rel, char *constrName, bool recurse,
 
 			validateForeignKeyConstraint(constrName, rel, refrel,
 										 con->conindid,
-										 con->oid);
+										 con->oid, con->contemporal);
 			table_close(refrel, NoLock);
 
 			/*
@@ -9824,10 +9910,12 @@ transformColumnNameList(Oid relId, List *colList,
  *
  *	Look up the names, attnums, and types of the primary key attributes
  *	for the pkrel.  Also return the index OID and index opclasses of the
- *	index supporting the primary key.
+ *	index supporting the primary key.  If this is a temporal primary key,
+ *	also set the WITHOUT OVERLAPS attribute name, attnum, and atttypid.
  *
  *	All parameters except pkrel are output parameters.  Also, the function
- *	return value is the number of attributes in the primary key.
+ *	return value is the number of attributes in the primary key,
+ *	not including the WITHOUT OVERLAPS if any.
  *
  *	Used when the column list in the REFERENCES specification is omitted.
  */
@@ -9835,6 +9923,8 @@ static int
 transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses)
 {
 	List	   *indexoidlist;
@@ -9902,35 +9992,50 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 	/*
 	 * Now build the list of PK attributes from the indkey definition (we
 	 * assume a primary key cannot have expressional elements)
+	 * TODO: range expressions will be how we support PERIODs though.
 	 */
 	*attnamelist = NIL;
 	for (i = 0; i < indexStruct->indnkeyatts; i++)
 	{
 		int			pkattno = indexStruct->indkey.values[i];
 
-		attnums[i] = pkattno;
-		atttypids[i] = attnumTypeId(pkrel, pkattno);
-		opclasses[i] = indclass->values[i];
-		*attnamelist = lappend(*attnamelist,
-							   makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		if (i == indexStruct->indnkeyatts - 1 && indexStruct->indisexclusion)
+		{
+			periodattnums[0] = pkattno;
+			periodatttypids[0] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*periodattname = (Node *)makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))));
+		}
+		else
+		{
+			attnums[i] = pkattno;
+			atttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*attnamelist = lappend(*attnamelist,
+								  makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		}
 	}
 
 	ReleaseSysCache(indexTuple);
 
-	return i;
+	if (indexStruct->indisexclusion) return i - 1;
+	else return i;
 }
 
 /*
  * transformFkeyCheckAttrs -
  *
  *	Make sure that the attributes of a referenced table belong to a unique
- *	(or primary key) constraint.  Return the OID of the index supporting
- *	the constraint, as well as the opclasses associated with the index
+ *	(or primary key) constraint.  Or if this is a temporal foreign key
+ *	the primary key should be an exclusion constraint instead.
+ *	Return the OID of the index supporting the constraint,
+ *	as well as the opclasses associated with the index
  *	columns.
  */
 static Oid
 transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
 						Oid *opclasses) /* output parameter */
 {
 	Oid			indexoid = InvalidOid;
@@ -9957,6 +10062,10 @@ transformFkeyCheckAttrs(Relation pkrel,
 						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 						 errmsg("foreign key referenced-columns list must not contain duplicates")));
 		}
+		if (is_temporal && attnums[i] == periodattnums[0])
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+					 errmsg("foreign key referenced-columns list must not contain duplicates")));
 	}
 
 	/*
@@ -9978,12 +10087,16 @@ transformFkeyCheckAttrs(Relation pkrel,
 		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		/*
-		 * Must have the right number of columns; must be unique and not a
+		 * Must have the right number of columns; must be unique
+		 * (or if temporal then exclusion instead) and not a
 		 * partial index; forget it if there are any expressions, too. Invalid
 		 * indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
-			indexStruct->indisunique &&
+		if ((is_temporal
+			  ? (indexStruct->indnkeyatts == numattrs + 1 &&
+				 indexStruct->indisexclusion)
+			  : (indexStruct->indnkeyatts == numattrs &&
+				 indexStruct->indisunique)) &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
 			heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
@@ -10023,6 +10136,19 @@ transformFkeyCheckAttrs(Relation pkrel,
 				if (!found)
 					break;
 			}
+			if (is_temporal)
+			{
+				found = false;
+				for (j = 0; j < numattrs + 1; j++)
+				{
+					if (periodattnums[0] == indexStruct->indkey.values[j])
+					{
+						opclasses[numattrs] = indclass->values[j];
+						found = true;
+						break;
+					}
+				}
+			}
 
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
@@ -10213,7 +10339,8 @@ validateForeignKeyConstraint(char *conname,
 							 Relation rel,
 							 Relation pkrel,
 							 Oid pkindOid,
-							 Oid constraintOid)
+							 Oid constraintOid,
+							 bool temporal)
 {
 	TupleTableSlot *slot;
 	TableScanDesc scan;
@@ -10243,8 +10370,10 @@ validateForeignKeyConstraint(char *conname,
 	/*
 	 * See if we can do it with a single LEFT JOIN query.  A false result
 	 * indicates we must proceed with the fire-the-trigger method.
+	 * We can't do a LEFT JOIN for temporal FKs yet,
+	 * but we can once we support temporal left joins.
 	 */
-	if (RI_Initial_Check(&trig, rel, pkrel))
+	if (!temporal && RI_Initial_Check(&trig, rel, pkrel))
 		return;
 
 	/*
@@ -10306,6 +10435,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 					 Oid constraintOid, Oid indexOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
+	bool is_temporal = fkconstraint->fk_period;
 
 	/*
 	 * Note: for a self-referential FK (referencing and referenced tables are
@@ -10317,7 +10447,10 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = "RI_ConstraintTrigger_c";
+	if (is_temporal)
+		fk_trigger->trigname = "TRI_ConstraintTrigger_c";
+	else
+		fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
 	fk_trigger->timing = TRIGGER_TYPE_AFTER;
@@ -10325,12 +10458,18 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	/* Either ON INSERT or ON UPDATE */
 	if (on_insert)
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_ins");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
 		fk_trigger->events = TRIGGER_TYPE_INSERT;
 	}
 	else
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_upd");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
 		fk_trigger->events = TRIGGER_TYPE_UPDATE;
 	}
 
@@ -10376,37 +10515,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_del_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_del");
+				break;
+				/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_del");
+				break;
+				*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
@@ -10432,37 +10612,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_upd_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_upd");
+				break;
+			/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_upd");
+				break;
+			*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index b408efb11e..be31226725 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -735,6 +735,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* noinherit */
+											  false, /* contemporal */
 											  isInternal);	/* is_internal */
 	}
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 455786128b..a6b74c9f51 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3413,6 +3413,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
+							  false,	/* contemporal */
 							  false);	/* is_internal */
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fbc2b..7d04844f3f 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1322,6 +1322,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_projectReturning = NULL;
 	resultRelInfo->ri_onConflictArbiterIndexes = NIL;
 	resultRelInfo->ri_onConflict = NULL;
+	resultRelInfo->ri_forPortionOf = NULL;
 	resultRelInfo->ri_ReturningSlot = NULL;
 	resultRelInfo->ri_TrigOldSlot = NULL;
 	resultRelInfo->ri_TrigNewSlot = NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4322..35c137a397 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -55,6 +55,7 @@
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/memutils.h"
+#include "utils/rangetypes.h"
 #include "utils/rel.h"
 
 
@@ -614,8 +615,10 @@ ExecInsert(ModifyTableState *mtstate,
 
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
+			{
 				recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL,
 													   NIL);
+			}
 		}
 	}
 
@@ -676,6 +679,119 @@ ExecInsert(ModifyTableState *mtstate,
 	return result;
 }
 
+/*
+ * Insert tuples for the untouched timespan of a row in a FOR PORTION OF UPDATE/DELETE
+ */
+static void
+ExecForPortionOfLeftovers(ModifyTableState *mtstate, EState *estate, ResultRelInfo *resultRelInfo, ItemPointer tupleid, TupleTableSlot *slot, TupleTableSlot *planSlot)
+{
+	// TODO: figure out if I need to make a copy of slot somehow in order to insert it....
+
+	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+	ForPortionOfExpr *forPortionOf = (ForPortionOfExpr *) node->forPortionOf;
+	Datum	oldRange;
+	Datum	targetRange;
+	RangeType *oldRangeType;
+	RangeType *targetRangeType;
+	RangeType *leftoverRangeType1;
+	RangeType *leftoverRangeType2;
+	Oid		rangeTypeOid;
+	bool	isNull;
+	TypeCacheEntry *typcache;
+	TupleTableSlot *oldtupleSlot = resultRelInfo->ri_forPortionOf->fp_Existing;
+	TupleTableSlot *leftoverTuple1 = resultRelInfo->ri_forPortionOf->fp_Leftover1;
+	TupleTableSlot *leftoverTuple2 = resultRelInfo->ri_forPortionOf->fp_Leftover2;
+	char *rangestr;
+
+	/* Get the range of the existing pre-UPDATE/DELETE tuple */
+
+	// TODO: Seems like we shouldn't have to do this,
+	// because the old tuple should already be available somehow?
+	// But this is what triggers do.... (Are you sure this is how they get the OLD tuple?)
+	// And even if we do have to do this, is SnapshotAny really correct?
+	// Shouldn't it be the snapshot of the UPDATE?
+	if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, tupleid, SnapshotAny,
+									   oldtupleSlot))
+		elog(ERROR, "failed to fetch tuple for FOR PORTION OF");
+
+	oldRange = slot_getattr(oldtupleSlot, forPortionOf->range_attno, &isNull);
+	if (isNull)
+		elog(ERROR, "found a NULL range in a temporal table");
+	oldRangeType = DatumGetRangeTypeP(oldRange);
+
+	/* Evaluate the target range if we haven't yet */
+
+	if (resultRelInfo->ri_forPortionOf->fp_targetRange)
+		targetRangeType = resultRelInfo->ri_forPortionOf->fp_targetRange;
+	else
+	{
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		econtext->ecxt_scantuple = slot;
+
+		ExprState *exprState = ExecPrepareExpr((Expr *) forPortionOf->targetRange, estate);
+		targetRange = ExecEvalExpr(exprState, econtext, &isNull);
+
+		if (isNull) elog(ERROR, "Got a NULL FOR PORTION OF target range");
+		targetRangeType = DatumGetRangeTypeP(targetRange);
+		resultRelInfo->ri_forPortionOf->fp_targetRange = targetRangeType;
+	}
+
+
+	/*
+	 * Get the range's type cache entry. This is worth caching for the whole UPDATE
+	 * like range functions do.
+	 */
+
+	typcache = resultRelInfo->ri_forPortionOf->fp_rangetypcache;
+	if (typcache == NULL)
+	{
+		rangeTypeOid = RangeTypeGetOid(oldRangeType);
+		typcache = lookup_type_cache(rangeTypeOid, TYPECACHE_RANGE_INFO);
+		if (typcache->rngelemtype == NULL)
+			elog(ERROR, "type %u is not a range type", rangeTypeOid);
+		resultRelInfo->ri_forPortionOf->fp_rangetypcache = typcache;
+	}
+
+	/* Get the ranges to the left/right of the targeted range. */
+
+	range_leftover_internal(typcache, oldRangeType, targetRangeType, &leftoverRangeType1,
+			&leftoverRangeType2);
+
+	/* Insert a copy of the tuple with the lower leftover range */
+
+	if (!RangeIsEmpty(leftoverRangeType1))
+	{
+		MinimalTuple oldtuple = ExecFetchSlotMinimalTuple(oldtupleSlot, NULL);
+		ExecForceStoreMinimalTuple(oldtuple, leftoverTuple1, false);
+
+		leftoverTuple1->tts_values[forPortionOf->range_attno - 1] = RangeTypePGetDatum(leftoverRangeType1);
+		leftoverTuple1->tts_isnull[forPortionOf->range_attno - 1] = false;
+
+		ExecMaterializeSlot(leftoverTuple1);
+
+		// TODO: tuple routing?
+		ExecInsert(mtstate, leftoverTuple1, planSlot,
+				   estate, node->canSetTag);
+	}
+
+	/* Insert a copy of the tuple with the upper leftover range */
+
+	if (!RangeIsEmpty(leftoverRangeType2))
+	{
+		MinimalTuple oldtuple = ExecFetchSlotMinimalTuple(oldtupleSlot, NULL);
+		ExecForceStoreMinimalTuple(oldtuple, leftoverTuple2, false);
+
+		leftoverTuple2->tts_values[forPortionOf->range_attno - 1] = RangeTypePGetDatum(leftoverRangeType2);
+		leftoverTuple2->tts_isnull[forPortionOf->range_attno - 1] = false;
+
+		ExecMaterializeSlot(leftoverTuple2);
+
+		// TODO: tuple routing?
+		ExecInsert(mtstate, leftoverTuple2, planSlot,
+				   estate, node->canSetTag);
+	}
+}
+
 /* ----------------------------------------------------------------
  *		ExecDelete
  *
@@ -717,6 +833,8 @@ ExecDelete(ModifyTableState *mtstate,
 	TM_FailureData tmfd;
 	TupleTableSlot *slot = NULL;
 	TransitionCaptureState *ar_delete_trig_tcs;
+	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+	ForPortionOfExpr *forPortionOf = (ForPortionOfExpr *) node->forPortionOf;
 
 	if (tupleDeleted)
 		*tupleDeleted = false;
@@ -987,6 +1105,13 @@ ldelete:;
 		ar_delete_trig_tcs = NULL;
 	}
 
+	/*
+	 * Compute leftovers in FOR PORTION OF
+	 */
+	// TODO: Skip this for FDW deletes?
+	if (forPortionOf)
+		ExecForPortionOfLeftovers(mtstate, estate, resultRelInfo, tupleid, slot, planSlot);
+
 	/* AFTER ROW DELETE Triggers */
 	ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
 						 ar_delete_trig_tcs);
@@ -1074,6 +1199,8 @@ ExecUpdate(ModifyTableState *mtstate,
 	TM_FailureData tmfd;
 	List	   *recheckIndexes = NIL;
 	TupleConversionMap *saved_tcs_map = NULL;
+	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+	ForPortionOfExpr *forPortionOf = (ForPortionOfExpr *) node->forPortionOf;
 
 	/*
 	 * abort the operation if not running transactions
@@ -1098,6 +1225,8 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;		/* "do nothing" */
 	}
 
+	// TODO: Is there an argument that we should set the temporal bounds
+	// before calling the INSTEAD OF trigger?? What do other dbs do?
 	/* INSTEAD OF ROW UPDATE Triggers */
 	if (resultRelInfo->ri_TrigDesc &&
 		resultRelInfo->ri_TrigDesc->trig_update_instead_row)
@@ -1475,6 +1604,13 @@ lreplace:;
 	if (canSetTag)
 		(estate->es_processed)++;
 
+	/*
+	 * Compute leftovers in FOR PORTION OF
+	 */
+	// TODO: Skip this for FDW updates?
+	if (forPortionOf)
+		ExecForPortionOfLeftovers(mtstate, estate, resultRelInfo, tupleid, slot, planSlot);
+
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, slot,
 						 recheckIndexes,
@@ -2367,7 +2503,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		 * query.
 		 */
 		if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
-			operation != CMD_DELETE &&
+			(operation != CMD_DELETE || node->forPortionOf != NULL) &&
 			resultRelInfo->ri_IndexRelationDescs == NULL)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
@@ -2589,6 +2725,31 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	}
 
 	/*
+	 * If needed, initialize ... TODO ... for FOR PORTION OF.
+	 */
+	if (node->forPortionOf)
+	{
+		// TODO: Is tupDesc the right thing?
+		TupleDesc tupDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+		/* create state for FOR PORTION OF operation */
+		resultRelInfo->ri_forPortionOf = makeNode(ForPortionOfState);
+
+		/* initialize slot for the existing tuple */
+		resultRelInfo->ri_forPortionOf->fp_Existing =
+			table_slot_create(resultRelInfo->ri_RelationDesc,
+					&mtstate->ps.state->es_tupleTable);
+
+		/* Create the tuple slots for INSERTing the leftovers. */
+		resultRelInfo->ri_forPortionOf->fp_Leftover1 =
+			ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+								   &TTSOpsVirtual);
+		resultRelInfo->ri_forPortionOf->fp_Leftover2 =
+			ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+								   &TTSOpsVirtual);
+	}
+
+	/*
 	 * If we have any secondary relations in an UPDATE or DELETE, they need to
 	 * be treated like non-locked relations in SELECT FOR UPDATE, ie, the
 	 * EvalPlanQual mechanism needs to be told about them.  Locate the
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index eaab97f753..efcc1c26ff 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -217,6 +217,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
 	COPY_SCALAR_FIELD(onConflictAction);
+	COPY_NODE_FIELD(forPortionOf);
 	COPY_NODE_FIELD(arbiterIndexes);
 	COPY_NODE_FIELD(onConflictSet);
 	COPY_NODE_FIELD(onConflictWhere);
@@ -2209,6 +2210,28 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyForPortionOfExpr
+ */
+static ForPortionOfExpr *
+_copyForPortionOfExpr(const ForPortionOfExpr *from)
+{
+	ForPortionOfExpr *newnode = makeNode(ForPortionOfExpr);
+
+	COPY_SCALAR_FIELD(range_attno);
+	COPY_STRING_FIELD(range_name);
+	COPY_NODE_FIELD(range);
+	COPY_NODE_FIELD(startCol);
+	COPY_NODE_FIELD(endCol);
+	COPY_NODE_FIELD(targetStart);
+	COPY_NODE_FIELD(targetEnd);
+	COPY_NODE_FIELD(targetRange);
+	COPY_NODE_FIELD(overlapsExpr);
+	COPY_NODE_FIELD(rangeSet);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						pathnodes.h copy functions
  *
@@ -2549,6 +2572,19 @@ _copyOnConflictClause(const OnConflictClause *from)
 	return newnode;
 }
 
+static ForPortionOfClause *
+_copyForPortionOfClause(const ForPortionOfClause *from)
+{
+	ForPortionOfClause *newnode = makeNode(ForPortionOfClause);
+
+	COPY_STRING_FIELD(range_name);
+	COPY_SCALAR_FIELD(range_name_location);
+	COPY_NODE_FIELD(target_start);
+	COPY_NODE_FIELD(target_end);
+
+	return newnode;
+}
+
 static CommonTableExpr *
 _copyCommonTableExpr(const CommonTableExpr *from)
 {
@@ -2934,12 +2970,15 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(where_clause);
 	COPY_NODE_FIELD(pktable);
 	COPY_NODE_FIELD(fk_attrs);
+	COPY_NODE_FIELD(fk_period);
 	COPY_NODE_FIELD(pk_attrs);
+	COPY_NODE_FIELD(pk_period);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
@@ -3481,6 +3520,7 @@ _copyIndexStmt(const IndexStmt *from)
 	COPY_SCALAR_FIELD(unique);
 	COPY_SCALAR_FIELD(primary);
 	COPY_SCALAR_FIELD(isconstraint);
+	COPY_SCALAR_FIELD(istemporal);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
 	COPY_SCALAR_FIELD(transformed);
@@ -5097,6 +5137,9 @@ copyObjectImpl(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_ForPortionOfExpr:
+			retval = _copyForPortionOfExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5625,6 +5668,9 @@ copyObjectImpl(const void *from)
 		case T_OnConflictClause:
 			retval = _copyOnConflictClause(from);
 			break;
+		case T_ForPortionOfClause:
+			retval = _copyForPortionOfClause(from);
+			break;
 		case T_CommonTableExpr:
 			retval = _copyCommonTableExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 88b912977e..a2be1dfeb4 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -818,6 +818,23 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
 	return true;
 }
 
+static bool
+_equalForPortionOfExpr(const ForPortionOfExpr *a, const ForPortionOfExpr *b)
+{
+	COMPARE_SCALAR_FIELD(range_attno);
+	COMPARE_STRING_FIELD(range_name);
+	COMPARE_NODE_FIELD(range);
+	COMPARE_NODE_FIELD(startCol);
+	COMPARE_NODE_FIELD(endCol);
+	COMPARE_NODE_FIELD(targetStart);
+	COMPARE_NODE_FIELD(targetEnd);
+	COMPARE_NODE_FIELD(targetRange);
+	COMPARE_NODE_FIELD(overlapsExpr);
+	COMPARE_NODE_FIELD(rangeSet);
+
+	return true;
+}
+
 /*
  * Stuff from pathnodes.h
  */
@@ -2826,6 +2843,17 @@ _equalOnConflictClause(const OnConflictClause *a, const OnConflictClause *b)
 }
 
 static bool
+_equalForPortionOfClause(const ForPortionOfClause *a, const ForPortionOfClause *b)
+{
+	COMPARE_STRING_FIELD(range_name);
+	COMPARE_SCALAR_FIELD(range_name_location);
+	COMPARE_NODE_FIELD(target_start);
+	COMPARE_NODE_FIELD(target_end);
+
+	return true;
+}
+
+static bool
 _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 {
 	COMPARE_STRING_FIELD(ctename);
@@ -3201,6 +3229,9 @@ equal(const void *a, const void *b)
 		case T_OnConflictExpr:
 			retval = _equalOnConflictExpr(a, b);
 			break;
+		case T_ForPortionOfExpr:
+			retval = _equalForPortionOfExpr(a, b);
+			break;
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
@@ -3719,6 +3750,9 @@ equal(const void *a, const void *b)
 		case T_OnConflictClause:
 			retval = _equalOnConflictClause(a, b);
 			break;
+		case T_ForPortionOfClause:
+			retval = _equalForPortionOfClause(a, b);
+			break;
 		case T_CommonTableExpr:
 			retval = _equalCommonTableExpr(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e084c3f069..c742bb7e8c 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -418,9 +418,11 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
 	WRITE_ENUM_FIELD(onConflictAction, OnConflictAction);
+	WRITE_NODE_FIELD(forPortionOf);
 	WRITE_NODE_FIELD(arbiterIndexes);
 	WRITE_NODE_FIELD(onConflictSet);
 	WRITE_NODE_FIELD(onConflictWhere);
+	// TODO: add things for ForPortionOf
 	WRITE_UINT_FIELD(exclRelRTI);
 	WRITE_NODE_FIELD(exclRelTlist);
 }
@@ -1688,6 +1690,23 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outForPortionOfExpr(StringInfo str, const ForPortionOfExpr *node)
+{
+	WRITE_NODE_TYPE("FORPORTIONOFEXPR");
+
+	WRITE_INT_FIELD(range_attno);
+	WRITE_STRING_FIELD(range_name);
+	WRITE_NODE_FIELD(range);
+	WRITE_NODE_FIELD(startCol);
+	WRITE_NODE_FIELD(endCol);
+	WRITE_NODE_FIELD(targetStart);
+	WRITE_NODE_FIELD(targetEnd);
+	WRITE_NODE_FIELD(targetRange);
+	WRITE_NODE_FIELD(overlapsExpr);
+	WRITE_NODE_FIELD(rangeSet);
+}
+
 /*****************************************************************************
  *
  *	Stuff from pathnodes.h.
@@ -3972,6 +3991,9 @@ outNode(StringInfo str, const void *obj)
 			case T_OnConflictExpr:
 				_outOnConflictExpr(str, obj);
 				break;
+			case T_ForPortionOfExpr:
+				_outForPortionOfExpr(str, obj);
+				break;
 			case T_Path:
 				_outPath(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index d5b23a3479..a7acb58764 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1374,6 +1374,27 @@ _readAppendRelInfo(void)
  */
 
 /*
+ * _readForPortionOfExpr
+ */
+static ForPortionOfExpr *
+_readForPortionOfExpr(void)
+{
+	READ_LOCALS(ForPortionOfExpr);
+
+	READ_INT_FIELD(range_attno);
+	READ_STRING_FIELD(range_name);
+	READ_NODE_FIELD(range);
+	READ_NODE_FIELD(startCol);
+	READ_NODE_FIELD(endCol);
+	READ_NODE_FIELD(targetStart);
+	READ_NODE_FIELD(targetEnd);
+	READ_NODE_FIELD(targetRange);
+	READ_NODE_FIELD(overlapsExpr);
+	READ_NODE_FIELD(rangeSet);
+	READ_DONE();
+}
+
+/*
  * _readRangeTblEntry
  */
 static RangeTblEntry *
@@ -1648,6 +1669,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
 	READ_ENUM_FIELD(onConflictAction, OnConflictAction);
+	READ_NODE_FIELD(forPortionOf);
 	READ_NODE_FIELD(arbiterIndexes);
 	READ_NODE_FIELD(onConflictSet);
 	READ_NODE_FIELD(onConflictWhere);
@@ -2723,6 +2745,8 @@ parseNodeString(void)
 		return_value = _readOnConflictExpr();
 	else if (MATCH("APPENDRELINFO", 13))
 		return_value = _readAppendRelInfo();
+	else if (MATCH("FORPORTIONOFEXPR", 16))
+		return_value = _readForPortionOfExpr();
 	else if (MATCH("RTE", 3))
 		return_value = _readRangeTblEntry();
 	else if (MATCH("RANGETBLFUNCTION", 16))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index fc25908dc6..7425c84b81 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -289,7 +289,8 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
 									 bool partColsUpdated,
 									 List *resultRelations, List *subplans, List *subroots,
 									 List *withCheckOptionLists, List *returningLists,
-									 List *rowMarks, OnConflictExpr *onconflict, int epqParam);
+									 List *rowMarks, OnConflictExpr *onconflict,
+									 ForPortionOfExpr *forPortionOf, int epqParam);
 static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
 											 GatherMergePath *best_path);
 
@@ -2631,6 +2632,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->returningLists,
 							best_path->rowMarks,
 							best_path->onconflict,
+							best_path->forPortionOf,
 							best_path->epqParam);
 
 	copy_generic_path_info(&plan->plan, &best_path->path);
@@ -6624,7 +6626,8 @@ make_modifytable(PlannerInfo *root,
 				 bool partColsUpdated,
 				 List *resultRelations, List *subplans, List *subroots,
 				 List *withCheckOptionLists, List *returningLists,
-				 List *rowMarks, OnConflictExpr *onconflict, int epqParam)
+				 List *rowMarks, OnConflictExpr *onconflict,
+				 ForPortionOfExpr *forPortionOf, int epqParam)
 {
 	ModifyTable *node = makeNode(ModifyTable);
 	List	   *fdw_private_list;
@@ -6681,6 +6684,7 @@ make_modifytable(PlannerInfo *root,
 		node->exclRelRTI = onconflict->exclRelIndex;
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
+	node->forPortionOf = (Node *) forPortionOf;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index b44efd6314..7b55718293 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1799,6 +1799,7 @@ inheritance_planner(PlannerInfo *root)
 									 returningLists,
 									 rowMarks,
 									 NULL,
+									 parse->forPortionOf,
 									 assign_special_exec_param(root)));
 }
 
@@ -2382,6 +2383,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										returningLists,
 										rowMarks,
 										parse->onConflict,
+										parse->forPortionOf,
 										assign_special_exec_param(root));
 		}
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index d9ce516211..0fb867ba92 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3450,7 +3450,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						List *subroots,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
-						int epqParam)
+						ForPortionOfExpr *forPortionOf, int epqParam)
 {
 	ModifyTablePath *pathnode = makeNode(ModifyTablePath);
 	double		total_size;
@@ -3521,6 +3521,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
 	pathnode->onconflict = onconflict;
+	pathnode->forPortionOf = forPortionOf;
 	pathnode->epqParam = epqParam;
 
 	return pathnode;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412842..aa4ade34c4 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -42,9 +42,12 @@
 #include "parser/parse_param.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
+#include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -58,6 +61,9 @@ static List *transformInsertRow(ParseState *pstate, List *exprlist,
 								bool strip_indirection);
 static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
 												 OnConflictClause *onConflictClause);
+static ForPortionOfExpr *transformForPortionOfClause(ParseState *pstate,
+													 int rtindex,
+													 ForPortionOfClause *forPortionOfClause);
 static int	count_rowexpr_columns(ParseState *pstate, Node *expr);
 static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
 static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
@@ -69,7 +75,8 @@ static void determineRecursiveColTypes(ParseState *pstate,
 static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
 static List *transformReturningList(ParseState *pstate, List *returningList);
 static List *transformUpdateTargetList(ParseState *pstate,
-									   List *targetList);
+									   List *targetList,
+									   ForPortionOfExpr *forPortionOf);
 static Query *transformDeclareCursorStmt(ParseState *pstate,
 										 DeclareCursorStmt *stmt);
 static Query *transformExplainStmt(ParseState *pstate,
@@ -398,6 +405,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
 	ParseNamespaceItem *nsitem;
+	Node	   *whereClause;
 	Node	   *qual;
 
 	qry->commandType = CMD_DELETE;
@@ -436,7 +444,20 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	nsitem->p_lateral_only = false;
 	nsitem->p_lateral_ok = true;
 
-	qual = transformWhereClause(pstate, stmt->whereClause,
+	if (stmt->forPortionOf)
+		qry->forPortionOf = transformForPortionOfClause(pstate, qry->resultRelation, stmt->forPortionOf);
+
+	// TODO: DRY with UPDATE
+	if (stmt->forPortionOf)
+	{
+		if (stmt->whereClause)
+			whereClause = (Node *) makeBoolExpr(AND_EXPR, list_make2(qry->forPortionOf->overlapsExpr, stmt->whereClause), -1);
+		else
+			whereClause = qry->forPortionOf->overlapsExpr;
+	}
+	else
+		whereClause = stmt->whereClause;
+	qual = transformWhereClause(pstate, whereClause,
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -1044,7 +1065,7 @@ transformOnConflictClause(ParseState *pstate,
 		 * Now transform the UPDATE subexpressions.
 		 */
 		onConflictSet =
-			transformUpdateTargetList(pstate, onConflictClause->targetList);
+			transformUpdateTargetList(pstate, onConflictClause->targetList, NULL);
 
 		onConflictWhere = transformWhereClause(pstate,
 											   onConflictClause->whereClause,
@@ -1066,6 +1087,204 @@ transformOnConflictClause(ParseState *pstate,
 	return result;
 }
 
+/*
+ * transformForPortionOfClause
+ *	  transforms a ForPortionOfClause in an UPDATE/DELETE statement
+ */
+static ForPortionOfExpr *
+transformForPortionOfClause(ParseState *pstate,
+							int rtindex,
+							ForPortionOfClause *forPortionOf)
+{
+	Relation targetrel = pstate->p_target_relation;
+	RangeTblEntry *target_rte = pstate->p_target_nsitem->p_rte;
+	char *range_name = forPortionOf->range_name;
+	char *range_type_name;
+	int	range_attno;
+	ForPortionOfExpr *result;
+	List *targetList;
+
+	result = makeNode(ForPortionOfExpr);
+
+	/*
+	 * First look for a range column, then look for a period.
+	 */
+	range_attno = attnameAttNum(targetrel, range_name, true);
+	if (range_attno != InvalidAttrNumber)
+	{
+		Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, range_attno - 1);
+
+		// TODO: check attr->attisdropped ?
+
+		/* Make sure it's a range column */
+		if (!type_is_range(attr->atttypid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("column \"%s\" of relation \"%s\" is not a range type",
+							range_name,
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+		/* Make sure the table has a primary key */
+		Oid pkoid = RelationGetPrimaryKeyIndex(targetrel);
+		if (pkoid == InvalidOid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("relation \"%s\" does not have a temporal primary key",
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+		/* Make sure the primary key is a temporal key */
+		// TODO: need a lock here?
+		HeapTuple indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(pkoid));
+		if (!HeapTupleIsValid(indexTuple))	/* should not happen */
+			elog(ERROR, "cache lookup failed for index %u", pkoid);
+		Form_pg_index pk = (Form_pg_index) GETSTRUCT(indexTuple);
+		ReleaseSysCache(indexTuple);
+
+		/*
+		 * Only temporal pkey indexes have both isprimary and isexclusion.
+		 * Checking those saves us from scanning pg_constraint
+		 * like in RelationGetExclusionInfo.
+		 */
+		if (!(pk->indisprimary && pk->indisexclusion))
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("relation \"%s\" does not have a temporal primary key",
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+		}
+
+		/* Make sure the range attribute is the last part of the pkey. */
+		if (range_attno != pk->indkey.values[pk->indnkeyatts - 1])
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("column \"%s\" is not the temporal part of the primary key for relation \"%s\"",
+							range_name,
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+		}
+
+		Var *v = makeVar(
+				rtindex,
+				range_attno,
+				attr->atttypid,
+				attr->atttypmod,
+				attr->attcollation,
+				0);
+		v->location = forPortionOf->range_name_location;
+		result->range = (Expr *) v;
+		range_type_name = get_typname(attr->atttypid);
+
+	} else {
+		// TODO: Try to find a period,
+		// and set result->range to an Expr like tsrange(period->start_col, period->end_col)
+		// Probably we can make an A_Expr and call transformExpr on it, right?
+
+		/*
+		 * We need to choose a range type based on the period's columns' type.
+		 * Normally inferring a range type from an element type is not allowed,
+		 * because there might be more than one.
+		 * In this case SQL:2011 only has periods for timestamp, timestamptz, and date,
+		 * which all have built-in range types.
+		 * Let's just take the first range we have for that type,
+		 * ordering by oid, so that we get built-in range types first.
+		 */
+
+		// TODO: set result->range
+		// TODO: set range_type_name
+	}
+
+	if (range_attno == InvalidAttrNumber)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("column or period \"%s\" of relation \"%s\" does not exist",
+						range_name,
+						RelationGetRelationName(pstate->p_target_relation)),
+				 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+	/*
+	 * targetStart and End are literal strings
+	 * that we'll coerce to the range's element type later.
+	 * But if they are "Infinity" or "-Infinity" we should set them to NULL,
+	 * because ranges treat NULL as "further" than +/-Infinity.
+	 */
+	if (pg_strcasecmp(((A_Const *) forPortionOf->target_start)->val.val.str,
+					  "-Infinity") == 0)
+	{
+		A_Const *n = makeNode(A_Const);
+		n->val.type = T_Null;
+		n->location = ((A_Const*)forPortionOf->target_start)->location;
+		result->targetStart = (Node *) n;
+	}
+	else
+		result->targetStart = forPortionOf->target_start;
+
+	if (pg_strcasecmp(((A_Const *) forPortionOf->target_end)->val.val.str,
+					  "Infinity") == 0)
+	{
+		A_Const *n = makeNode(A_Const);
+		n->val.type = T_Null;
+		n->location = ((A_Const*)forPortionOf->target_end)->location;
+		result->targetEnd = (Node *) n;
+	}
+	else
+		result->targetEnd = forPortionOf->target_end;
+
+	FuncCall *fc = makeFuncCall(SystemFuncName(range_type_name),
+								list_make2(result->targetStart,
+										   result->targetEnd),
+								// TODO: FROM...TO... location instead?:
+								forPortionOf->range_name_location);
+	result->targetRange = transformExpr(pstate, (Node *) fc, EXPR_KIND_UPDATE_PORTION);
+
+	/* overlapsExpr is something we can add to the whereClause */
+	result->overlapsExpr = (Node *) makeSimpleA_Expr(AEXPR_OP, "&&",
+			// TODO: Maybe need a copy here?:
+			(Node *) result->range, (Node *) fc,
+			forPortionOf->range_name_location);
+
+	/*
+	 * Now make sure we update the start/end time of the record.
+	 * For a range col (r) this is `r = r * targetRange`.
+	 * For a PERIOD with cols (s, e) this is `s = lower(tsrange(s, r) * targetRange)`
+	 * and `e = upper(tsrange(e, r) * targetRange` (of course not necessarily with
+	 * tsrange, but with whatever range type is used there)).
+	 *
+	 * We also compute the possible left-behind bits at the start and end of the tuple,
+	 * so that we can INSERT them if necessary.
+	 */
+	targetList = NIL;
+	if (range_attno != InvalidAttrNumber)
+	{
+		Expr *rangeSetExpr = (Expr *) makeSimpleA_Expr(AEXPR_OP, "*",
+				// TODO: Maybe need a copy here?:
+				(Node *) result->range, (Node *) fc,
+				forPortionOf->range_name_location);
+
+		rangeSetExpr = (Expr *) transformExpr(pstate, (Node *) rangeSetExpr, EXPR_KIND_UPDATE_PORTION);
+		TargetEntry *tle = makeTargetEntry(rangeSetExpr,
+							  range_attno,
+							  range_name,
+							  false);
+
+		targetList = lappend(targetList, tle);
+	} else {
+		/* TODO: Set up targetList for PERIODs */
+	}
+	result->rangeSet = targetList;
+
+	/* Mark the range column as requiring update permissions */
+	target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
+											 range_attno - FirstLowInvalidHeapAttributeNumber);
+
+	result->range_attno = range_attno;
+	result->range_name = range_name;
+
+	return result;
+}
 
 /*
  * BuildOnConflictExcludedTargetlist
@@ -2221,6 +2440,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
 	ParseNamespaceItem *nsitem;
+	Node	   *whereClause;
 	Node	   *qual;
 
 	qry->commandType = CMD_UPDATE;
@@ -2238,6 +2458,10 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 										 stmt->relation->inh,
 										 true,
 										 ACL_UPDATE);
+
+	if (stmt->forPortionOf)
+		qry->forPortionOf = transformForPortionOfClause(pstate, qry->resultRelation, stmt->forPortionOf);
+
 	nsitem = pstate->p_target_nsitem;
 
 	/* subqueries in FROM cannot access the result relation */
@@ -2254,7 +2478,16 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	nsitem->p_lateral_only = false;
 	nsitem->p_lateral_ok = true;
 
-	qual = transformWhereClause(pstate, stmt->whereClause,
+	if (stmt->forPortionOf)
+	{
+		if (stmt->whereClause)
+			whereClause = (Node *) makeBoolExpr(AND_EXPR, list_make2(qry->forPortionOf->overlapsExpr, stmt->whereClause), -1);
+		else
+			whereClause = qry->forPortionOf->overlapsExpr;
+	}
+	else
+		whereClause = stmt->whereClause;
+	qual = transformWhereClause(pstate, whereClause,
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -2263,7 +2496,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	 * Now we are done with SELECT-like processing, and can get on with
 	 * transforming the target list to match the UPDATE target columns.
 	 */
-	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
+	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList, qry->forPortionOf);
 
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
@@ -2281,7 +2514,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
  *	handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
  */
 static List *
-transformUpdateTargetList(ParseState *pstate, List *origTlist)
+transformUpdateTargetList(ParseState *pstate, List *origTlist, ForPortionOfExpr *forPortionOf)
 {
 	List	   *tlist = NIL;
 	RangeTblEntry *target_rte;
@@ -2332,6 +2565,9 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							RelationGetRelationName(pstate->p_target_relation)),
 					 parser_errposition(pstate, origTarget->location)));
 
+		/* TODO: Make sure user isn't trying to SET the range attribute directly --- TODO or permit it?? */
+
+
 		updateTargetListEntry(pstate, tle, origTarget->name,
 							  attrno,
 							  origTarget->indirection,
@@ -2348,6 +2584,23 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 
 	fill_extraUpdatedCols(target_rte, tupdesc);
 
+	/*
+	 * Record in extraUpdatedCols the temporal bounds if using FOR PORTION OF.
+	 * Since these are part of the primary key this ensures we get the right lock type,
+	 * and it also tells column-specific triggers on those columns to fire.
+	 */
+	if (forPortionOf)
+	{
+		foreach(tl, forPortionOf->rangeSet)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(tl);
+			// TODO: I probably don't want to do this until after rewriting, or maybe not even until the ModifyTable node (which seems to be how generated columns work).
+			tlist = lappend(tlist, tle);
+			target_rte->extraUpdatedCols = bms_add_member(target_rte->extraUpdatedCols,
+														  tle->resno - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
 	return tlist;
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 7e384f956c..aac79175ad 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -229,6 +229,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RangeVar			*range;
 	IntoClause			*into;
 	WithClause			*with;
+	ForPortionOfClause	*forportionof;
 	InferClause			*infer;
 	OnConflictClause	*onconflict;
 	A_Indices			*aind;
@@ -475,10 +476,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause optionalPeriodName
+				where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
+%type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
@@ -498,6 +501,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <jexpr>	joined_table
 %type <range>	relation_expr
 %type <range>	relation_expr_opt_alias
+%type <forportionof> for_portion_of_clause
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
 
@@ -672,8 +676,8 @@ 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
-	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PERIOD PLACING PLANS POLICY
+	PORTION POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
 	QUOTE
@@ -3486,6 +3490,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3702,18 +3707,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3724,6 +3730,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3752,19 +3759,21 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
-				opt_column_list key_match key_actions ConstraintAttributeSpec
+			| FOREIGN KEY '(' columnList optionalPeriodName ')' REFERENCES qualified_name
+				opt_column_and_period_list key_match key_actions ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_FOREIGN;
 					n->location = @1;
-					n->pktable			= $7;
+					n->pktable			= $8;
 					n->fk_attrs			= $4;
-					n->pk_attrs			= $8;
-					n->fk_matchtype		= $9;
-					n->fk_upd_action	= (char) ($10 >> 8);
-					n->fk_del_action	= (char) ($10 & 0xFF);
-					processCASbits($11, @11, "FOREIGN KEY",
+					n->fk_period		= $5;
+					n->pk_attrs			= linitial($9);
+					n->pk_period		= lsecond($9);
+					n->fk_matchtype		= $10;
+					n->fk_upd_action	= (char) ($11 >> 8);
+					n->fk_del_action	= (char) ($11 & 0xFF);
+					processCASbits($12, @12, "FOREIGN KEY",
 								   &n->deferrable, &n->initdeferred,
 								   &n->skip_validation, NULL,
 								   yyscanner);
@@ -3787,6 +3796,21 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+optionalPeriodName:
+			',' PERIOD columnElem { $$ = $3; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+opt_column_and_period_list:
+			'(' columnList optionalPeriodName ')'			{ $$ = list_make2($2, $3); }
+			| /*EMPTY*/								{ $$ = list_make2(NIL, NULL); }
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
@@ -11124,13 +11148,15 @@ returning_clause:
  *****************************************************************************/
 
 DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
+			for_portion_of_clause
 			using_clause where_or_current_clause returning_clause
 				{
 					DeleteStmt *n = makeNode(DeleteStmt);
 					n->relation = $4;
-					n->usingClause = $5;
-					n->whereClause = $6;
-					n->returningList = $7;
+					n->forPortionOf = $5;
+					n->usingClause = $6;
+					n->whereClause = $7;
+					n->returningList = $8;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
@@ -11193,6 +11219,7 @@ opt_nowait_or_skip:
  *****************************************************************************/
 
 UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
+			for_portion_of_clause
 			SET set_clause_list
 			from_clause
 			where_or_current_clause
@@ -11200,10 +11227,11 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
 				{
 					UpdateStmt *n = makeNode(UpdateStmt);
 					n->relation = $3;
-					n->targetList = $5;
-					n->fromClause = $6;
-					n->whereClause = $7;
-					n->returningList = $8;
+					n->forPortionOf = $4;
+					n->targetList = $6;
+					n->fromClause = $7;
+					n->whereClause = $8;
+					n->returningList = $9;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
@@ -12308,6 +12336,20 @@ relation_expr_opt_alias: relation_expr					%prec UMINUS
 				}
 		;
 
+for_portion_of_clause:
+			FOR PORTION OF ColId FROM Sconst TO Sconst
+				{
+					ForPortionOfClause *n = makeNode(ForPortionOfClause);
+					n->range_name = $4;
+					n->range_name_location = @4;
+					n->target_start = makeStringConst($6, @6);
+					n->target_end = makeStringConst($8, @8);
+					$$ = n;
+				}
+			| /*EMPTY*/					{ $$ = NULL; }
+		;
+
+
 /*
  * TABLESAMPLE decoration in a FROM item
  */
@@ -15332,6 +15374,7 @@ unreserved_keyword:
 			| PASSWORD
 			| PLANS
 			| POLICY
+			| PORTION
 			| PRECEDING
 			| PREPARE
 			| PREPARED
@@ -15612,6 +15655,7 @@ reserved_keyword:
 			| ONLY
 			| OR
 			| ORDER
+			| PERIOD
 			| PLACING
 			| PRIMARY
 			| REFERENCES
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 831db4af95..5c4da05240 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -3518,6 +3518,8 @@ ParseExprKindName(ParseExprKind exprKind)
 		case EXPR_KIND_UPDATE_SOURCE:
 		case EXPR_KIND_UPDATE_TARGET:
 			return "UPDATE";
+		case EXPR_KIND_UPDATE_PORTION:
+			return "FOR PORTION OF";
 		case EXPR_KIND_GROUP_BY:
 			return "GROUP BY";
 		case EXPR_KIND_ORDER_BY:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index af77f1890f..f3eaa9de58 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1414,6 +1414,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
 	index->oldNode = InvalidOid;
 	index->unique = idxrec->indisunique;
 	index->primary = idxrec->indisprimary;
+	index->istemporal = idxrec->indisprimary && idxrec->indisexclusion;
 	index->transformed = true;	/* don't need transformIndexStmt */
 	index->concurrent = false;
 	index->if_not_exists = false;
@@ -1973,7 +1974,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -1991,6 +1992,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 		 */
 	}
 	index->isconstraint = true;
+	index->istemporal = constraint->without_overlaps != NULL;
 	index->deferrable = constraint->deferrable;
 	index->initdeferred = constraint->initdeferred;
 
@@ -2364,6 +2366,153 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				notnullcmd->name = pstrdup(key);
 				notnullcmds = lappend(notnullcmds, notnullcmd);
 			}
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				AlterTableCmd *notnullcmd;
+				iparam->name = pstrdup(without_overlaps_str);
+				iparam->expr = NULL;
+
+				/*
+				 * Force the column to NOT NULL since it is part of the primary key.
+				 */
+				notnullcmd = makeNode(AlterTableCmd);
+
+				notnullcmd->subtype = AT_SetNotNull;
+				notnullcmd->name = pstrdup(without_overlaps_str);
+				notnullcmds = lappend(notnullcmds, notnullcmd);
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+			{
+				List *opname;
+				iparam->indexcolname = NULL;
+				iparam->collation = NIL;
+				iparam->opclass = NIL;
+				iparam->ordering = SORTBY_DEFAULT;
+				iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+				index->indexParams = lappend(index->indexParams, iparam);
+
+				opname = list_make1(makeString("&&"));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+				index->accessMethod = "gist";
+				constraint->access_method = "gist";
+			}
 		}
 	}
 
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 3b4f28874a..17b23c4bd1 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2990,6 +2990,18 @@ rewriteTargetView(Query *parsetree, Relation view)
 			}
 		}
 
+		if (parsetree->forPortionOf)
+		{
+			foreach(lc, parsetree->forPortionOf->rangeSet)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+				if (!tle->resjunk)
+					modified_cols = bms_add_member(modified_cols,
+												   tle->resno - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+
 		auto_update_detail = view_cols_are_auto_updatable(viewquery,
 														  modified_cols,
 														  NULL,
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 9d1ca13e32..c65b82b41c 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -1226,6 +1226,48 @@ range_split_internal(TypeCacheEntry *typcache, const RangeType *r1, const RangeT
 	return false;
 }
 
+/*
+ * range_leftover_internal - Sets output1 and output2 to the remaining parts of r1
+ * after subtracting r2, or if nothing is left then to the empty range.
+ * output1 will always be "before" r2 and output2 "after".
+ */
+void
+range_leftover_internal(TypeCacheEntry *typcache, const RangeType *r1,
+						const RangeType *r2, RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0)
+	{
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+	}
+	else
+	{
+		*output1 = make_empty_range(typcache);
+	}
+
+	if (range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+	}
+	else
+	{
+		*output2 = make_empty_range(typcache);
+	}
+}
+
 /* range -> range aggregate functions */
 
 Datum
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 4ab7cda110..d50bf47ba3 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -108,6 +108,7 @@ typedef struct RI_ConstraintInfo
 	char		confupdtype;	/* foreign key's ON UPDATE action */
 	char		confdeltype;	/* foreign key's ON DELETE action */
 	char		confmatchtype;	/* foreign key's match type */
+	bool		temporal;		/* if the foreign key is temporal */
 	int			nkeys;			/* number of key columns */
 	int16		pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
 	int16		fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
@@ -191,7 +192,7 @@ static int	ri_NullCheck(TupleDesc tupdesc, TupleTableSlot *slot,
 static void ri_BuildQueryKey(RI_QueryKey *key,
 							 const RI_ConstraintInfo *riinfo,
 							 int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+static bool ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
 							   Datum oldvalue, Datum newvalue);
@@ -351,18 +352,46 @@ RI_FKey_check(TriggerData *trigdata)
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
-		 *		   FOR KEY SHARE OF x
+		 *	SELECT 1
+		 *	FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *	FOR KEY SHARE OF x
 		 * The type id's for the $ parameters are those of the
 		 * corresponding FK attributes.
+		 *
+		 * But for temporal FKs we need to make sure
+		 * the FK's range is completely covered.
+		 * So we use this query instead:
+		 *  SELECT 1
+		 *  FROM (
+		 *	    SELECT	range_agg(r) AS r
+		 *	    FROM	(
+		 *			SELECT pkperiodatt AS r
+		 *			FROM   [ONLY] pktable x
+		 *			WHERE  pkatt1 = $1 [AND ...]
+		 *			FOR KEY SHARE OF x
+		 *		) x1
+		 *  ) x2
+		 *  WHERE $n <@ x2.r
+		 * Note if FOR KEY SHARE ever allows aggregate functions
+		 * we can make this a bit simpler.
 		 * ----------
 		 */
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
 		quoteRelationName(pkrelname, pk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 pk_only, pkrelname);
+		if (riinfo->temporal)
+		{
+			quoteOneName(attname,
+					RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			appendStringInfo(&querybuf,
+					"SELECT 1 FROM (SELECT range_agg(r) AS r FROM (SELECT %s AS r FROM %s%s x",
+					attname, pk_only, pkrelname);
+		}
+		else {
+			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
+							 pk_only, pkrelname);
+		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -380,6 +409,8 @@ RI_FKey_check(TriggerData *trigdata)
 			queryoids[i] = fk_type;
 		}
 		appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
+		if (riinfo->temporal)
+			appendStringInfo(&querybuf, ") x1) x2 WHERE $%d <@ x2.r", riinfo->nkeys);
 
 		/* Prepare and save the plan */
 		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -1174,7 +1205,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
 		return false;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
+	if (newslot && ri_KeysStable(pk_rel, oldslot, newslot, riinfo, true))
 		return false;
 
 	/* Else we need to fire the trigger. */
@@ -1267,13 +1298,135 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 		return true;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
+	if (ri_KeysStable(fk_rel, oldslot, newslot, riinfo, false))
 		return false;
 
 	/* Else we need to fire the trigger. */
 	return true;
 }
 
+/* ----------
+ * TRI_FKey_check_ins -
+ *
+ *	Check temporal foreign key existence at insert event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_ins(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+	/*
+	 * Share code with UPDATE case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_check_upd -
+ *
+ *	Check temporal foreign key existence at update event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_upd(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+	/*
+	 * Share code with INSERT case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_noaction_del -
+ *
+ *	Give an error and roll back the current transaction if the
+ *	delete has resulted in a violation of the given temporal
+ *	referential integrity constraint.
+ * ----------
+ */
+Datum
+TRI_FKey_noaction_del(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
+
+	/*
+	 * Share code with RESTRICT/UPDATE cases.
+	 */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_del -
+ *
+ * Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the delete is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with NO ACTION/UPDATE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+/*
+ * TRI_FKey_noaction_upd -
+ *
+ * Give an error and roll back the current transaction if the
+ * update has resulted in a violation of the given referential
+ * integrity constraint.
+ */
+Datum
+TRI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with RESTRICT/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_upd -
+ *
+ * Restrict update of PK to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the update is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with NO ACTION/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+
 /*
  * RI_Initial_Check -
  *
@@ -2049,6 +2202,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo->confupdtype = conForm->confupdtype;
 	riinfo->confdeltype = conForm->confdeltype;
 	riinfo->confmatchtype = conForm->confmatchtype;
+	riinfo->temporal = conForm->contemporal;
 
 	DeconstructFkConstraintRow(tup,
 							   &riinfo->nkeys,
@@ -2643,9 +2797,12 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 
 
 /*
- * ri_KeysEqual -
+ * ri_KeysStable -
  *
- * Check if all key values in OLD and NEW are equal.
+ * Check if all key values in OLD and NEW are "equivalent":
+ * For normal FKs we check for equality.
+ * For temporal FKs we check that the PK side is a superset of its old value,
+ * or the FK side is a subset.
  *
  * Note: at some point we might wish to redefine this as checking for
  * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
@@ -2653,7 +2810,7 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
  * previously found at least one of the rows to contain no nulls.
  */
 static bool
-ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -2686,29 +2843,43 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 
 		if (rel_is_pk)
 		{
-			/*
-			 * If we are looking at the PK table, then do a bytewise
-			 * comparison.  We must propagate PK changes if the value is
-			 * changed to one that "looks" different but would compare as
-			 * equal using the equality operator.  This only makes a
-			 * difference for ON UPDATE CASCADE, but for consistency we treat
-			 * all changes to the PK the same.
-			 */
-			Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, newvalue, oldvalue));
+			}
+			else
+			{
+				/*
+				 * If we are looking at the PK table, then do a bytewise
+				 * comparison.  We must propagate PK changes if the value is
+				 * changed to one that "looks" different but would compare as
+				 * equal using the equality operator.  This only makes a
+				 * difference for ON UPDATE CASCADE, but for consistency we treat
+				 * all changes to the PK the same.
+				 */
+				Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
 
-			if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
-				return false;
+				if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
+					return false;
+			}
 		}
 		else
 		{
-			/*
-			 * For the FK table, compare with the appropriate equality
-			 * operator.  Changes that compare equal will still satisfy the
-			 * constraint after the update.
-			 */
-			if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
-									oldvalue, newvalue))
-				return false;
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, oldvalue, newvalue));
+			}
+			else
+			{
+				/*
+				 * For the FK table, compare with the appropriate equality
+				 * operator.  Changes that compare equal will still satisfy the
+				 * constraint after the update.
+				 */
+				if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
+										oldvalue, newvalue))
+					return false;
+			}
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 5e63238f03..e7d618ca53 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -331,7 +331,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 								   int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int	decompile_column_index_array(Datum column_index_array, Oid relId,
-										 StringInfo buf);
+										 bool withoutOverlaps, bool withPeriod, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									const Oid *excludeOps,
@@ -1998,6 +1998,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			{
 				Datum		val;
 				bool		isnull;
+				bool		hasperiod;
 				const char *string;
 
 				/* Start off the constraint definition */
@@ -2010,7 +2011,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it is a temporal foreign key
+				 * then it uses PERIOD.
+				 */
+				hasperiod = DatumGetBool(SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_contemporal, &isnull));
+				decompile_column_index_array(val, conForm->conrelid, false, hasperiod, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2024,7 +2031,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, hasperiod, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2125,7 +2132,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2327,7 +2340,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, bool withPeriod, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2345,9 +2358,21 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
+		else if (withPeriod && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", PERIOD %s", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 3ad1a9aa71..d7401ffed5 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -1967,6 +1967,32 @@ get_typisdefined(Oid typid)
 }
 
 /*
+ * get_typname
+ *
+ *		Returns the name of a given type
+ *
+ * Returns a palloc'd copy of the string, or NULL if no such type.
+ */
+char *
+get_typname(Oid typid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
+		char	   *result;
+
+		result = pstrdup(NameStr(typtup->typname));
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return NULL;
+}
+
+/*
  * get_typlen
  *
  *		Given the type OID, return the length of the type.
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326474..6489e1b626 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4386,11 +4386,17 @@ RelationGetIndexList(Relation relation)
 		 * interesting for either oid indexes or replication identity indexes,
 		 * so don't check them.
 		 */
-		if (!index->indisvalid || !index->indisunique ||
-			!index->indimmediate ||
+		if (!index->indisvalid || !index->indimmediate ||
 			!heap_attisnull(htup, Anum_pg_index_indpred, NULL))
 			continue;
 
+		/*
+		 * Non-unique indexes aren't interesting either,
+		 * except when they are temporal primary keys.
+		 */
+		if (!index->indisunique && !index->indisprimary)
+			continue;
+
 		/* remember primary key index if any */
 		if (index->indisprimary)
 			pkeyIndex = index->indexrelid;
@@ -4992,8 +4998,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5059,7 +5066,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 4cbb37511c..02f8351f1a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6907,7 +6907,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -6968,7 +6969,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -7007,7 +7009,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7042,7 +7045,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7073,7 +7077,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7107,7 +7112,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7147,6 +7153,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7207,6 +7214,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16729,9 +16737,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 52f2c12381..cc2e8588ac 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -449,6 +449,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+	bool		withoutoverlaps;	/* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 1b90cbd9b5..9d3f752948 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -634,6 +634,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index f3c7eb96fa..644adb995d 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2386,6 +2386,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a2890c1314..d42da26347 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -77,6 +77,7 @@ extern Oid	index_create(Relation heapRelation,
 #define	INDEX_CONSTR_CREATE_INIT_DEFERRED	(1 << 2)
 #define	INDEX_CONSTR_CREATE_UPDATE_INDEX	(1 << 3)
 #define	INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS	(1 << 4)
+#define	INDEX_CONSTR_CREATE_TEMPORAL		(1 << 5)
 
 extern Oid	index_concurrently_create_copy(Relation heapRelation,
 										   Oid oldIndexId,
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece93c..1740578e37 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -102,6 +102,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	/* Has a local definition and cannot be inherited */
 	bool		connoinherit;
 
+	/*
+	 * For primary and foreign keys, signifies the last column is a range 
+	 * and should use overlaps instead of equals.
+	 */
+	bool		contemporal;
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 
 	/*
@@ -116,26 +122,26 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	int16		confkey[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * If a foreign key, the OIDs of the PK = FK comparison operators for each
 	 * column of the constraint
 	 */
 	Oid			conpfeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * If a foreign key, the OIDs of the PK = PK comparison operators for each
 	 * column of the constraint (i.e., equality for the referenced columns)
 	 */
 	Oid			conppeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * If a foreign key, the OIDs of the FK = FK comparison operators for each
 	 * column of the constraint (i.e., equality for the referencing columns)
 	 */
 	Oid			conffeqop[1];
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
@@ -210,6 +216,7 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conIsLocal,
 								  int conInhCount,
 								  bool conNoInherit,
+								  bool conTemporal,
 								  bool is_internal);
 
 extern void RemoveConstraintById(Oid conId);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 27f25d989b..05eaca30c3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3726,6 +3726,50 @@
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'RI_FKey_noaction_upd' },
 
+# Temporal referential integrity constraint triggers
+{ oid => '6122', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_ins' },
+{ oid => '6123', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_upd' },
+# { oid => '6124', descr => 'temporal referential integrity ON DELETE CASCADE',
+#   proname => 'TRI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_del' },
+# { oid => '6125', descr => 'temporal referential integrity ON UPDATE CASCADE',
+#   proname => 'TRI_FKey_cascade_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_upd' },
+{ oid => '6126', descr => 'temporal referential integrity ON DELETE RESTRICT',
+  proname => 'TRI_FKey_restrict_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_del' },
+{ oid => '6127', descr => 'temporal referential integrity ON UPDATE RESTRICT',
+  proname => 'TRI_FKey_restrict_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_upd' },
+# { oid => '6128', descr => 'temporal referential integrity ON DELETE SET NULL',
+#   proname => 'TRI_FKey_setnull_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_del' },
+# { oid => '6129', descr => 'temporal referential integrity ON UPDATE SET NULL',
+#   proname => 'TRI_FKey_setnull_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_upd' },
+# { oid => '6130', descr => 'temporal referential integrity ON DELETE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_del', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_del' },
+# { oid => '6131', descr => 'temporal referential integrity ON UPDATE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_upd', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_upd' },
+{ oid => '6132', descr => 'temporal referential integrity ON DELETE NO ACTION',
+  proname => 'TRI_FKey_noaction_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_del' },
+{ oid => '6133', descr => 'temporal referential integrity ON UPDATE NO ACTION',
+  proname => 'TRI_FKey_noaction_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_upd' },
+
 { oid => '1666',
   proname => 'varbiteq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'varbit varbit', prosrc => 'biteq' },
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index cd3ddf781f..517e782a31 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -25,12 +25,14 @@
 #include "storage/condition_variable.h"
 #include "utils/hsearch.h"
 #include "utils/queryenvironment.h"
+#include "utils/rangetypes.h"
 #include "utils/reltrigger.h"
 #include "utils/sharedtuplestore.h"
 #include "utils/snapshot.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplesort.h"
 #include "utils/tuplestore.h"
+#include "utils/typcache.h"
 
 struct PlanState;				/* forward references in this file */
 struct PartitionRoutingInfo;
@@ -387,6 +389,22 @@ typedef struct OnConflictSetState
 } OnConflictSetState;
 
 /*
+ * ForPortionOfState
+ *
+ * Executor state of a FOR PORTION OF operation.
+ */
+typedef struct ForPortionOfState
+{
+	NodeTag		type;
+
+	TypeCacheEntry	*fp_rangetypcache;	/* type cache entry of the range */
+	RangeType		*fp_targetRange;	/* the range from FOR PORTION OF */
+	TupleTableSlot	*fp_Existing;	/* slot to store existing target tuple in */
+	TupleTableSlot	*fp_Leftover1;	/* slot to store leftover below the target range */
+	TupleTableSlot	*fp_Leftover2;	/* slot to store leftover above the target range */
+} ForPortionOfState;
+
+/*
  * ResultRelInfo
  *
  * Whenever we update an existing relation, we have to update indexes on the
@@ -475,6 +493,9 @@ typedef struct ResultRelInfo
 	/* ON CONFLICT evaluation state */
 	OnConflictSetState *ri_onConflict;
 
+	/* FOR PORTION OF evaluation state */
+	ForPortionOfState *ri_forPortionOf;
+
 	/* partition check expression */
 	List	   *ri_PartitionCheck;
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 8a76afe8cc..8f28305964 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -35,6 +35,7 @@ typedef enum NodeTag
 	T_ProjectionInfo,
 	T_JunkFilter,
 	T_OnConflictSetState,
+	T_ForPortionOfState,
 	T_ResultRelInfo,
 	T_EState,
 	T_TupleTableSlot,
@@ -195,6 +196,7 @@ typedef enum NodeTag
 	T_JoinExpr,
 	T_FromExpr,
 	T_OnConflictExpr,
+	T_ForPortionOfExpr,
 	T_IntoClause,
 
 	/*
@@ -469,6 +471,7 @@ typedef enum NodeTag
 	T_WithClause,
 	T_InferClause,
 	T_OnConflictClause,
+	T_ForPortionOfClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 2039b42449..9bd5dec0a6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -122,6 +122,8 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
 
+	ForPortionOfExpr *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
+
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
@@ -1384,6 +1386,19 @@ typedef struct RowMarkClause
 } RowMarkClause;
 
 /*
+ * ForPortionOfClause
+ *		representation of FOR PORTION OF <period-name> FROM <t1> TO <t2>
+ */
+typedef struct ForPortionOfClause
+{
+	NodeTag		type;
+	char	   *range_name;
+	int			range_name_location;
+	Node	   *target_start;
+	Node	   *target_end;
+} ForPortionOfClause;
+
+/*
  * WithClause -
  *	   representation of WITH clause
  *
@@ -1538,12 +1553,13 @@ typedef struct InsertStmt
  */
 typedef struct DeleteStmt
 {
-	NodeTag		type;
-	RangeVar   *relation;		/* relation to delete from */
-	List	   *usingClause;	/* optional using clause for more tables */
-	Node	   *whereClause;	/* qualifications */
-	List	   *returningList;	/* list of expressions to return */
-	WithClause *withClause;		/* WITH clause */
+	NodeTag				type;
+	RangeVar		   *relation;		/* relation to delete from */
+	ForPortionOfClause *forPortionOf;	/* FOR PORTION OF clause */
+	List			   *usingClause;	/* optional using clause for more tables */
+	Node			   *whereClause;	/* qualifications */
+	List			   *returningList;	/* list of expressions to return */
+	WithClause		   *withClause;		/* WITH clause */
 } DeleteStmt;
 
 /* ----------------------
@@ -1552,13 +1568,14 @@ typedef struct DeleteStmt
  */
 typedef struct UpdateStmt
 {
-	NodeTag		type;
-	RangeVar   *relation;		/* relation to update */
-	List	   *targetList;		/* the target list (of ResTarget) */
-	Node	   *whereClause;	/* qualifications */
-	List	   *fromClause;		/* optional from clause for more tables */
-	List	   *returningList;	/* list of expressions to return */
-	WithClause *withClause;		/* WITH clause */
+	NodeTag				type;
+	RangeVar		   *relation;		/* relation to update */
+	ForPortionOfClause *forPortionOf;	/* FOR PORTION OF clause */
+	List			   *targetList;		/* the target list (of ResTarget) */
+	Node			   *whereClause;	/* qualifications */
+	List			   *fromClause;		/* optional from clause for more tables */
+	List			   *returningList;	/* list of expressions to return */
+	WithClause		   *withClause;		/* WITH clause */
 } UpdateStmt;
 
 /* ----------------------
@@ -2182,7 +2199,9 @@ typedef struct Constraint
 	/* Fields used for FOREIGN KEY constraints: */
 	RangeVar   *pktable;		/* Primary key table */
 	List	   *fk_attrs;		/* Attributes of foreign key */
+	Node	   *fk_period;		/* String node naming Period or range column */
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
+	Node	   *pk_period;		/* String node naming Period or range column */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
 	char		fk_del_action;	/* ON DELETE action */
@@ -2190,6 +2209,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+	Node	   *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
@@ -2785,6 +2807,7 @@ typedef struct IndexStmt
 	bool		unique;			/* is index unique? */
 	bool		primary;		/* is index a primary key? */
 	bool		isconstraint;	/* is it for a pkey/unique constraint? */
+	bool		istemporal;		/* is it for a temporal pkey? */
 	bool		deferrable;		/* is the constraint DEFERRABLE? */
 	bool		initdeferred;	/* is the constraint INITIALLY DEFERRED? */
 	bool		transformed;	/* true when transformIndexStmt is finished */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 0ceb809644..e1e6145fd1 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1785,6 +1785,7 @@ typedef struct ModifyTablePath
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
+	ForPortionOfExpr *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 } ModifyTablePath;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 4869fe7b6d..9b014ce9e0 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -234,6 +234,8 @@ typedef struct ModifyTable
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
+	// TODO: Instead of re-using Expr here, break it into pieces like onConflict{Action,Set,Where}?
+	Node	   *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
 	List	   *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs  */
 	List	   *onConflictSet;	/* SET for INSERT ON CONFLICT DO UPDATE */
 	Node	   *onConflictWhere;	/* WHERE for ON CONFLICT UPDATE */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index d73be2ad46..e19dc9fd4e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1538,4 +1538,25 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * ForPortionOfExpr - represents a FOR PORTION OF ... expression
+ *
+ * TODO: more notes as needed
+ *----------
+ */
+typedef struct ForPortionOfExpr
+{
+	NodeTag		type;
+	int			range_attno;	/* Range column number */
+	char	   *range_name;		/* Range name */
+	Expr	   *range;			/* Range column or expression */
+	Node	   *startCol;		/* Start column if using a PERIOD */
+	Node	   *endCol;			/* End column if using a PERIOD */
+	Node	   *targetStart;	/* Same type as the range's elements */
+	Node	   *targetEnd;		/* Same type as the range's elements */
+	Node	   *targetRange;	/* A range from targetStart to targetEnd */
+	Node	   *overlapsExpr;	/* range && targetRange */
+	List	   *rangeSet;		/* List of TargetEntrys to set the time column(s) */
+} ForPortionOfExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index e450fe112a..08eb937346 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -258,7 +258,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												List *subroots,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
-												int epqParam);
+												ForPortionOfExpr *forPortionOf, int epqParam);
 extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
 									Path *subpath,
 									Node *limitOffset, Node *limitCount,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index b1184c2d15..5e5618ec3e 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -300,9 +300,11 @@ 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)
+PG_KEYWORD("portion", PORTION, UNRESERVED_KEYWORD)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD)
 PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..9911d96120 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -55,6 +55,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_INSERT_TARGET,	/* INSERT target list item */
 	EXPR_KIND_UPDATE_SOURCE,	/* UPDATE assignment source item */
 	EXPR_KIND_UPDATE_TARGET,	/* UPDATE assignment target item */
+	EXPR_KIND_UPDATE_PORTION,	/* UPDATE FOR PORTION OF item */
 	EXPR_KIND_GROUP_BY,			/* GROUP BY */
 	EXPR_KIND_ORDER_BY,			/* ORDER BY */
 	EXPR_KIND_DISTINCT_ON,		/* DISTINCT ON */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index a46ce1978e..2b77f82303 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -134,6 +134,7 @@ extern char get_rel_persistence(Oid relid);
 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 int16 get_typlen(Oid typid);
 extern bool get_typbyval(Oid typid);
 extern void get_typlenbyval(Oid typid, int16 *typlen, bool *typbyval);
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index 204aee4054..b1eee65133 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -156,5 +156,8 @@ extern RangeType *make_empty_range(TypeCacheEntry *typcache);
 extern bool range_split_internal(TypeCacheEntry *typcache, const RangeType *r1,
 								 const RangeType *r2, RangeType **output1,
 								 RangeType **output2);
+extern void range_leftover_internal(TypeCacheEntry *typcache, const RangeType *r1,
+									const RangeType *r2, RangeType **output1,
+									RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/test/regress/expected/for_portion_of.out b/src/test/regress/expected/for_portion_of.out
new file mode 100644
index 0000000000..66cc455428
--- /dev/null
+++ b/src/test/regress/expected/for_portion_of.out
@@ -0,0 +1,261 @@
+-- Tests for UPDATE/DELETE FOR PORTION OF
+-- Fails on tables without a temporal PK:
+CREATE TABLE for_portion_of_test (
+  id int4range PRIMARY KEY,
+  valid_at tsrange NOT NULL,
+  name text NOT NULL
+);
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-01-15' TO 'Infinity'
+SET name = 'foo';
+ERROR:  relation "for_portion_of_test" does not have a temporal primary key
+LINE 2: FOR PORTION OF valid_at FROM '2018-01-15' TO 'Infinity'
+                       ^
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-01-15' TO 'Infinity';
+ERROR:  relation "for_portion_of_test" does not have a temporal primary key
+LINE 2: FOR PORTION OF valid_at FROM '2018-01-15' TO 'Infinity';
+                       ^
+DROP TABLE for_portion_of_test;
+CREATE TABLE for_portion_of_test (
+  id int4range NOT NULL,
+  valid_at tsrange NOT NULL,
+  name text NOT NULL,
+	CONSTRAINT for_portion_of_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO for_portion_of_test
+VALUES
+('[1,2)', '[2018-01-02,2018-02-03)', 'one'),
+('[1,2)', '[2018-02-03,2018-03-03)', 'one'),
+('[1,2)', '[2018-03-03,2018-04-04)', 'one'),
+('[2,3)', '[2018-01-01,2018-01-05)', 'two'),
+('[3,4)', '[2018-01-01,)', 'three'),
+('[4,5)', '(,2018-04-01)', 'four'),
+('[5,6)', '(,)', 'five')
+;
+--
+-- UPDATE tests
+--
+-- Setting with a missing column fails
+UPDATE for_portion_of_test
+FOR PORTION OF invalid_at FROM '2018-06-01' TO 'Infinity'
+SET name = 'foo'
+WHERE id = '[5,6)';
+ERROR:  column or period "invalid_at" of relation "for_portion_of_test" does not exist
+LINE 2: FOR PORTION OF invalid_at FROM '2018-06-01' TO 'Infinity'
+                       ^
+-- Setting the range fails
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO 'Infinity'
+SET valid_at = '[1990-01-01,1999-01-01)'
+WHERE id = '[5,6)';
+ERROR:  multiple assignments to same column "valid_at"
+-- Setting with timestamps reversed fails
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO '2018-01-01'
+SET name = 'three^1'
+WHERE id = '[3,4)';
+ERROR:  range lower bound must be less than or equal to range upper bound
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM 'Infinity' TO '-Infinity'
+SET name = 'three^1'
+WHERE id = '[3,4)';
+ERROR:  range lower bound must be less than or equal to range upper bound
+-- Setting with timestamps equal does nothing
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-04-01' TO '2018-04-01'
+SET name = 'three^0'
+WHERE id = '[3,4)';
+-- Updating a finite/open portion with a finite/open target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO 'Infinity'
+SET name = 'three^1'
+WHERE id = '[3,4)';
+-- Updating a finite/open portion with an open/finite target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO '2018-03-01'
+SET name = 'three^2'
+WHERE id = '[3,4)';
+-- Updating an open/finite portion with an open/finite target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO '2018-02-01'
+SET name = 'four^1'
+WHERE id = '[4,5)';
+-- Updating an open/finite portion with a finite/open target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2017-01-01' TO 'Infinity'
+SET name = 'four^2'
+WHERE id = '[4,5)';
+-- Updating a finite/finite portion with an exact fit
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2017-01-01' TO '2018-02-01'
+SET name = 'four^3'
+WHERE id = '[4,5)';
+-- Updating an enclosed span
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO 'Infinity'
+SET name = 'two^2'
+WHERE id = '[2,3)';
+-- Updating an open/open portion with a finite/finite target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-01-01' TO '2019-01-01'
+SET name = 'five^2'
+WHERE id = '[5,6)';
+-- Updating an enclosed span with separate protruding spans
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2017-01-01' TO '2020-01-01'
+SET name = 'five^3'
+WHERE id = '[5,6)';
+-- Updating multiple enclosed spans
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO 'Infinity'
+SET name = 'one^2'
+WHERE id = '[1,2)';
+-- Updating the non-range part of the PK:
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-02-15' TO 'Infinity'
+SET id = '[6,7)'
+WHERE id = '[1,2)';
+-- UPDATE with no WHERE clause
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2030-01-01' TO 'Infinity'
+SET name = name || '*';
+SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
+  id   |                        valid_at                         |   name   
+-------+---------------------------------------------------------+----------
+ [1,2) | ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018") | one^2
+ [1,2) | ["Sat Feb 03 00:00:00 2018","Thu Feb 15 00:00:00 2018") | one^2
+ [2,3) | ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018") | two^2
+ [3,4) | ["Mon Jan 01 00:00:00 2018","Thu Mar 01 00:00:00 2018") | three^2
+ [3,4) | ["Thu Mar 01 00:00:00 2018","Fri Jun 01 00:00:00 2018") | three
+ [3,4) | ["Fri Jun 01 00:00:00 2018","Tue Jan 01 00:00:00 2030") | three^1
+ [3,4) | ["Tue Jan 01 00:00:00 2030",)                           | three^1*
+ [4,5) | (,"Sun Jan 01 00:00:00 2017")                           | four^1
+ [4,5) | ["Sun Jan 01 00:00:00 2017","Thu Feb 01 00:00:00 2018") | four^3
+ [4,5) | ["Thu Feb 01 00:00:00 2018","Sun Apr 01 00:00:00 2018") | four^2
+ [5,6) | (,"Sun Jan 01 00:00:00 2017")                           | five
+ [5,6) | ["Sun Jan 01 00:00:00 2017","Mon Jan 01 00:00:00 2018") | five^3
+ [5,6) | ["Mon Jan 01 00:00:00 2018","Tue Jan 01 00:00:00 2019") | five^3
+ [5,6) | ["Tue Jan 01 00:00:00 2019","Wed Jan 01 00:00:00 2020") | five^3
+ [5,6) | ["Wed Jan 01 00:00:00 2020","Tue Jan 01 00:00:00 2030") | five
+ [5,6) | ["Tue Jan 01 00:00:00 2030",)                           | five*
+ [6,7) | ["Thu Feb 15 00:00:00 2018","Sat Mar 03 00:00:00 2018") | one^2
+ [6,7) | ["Sat Mar 03 00:00:00 2018","Wed Apr 04 00:00:00 2018") | one^2
+(18 rows)
+
+--
+-- DELETE tests
+--
+-- Deleting with a missing column fails
+DELETE FROM for_portion_of_test
+FOR PORTION OF invalid_at FROM '2018-06-01' TO 'Infinity'
+WHERE id = '[5,6)';
+ERROR:  column or period "invalid_at" of relation "for_portion_of_test" does not exist
+LINE 2: FOR PORTION OF invalid_at FROM '2018-06-01' TO 'Infinity'
+                       ^
+-- Deleting with timestamps reversed fails
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO '2018-01-01'
+WHERE id = '[3,4)';
+ERROR:  range lower bound must be less than or equal to range upper bound
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM 'Infinity' TO '-Infinity'
+WHERE id = '[3,4)';
+ERROR:  range lower bound must be less than or equal to range upper bound
+-- Deleting with timestamps equal does nothing
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-04-01' TO '2018-04-01'
+WHERE id = '[3,4)';
+-- Deleting with a closed/closed target
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO '2020-06-01'
+WHERE id = '[5,6)';
+-- Deleting with a closed/open target
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-04-01' TO 'Infinity'
+WHERE id = '[3,4)';
+-- Deleting with an open/closed target
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO '2018-02-08'
+WHERE id = '[1,2)';
+-- Deleting with an open/open target
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO 'Infinity'
+WHERE id = '[6,7)';
+-- DELETE with no WHERE clause
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2025-01-01' TO 'Infinity';
+SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
+  id   |                        valid_at                         |  name   
+-------+---------------------------------------------------------+---------
+ [1,2) | ["Thu Feb 08 00:00:00 2018","Thu Feb 15 00:00:00 2018") | one^2
+ [2,3) | ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018") | two^2
+ [3,4) | ["Mon Jan 01 00:00:00 2018","Thu Mar 01 00:00:00 2018") | three^2
+ [3,4) | ["Thu Mar 01 00:00:00 2018","Sun Apr 01 00:00:00 2018") | three
+ [4,5) | (,"Sun Jan 01 00:00:00 2017")                           | four^1
+ [4,5) | ["Sun Jan 01 00:00:00 2017","Thu Feb 01 00:00:00 2018") | four^3
+ [4,5) | ["Thu Feb 01 00:00:00 2018","Sun Apr 01 00:00:00 2018") | four^2
+ [5,6) | (,"Sun Jan 01 00:00:00 2017")                           | five
+ [5,6) | ["Sun Jan 01 00:00:00 2017","Mon Jan 01 00:00:00 2018") | five^3
+ [5,6) | ["Mon Jan 01 00:00:00 2018","Fri Jun 01 00:00:00 2018") | five^3
+ [5,6) | ["Mon Jun 01 00:00:00 2020","Wed Jan 01 00:00:00 2025") | five
+(11 rows)
+-- test that we run triggers on the UPDATE/DELETEd row and the INSERTed rows
+CREATE FUNCTION for_portion_of_trigger()
+RETURNS trigger
+AS
+$$
+BEGIN
+  RAISE NOTICE '% % % of %', TG_WHEN, TG_OP, NEW.valid_at, OLD.valid_at;
+  IF TG_OP = 'DELETE' THEN
+    RETURN OLD;
+  ELSE
+    RETURN NEW;
+  END IF;
+END;
+$$
+LANGUAGE plpgsql;
+CREATE TRIGGER trg_for_portion_of_before_insert
+  BEFORE INSERT ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_after_insert
+  AFTER INSERT ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_before_update
+  BEFORE UPDATE ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_after_update
+  AFTER UPDATE ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_before_delete
+  BEFORE DELETE ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_after_delete
+  AFTER DELETE ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2021-01-01' TO '2022-01-01'
+SET name = 'five^4'
+WHERE id = '[5,6)';
+SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
+NOTICE:  BEFORE UPDATE ["Fri Jan 01 00:00:00 2021","Sat Jan 01 00:00:00 2022") of ["Mon Jun 01 00:00:00 2020","Wed Jan 01 00:00:00 2025")
+NOTICE:  BEFORE INSERT ["Mon Jun 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") of <NULL>
+NOTICE:  BEFORE INSERT ["Sat Jan 01 00:00:00 2022","Wed Jan 01 00:00:00 2025") of <NULL>
+NOTICE:  AFTER INSERT ["Mon Jun 01 00:00:00 2020","Fri Jan 01 00:00:00 2021") of <NULL>
+NOTICE:  AFTER INSERT ["Sat Jan 01 00:00:00 2022","Wed Jan 01 00:00:00 2025") of <NULL>
+NOTICE:  AFTER UPDATE ["Fri Jan 01 00:00:00 2021","Sat Jan 01 00:00:00 2022") of ["Mon Jun 01 00:00:00 2020","Wed Jan 01 00:00:00 2025")
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2023-01-01' TO '2024-01-01'
+WHERE id = '[5,6)';
+NOTICE:  BEFORE DELETE <NULL> of ["Sat Jan 01 00:00:00 2022","Wed Jan 01 00:00:00 2025")
+NOTICE:  BEFORE INSERT ["Sat Jan 01 00:00:00 2022","Sun Jan 01 00:00:00 2023") of <NULL>
+NOTICE:  BEFORE INSERT ["Mon Jan 01 00:00:00 2024","Wed Jan 01 00:00:00 2025") of <NULL>
+NOTICE:  AFTER INSERT ["Sat Jan 01 00:00:00 2022","Sun Jan 01 00:00:00 2023") of <NULL>
+NOTICE:  AFTER INSERT ["Mon Jan 01 00:00:00 2024","Wed Jan 01 00:00:00 2025") of <NULL>
+NOTICE:  AFTER DELETE <NULL> of ["Sat Jan 01 00:00:00 2022","Wed Jan 01 00:00:00 2025")
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d9ce961be2..7663d94dde 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -48,6 +48,7 @@ f_star|f
 fast_emp4000|t
 float4_tbl|f
 float8_tbl|f
+for_portion_of_test|t
 func_index_heap|t
 hash_f8_heap|t
 hash_i4_heap|t
@@ -173,6 +174,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+referencing_period_test|t
 reservations|f
 road|t
 shighway|t
@@ -215,6 +217,7 @@ trigger_parted_p1|t
 trigger_parted_p1_1|t
 varchar_tbl|f
 view_base_table|t
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/expected/without_overlaps.out b/src/test/regress/expected/without_overlaps.out
new file mode 100644
index 0000000000..8c167ebb83
--- /dev/null
+++ b/src/test/regress/expected/without_overlaps.out
@@ -0,0 +1,363 @@
+-- Tests for WITHOUT OVERLAPS.
+--
+-- test input parser
+--
+-- PK with no columns just WITHOUT OVERLAPS:
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+ERROR:  syntax error at or near "WITHOUT"
+LINE 3: ...STRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OV...
+                                                             ^
+-- PK with a range column that isn't there:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  range or PERIOD "valid_at" named in WITHOUT OVERLAPS does not exist
+-- PK with a PERIOD that isn't there:
+-- PK with a non-range column:
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ERROR:  column "valid_at" named in WITHOUT OVERLAPS is not a range type
+-- PK with one column plus a range:
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+-- PK with one column plus a PERIOD:
+-- PK with two columns plus a PERIOD:
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+            pg_get_constraintdef             
+---------------------------------------------
+ PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+(1 row)
+
+--
+-- test PK inserts
+--
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+ERROR:  conflicting key value violates exclusion constraint "without_overlaps_pk"
+DETAIL:  Key (id, valid_at)=([1,2), ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")) conflicts with existing key (id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sat Feb 03 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+ERROR:  null value in column "id" of relation "without_overlaps_test" violates not-null constraint
+DETAIL:  Failing row contains (null, ["Mon Jan 01 00:00:00 2018","Fri Jan 05 00:00:00 2018")).
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+ERROR:  null value in column "valid_at" of relation "without_overlaps_test" violates not-null constraint
+DETAIL:  Failing row contains ([3,4), null).
+--
+-- test changing the PK's dependencies
+--
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ERROR:  column "valid_at" is in a primary key
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
+--
+-- test FK parser
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+ERROR:  foreign key referenced-columns list must not contain duplicates
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+--
+-- test pg_get_constraintdef
+--
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+                                      pg_get_constraintdef                                      
+------------------------------------------------------------------------------------------------
+ FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES without_overlaps_test(id, PERIOD valid_at)
+(1 row)
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Sun Apr 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([1,2), ["Tue Jan 02 00:00:00 2018","Tue May 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+ERROR:  insert or update on table "referencing_period_test" violates foreign key constraint "referencing_period_fk"
+DETAIL:  Key (parent_id, valid_at)=([8,9), ["Tue Jan 02 00:00:00 2018","Thu Mar 01 00:00:00 2018")) is not present in table "without_overlaps_test".
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+--
+-- test FK parent updates SET NULL
+--
+--
+-- test FK parent updates SET DEFAULT
+--
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+ERROR:  update or delete on table "without_overlaps_test" violates foreign key constraint "referencing_period_fk" on table "referencing_period_test"
+DETAIL:  Key (id, valid_at)=([5,6), ["Mon Jan 01 00:00:00 2018","Thu Feb 01 00:00:00 2018")) is still referenced from table "referencing_period_test".
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+--
+-- test FK parent deletes SET NULL
+--
+--
+-- test FK parent deletes SET DEFAULT
+--
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 5355da7b01..dade7844bf 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -108,12 +108,12 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml without_overlaps
 
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain without_overlaps for_portion_of
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 1f677f63af..c31e58137e 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -20,6 +20,8 @@ test: enum
 test: money
 test: rangetypes
 test: multirangetypes
+test: without_overlaps
+test: for_portion_of
 test: pg_lsn
 test: regproc
 test: strings
diff --git a/src/test/regress/sql/for_portion_of.sql b/src/test/regress/sql/for_portion_of.sql
new file mode 100644
index 0000000000..81bdb05b01
--- /dev/null
+++ b/src/test/regress/sql/for_portion_of.sql
@@ -0,0 +1,239 @@
+-- Tests for UPDATE/DELETE FOR PORTION OF
+
+-- Fails on tables without a temporal PK:
+CREATE TABLE for_portion_of_test (
+  id int4range PRIMARY KEY,
+  valid_at tsrange NOT NULL,
+  name text NOT NULL
+);
+
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-01-15' TO 'Infinity'
+SET name = 'foo';
+
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-01-15' TO 'Infinity';
+
+DROP TABLE for_portion_of_test;
+CREATE TABLE for_portion_of_test (
+  id int4range NOT NULL,
+  valid_at tsrange NOT NULL,
+  name text NOT NULL,
+	CONSTRAINT for_portion_of_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO for_portion_of_test
+VALUES
+('[1,2)', '[2018-01-02,2018-02-03)', 'one'),
+('[1,2)', '[2018-02-03,2018-03-03)', 'one'),
+('[1,2)', '[2018-03-03,2018-04-04)', 'one'),
+('[2,3)', '[2018-01-01,2018-01-05)', 'two'),
+('[3,4)', '[2018-01-01,)', 'three'),
+('[4,5)', '(,2018-04-01)', 'four'),
+('[5,6)', '(,)', 'five')
+;
+
+--
+-- UPDATE tests
+--
+
+-- Setting with a missing column fails
+UPDATE for_portion_of_test
+FOR PORTION OF invalid_at FROM '2018-06-01' TO 'Infinity'
+SET name = 'foo'
+WHERE id = '[5,6)';
+
+-- Setting the range fails
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO 'Infinity'
+SET valid_at = '[1990-01-01,1999-01-01)'
+WHERE id = '[5,6)';
+
+-- Setting with timestamps reversed fails
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO '2018-01-01'
+SET name = 'three^1'
+WHERE id = '[3,4)';
+
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM 'Infinity' TO '-Infinity'
+SET name = 'three^1'
+WHERE id = '[3,4)';
+
+-- Setting with timestamps equal does nothing
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-04-01' TO '2018-04-01'
+SET name = 'three^0'
+WHERE id = '[3,4)';
+
+-- Updating a finite/open portion with a finite/open target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO 'Infinity'
+SET name = 'three^1'
+WHERE id = '[3,4)';
+
+-- Updating a finite/open portion with an open/finite target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO '2018-03-01'
+SET name = 'three^2'
+WHERE id = '[3,4)';
+
+-- Updating an open/finite portion with an open/finite target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO '2018-02-01'
+SET name = 'four^1'
+WHERE id = '[4,5)';
+
+-- Updating an open/finite portion with a finite/open target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2017-01-01' TO 'Infinity'
+SET name = 'four^2'
+WHERE id = '[4,5)';
+
+-- Updating a finite/finite portion with an exact fit
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2017-01-01' TO '2018-02-01'
+SET name = 'four^3'
+WHERE id = '[4,5)';
+
+-- Updating an enclosed span
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO 'Infinity'
+SET name = 'two^2'
+WHERE id = '[2,3)';
+
+-- Updating an open/open portion with a finite/finite target
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-01-01' TO '2019-01-01'
+SET name = 'five^2'
+WHERE id = '[5,6)';
+
+-- Updating an enclosed span with separate protruding spans
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2017-01-01' TO '2020-01-01'
+SET name = 'five^3'
+WHERE id = '[5,6)';
+
+-- Updating multiple enclosed spans
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO 'Infinity'
+SET name = 'one^2'
+WHERE id = '[1,2)';
+
+-- Updating the non-range part of the PK:
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-02-15' TO 'Infinity'
+SET id = '[6,7)'
+WHERE id = '[1,2)';
+
+-- UPDATE with no WHERE clause
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2030-01-01' TO 'Infinity'
+SET name = name || '*';
+
+-- TODO: UPDATE with generated columns too
+SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
+
+--
+-- DELETE tests
+--
+
+-- Deleting with a missing column fails
+DELETE FROM for_portion_of_test
+FOR PORTION OF invalid_at FROM '2018-06-01' TO 'Infinity'
+WHERE id = '[5,6)';
+
+-- Deleting with timestamps reversed fails
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO '2018-01-01'
+WHERE id = '[3,4)';
+
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM 'Infinity' TO '-Infinity'
+WHERE id = '[3,4)';
+
+-- Deleting with timestamps equal does nothing
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-04-01' TO '2018-04-01'
+WHERE id = '[3,4)';
+
+-- Deleting with a closed/closed target
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-06-01' TO '2020-06-01'
+WHERE id = '[5,6)';
+
+-- Deleting with a closed/open target
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2018-04-01' TO 'Infinity'
+WHERE id = '[3,4)';
+
+-- Deleting with an open/closed target
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO '2018-02-08'
+WHERE id = '[1,2)';
+
+-- Deleting with an open/open target
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '-Infinity' TO 'Infinity'
+WHERE id = '[6,7)';
+
+-- DELETE with no WHERE clause
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2025-01-01' TO 'Infinity';
+
+SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
+
+-- TODO: and UPDATE ... RETURNING ... returns only the updated values (not the inserted side values)
+
+-- test that we run triggers on the UPDATE/DELETEd row and the INSERTed rows
+
+CREATE FUNCTION for_portion_of_trigger()
+RETURNS trigger
+AS
+$$
+BEGIN
+  RAISE NOTICE '% % % of %', TG_WHEN, TG_OP, NEW.valid_at, OLD.valid_at;
+  IF TG_OP = 'DELETE' THEN
+    RETURN OLD;
+  ELSE
+    RETURN NEW;
+  END IF;
+END;
+$$
+LANGUAGE plpgsql;
+
+CREATE TRIGGER trg_for_portion_of_before_insert
+  BEFORE INSERT ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_after_insert
+  AFTER INSERT ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_before_update
+  BEFORE UPDATE ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_after_update
+  AFTER UPDATE ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_before_delete
+  BEFORE DELETE ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+CREATE TRIGGER trg_for_portion_of_after_delete
+  AFTER DELETE ON for_portion_of_test
+  FOR EACH ROW
+  EXECUTE FUNCTION for_portion_of_trigger();
+
+UPDATE for_portion_of_test
+FOR PORTION OF valid_at FROM '2021-01-01' TO '2022-01-01'
+SET name = 'five^4'
+WHERE id = '[5,6)';
+
+DELETE FROM for_portion_of_test
+FOR PORTION OF valid_at FROM '2023-01-01' TO '2024-01-01'
+WHERE id = '[5,6)';
+
+-- test that we run triggers on the DELETEd row and the INSERTed rows
+SELECT * FROM for_portion_of_test ORDER BY id, valid_at;
diff --git a/src/test/regress/sql/without_overlaps.sql b/src/test/regress/sql/without_overlaps.sql
new file mode 100644
index 0000000000..83824a55d3
--- /dev/null
+++ b/src/test/regress/sql/without_overlaps.sql
@@ -0,0 +1,369 @@
+-- Tests for WITHOUT OVERLAPS.
+
+--
+-- test input parser
+--
+
+-- PK with no columns just WITHOUT OVERLAPS:
+
+CREATE TABLE without_overlaps_test (
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a range column that isn't there:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with a PERIOD that isn't there:
+-- TODO
+
+-- PK with a non-range column:
+
+CREATE TABLE without_overlaps_test (
+	id INTEGER,
+	valid_at TEXT,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with one column plus a range:
+
+CREATE TABLE without_overlaps_test (
+	-- Since we can't depend on having btree_gist here,
+	-- use an int4range instead of an int.
+	-- (The rangetypes regression test uses the same trick.)
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+-- PK with two columns plus a range:
+CREATE TABLE without_overlaps_test2 (
+	id1 int4range,
+	id2 int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id1, id2, valid_at WITHOUT OVERLAPS)
+);
+DROP TABLE without_overlaps_test2;
+
+
+-- PK with one column plus a PERIOD:
+-- TODO
+
+-- PK with two columns plus a PERIOD:
+-- TODO
+
+-- PK with a custom range type:
+CREATE TYPE textrange2 AS range (subtype=text, collation="C");
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at textrange2,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE without_overlaps_test2 DROP CONSTRAINT without_overlaps2_pk;
+DROP TABLE without_overlaps_test2;
+DROP TYPE textrange2;
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+DROP TABLE without_overlaps_test;
+CREATE TABLE without_overlaps_test (
+	id int4range,
+	valid_at tsrange
+);
+ALTER TABLE without_overlaps_test
+	ADD CONSTRAINT without_overlaps_pk
+	PRIMARY KEY (id, valid_at WITHOUT OVERLAPS);
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'without_overlaps_pk';
+
+--
+-- test PK inserts
+--
+
+-- okay:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-03'));
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-03-03', '2018-04-04'));
+INSERT INTO without_overlaps_test VALUES ('[2,2]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', tsrange('2018-01-01', NULL));
+
+-- should fail:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES (NULL, tsrange('2018-01-01', '2018-01-05'));
+INSERT INTO without_overlaps_test VALUES ('[3,3]', NULL);
+
+--
+-- test changing the PK's dependencies
+--
+
+CREATE TABLE without_overlaps_test2 (
+	id int4range,
+	valid_at tsrange,
+	CONSTRAINT without_overlaps2_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at DROP NOT NULL;
+ALTER TABLE without_overlaps_test2 ALTER COLUMN valid_at TYPE tstzrange USING tstzrange(lower(valid_at), upper(valid_at));
+ALTER TABLE without_overlaps_test2 RENAME COLUMN valid_at TO valid_thru;
+ALTER TABLE without_overlaps_test2 DROP COLUMN valid_thru;
+DROP TABLE without_overlaps_test2;
+
+--
+-- test FK parser
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test (id, PERIOD valid_at)
+);
+DROP TABLE referencing_period_test;
+
+-- with inferred PK on the referenced table:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD valid_at)
+		REFERENCES without_overlaps_test
+);
+DROP TABLE referencing_period_test;
+
+-- should fail because of duplicate referenced columns:
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
+	CONSTRAINT referencing_period_fk FOREIGN KEY (parent_id, PERIOD parent_id)
+		REFERENCES without_overlaps_test (id, PERIOD id)
+);
+
+--
+-- test ALTER TABLE ADD CONSTRAINT
+--
+
+CREATE TABLE referencing_period_test (
+	id int4range,
+	valid_at tsrange,
+	parent_id int4range,
+	CONSTRAINT referencing_period_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
+);
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test (id, PERIOD valid_at);
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+-- with inferred PK on the referenced table:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+-- should fail because of duplicate referenced columns:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk2
+  FOREIGN KEY (parent_id, PERIOD parent_id)
+  REFERENCES without_overlaps_test (id, PERIOD id);
+
+--
+-- test with rows already
+--
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- should fail:
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- okay again:
+DELETE FROM referencing_period_test;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+
+--
+-- test pg_get_constraintdef
+--
+
+SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = 'referencing_period_fk';
+
+--
+-- test FK child inserts
+--
+INSERT INTO referencing_period_test VALUES ('[1,1]', tsrange('2018-01-02', '2018-02-01'), '[1,1]');
+-- should fail:
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+-- now it should work:
+INSERT INTO without_overlaps_test VALUES ('[1,1]', tsrange('2018-02-03', '2018-03-03'));
+INSERT INTO referencing_period_test VALUES ('[2,2]', tsrange('2018-01-02', '2018-04-01'), '[1,1]');
+
+--
+-- test FK child updates
+--
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-03-01') WHERE id = '[1,1]';
+-- should fail:
+UPDATE referencing_period_test SET valid_at = tsrange('2018-01-02', '2018-05-01') WHERE id = '[1,1]';
+UPDATE referencing_period_test SET parent_id = '[8,8]' WHERE id = '[1,1]';
+
+--
+-- test FK parent updates NO ACTION
+--
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+-- a PK update that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01') WHERE id = '[5,5]';
+-- a PK update that succeeds even though the numeric id is referenced because the range isn't:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-02-01', '2016-03-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK update that fails because both are referenced:
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK update succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+UPDATE without_overlaps_test SET valid_at = tsrange('2016-01-01', '2016-02-01')
+  WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- clean up:
+DELETE FROM referencing_period_test WHERE parent_id = '[5,5]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+--
+-- test FK parent updates CASCADE
+--
+-- TODO
+--
+-- test FK parent updates SET NULL
+--
+-- TODO
+--
+-- test FK parent updates SET DEFAULT
+--
+-- TODO
+
+--
+-- test FK parent deletes NO ACTION
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test;
+-- a PK delete that succeeds because the numeric id isn't referenced:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes RESTRICT
+--
+ALTER TABLE referencing_period_test
+  DROP CONSTRAINT referencing_period_fk;
+ALTER TABLE referencing_period_test
+  ADD CONSTRAINT referencing_period_fk
+  FOREIGN KEY (parent_id, PERIOD valid_at)
+  REFERENCES without_overlaps_test
+  ON DELETE RESTRICT;
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+DELETE FROM without_overlaps_test WHERE id = '[5,5]';
+-- a PK delete that succeeds even though the numeric id is referenced because the range isn't:
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-01-01', '2018-02-01'));
+INSERT INTO without_overlaps_test VALUES ('[5,5]', tsrange('2018-02-01', '2018-03-01'));
+INSERT INTO referencing_period_test VALUES ('[3,3]', tsrange('2018-01-05', '2018-01-10'), '[5,5]');
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-02-01', '2018-03-01');
+-- a PK delete that fails because both are referenced:
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+-- then delete the objecting FK record and the same PK delete succeeds:
+DELETE FROM referencing_period_test WHERE id = '[3,3]';
+DELETE FROM without_overlaps_test WHERE id = '[5,5]' AND valid_at = tsrange('2018-01-01', '2018-02-01');
+--
+-- test FK parent deletes CASCADE
+--
+-- TODO
+--
+-- test FK parent deletes SET NULL
+--
+-- TODO
+--
+-- test FK parent deletes SET DEFAULT
+--
+-- TODO
#28Michael Paquier
michael@paquier.xyz
In reply to: Paul A Jungwirth (#27)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Wed, Mar 11, 2020 at 04:27:53PM -0700, Paul A Jungwirth wrote:

Here is a patch rebasing on master (meant to be applied on top of my
other multirange patch) and newly including UPDATE/DELETE FOR PORTION
OF. FOR PORTION OF works on any table with a temporal primary key. It
restricts the UPDATE/DELETE to the given time frame, and then if the
affected row(s) had any "leftovers" above or below the targeted range,
it INSERTs new rows to preserve the untouched intervals.

This patch had no reviews, unfortunately it cannot be applied
cleanly. Could you send a rebase please?
--
Michael

#29Michael Paquier
michael@paquier.xyz
In reply to: Michael Paquier (#28)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Thu, Sep 17, 2020 at 04:51:01PM +0900, Michael Paquier wrote:

This patch had no reviews, unfortunately it cannot be applied
cleanly. Could you send a rebase please?

This had no replies after two weeks, so I have marked the patch as
RwF. Please feel free to resubmit if you are planning to work more on
that.
--
Michael

#30Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: Michael Paquier (#29)
1 attachment(s)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Wed, Sep 30, 2020 at 12:39 PM Michael Paquier <michael@paquier.xyz>
wrote:

On Thu, Sep 17, 2020 at 04:51:01PM +0900, Michael Paquier wrote:

This patch had no reviews, unfortunately it cannot be applied
cleanly. Could you send a rebase please?

This had no replies after two weeks, so I have marked the patch as
RwF. Please feel free to resubmit if you are planning to work more on
that.
--
Michael

I have spent some more time on the patch and did a lot of cleanup along
with some fixes, compilation errors, and warnings. There is a need for
another round of clean up to make that patch perfect. Currently, it based
on master (0525572860335d050a1bea194a5278c8833304d1). Regression is not
passing because it is dependent on the range_agg patch. Currently, I am
collecting all the TODO's and try to finish all of these.

The patch requires some reviews, so I am putting that again in the current
commitfest.

--
Ibrar Ahmed

Attachments:

v0004-temporal-pk-fk-update-delete.patchapplication/octet-stream; name=v0004-temporal-pk-fk-update-delete.patchDownload
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index fd6777ae01..7da5c76aef 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -78,9 +78,9 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI
 [ CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> ]
 { CHECK ( <replaceable class="parameter">expression</replaceable> ) [ NO INHERIT ] |
   UNIQUE ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
-  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> |
+  PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] ) <replaceable class="parameter">index_parameters</replaceable> |
   EXCLUDE [ USING <replaceable class="parameter">index_method</replaceable> ] ( <replaceable class="parameter">exclude_element</replaceable> WITH <replaceable class="parameter">operator</replaceable> [, ... ] ) <replaceable class="parameter">index_parameters</replaceable> [ WHERE ( <replaceable class="parameter">predicate</replaceable> ) ] |
-  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+  FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE <replaceable
 class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -105,6 +105,11 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 <phrase><replaceable class="parameter">exclude_element</replaceable> in an <literal>EXCLUDE</literal> constraint is:</phrase>
 
 { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ]
+
+<phrase><replaceable class="parameter">temporal_interval</replaceable> in a <literal>PRIMARY KEY</literal> or <literal>FOREIGN KEY</literal> constraint is:</phrase>
+
+<replaceable class="parameter">range_column_name</replaceable>
+
 </synopsis>
 
  </refsynopsisdiv>
@@ -922,7 +927,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term><literal>PRIMARY KEY</literal> (column constraint)</term>
-    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )</literal>
+    <term><literal>PRIMARY KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ]
+    [, <replaceable class="parameter">temporal_interval</replaceable> WITHOUT OVERLAPS ] )</literal>
     <optional> INCLUDE ( <replaceable class="parameter">column_name</replaceable> [, ...]) </optional> (table constraint)</term>
     <listitem>
      <para>
@@ -955,14 +961,31 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
      <para>
       Adding a <literal>PRIMARY KEY</literal> constraint will automatically
-      create a unique btree index on the column or group of columns used in the
-      constraint.  The optional <literal>INCLUDE</literal> clause allows a list
-      of columns to be specified which will be included in the non-key portion
-      of the index.  Although uniqueness is not enforced on the included columns,
-      the constraint still depends on them. Consequently, some operations on the
-      included columns (e.g., <literal>DROP COLUMN</literal>) can cause cascaded
-      constraint and index deletion.
-     </para>
+      create a unique btree (or GiST if temporal) index on the column or group of
+	  columns used in the constraint.  The optional <literal>INCLUDE</literal> clause
+	  allows a list of columns to be specified which will be included in the non-key
+	  portion of the index.  Although uniqueness is not enforced on the included
+	  columns, the constraint still depends on them. Consequently, some operations
+	  on the included columns (e.g., <literal>DROP COLUMN</literal>) can cause
+	  cascaded constraint and index deletion.
+
+	<para>
+	 A <literal>PRIMARY KEY</literal> with a <literal>WITHOUT OVERLAPS</literal> option
+	 is a <emphasis>temporal</emphasis> primary key.
+	 The <literal>WITHOUT OVERLAPS</literal> column
+	 must be a range type and is used to constrain the record's applicability
+	 to just that range (usually a range of dates or timestamps).
+	 The main part of the primary key may be repeated elsewhere in the table,
+	 as long as records with the same key don't overlap in the
+	 <literal>WITHOUT OVERLAPS</literal> column.
+	</para>
+
+	<para>
+	 A temporal <literal>PRIMARY KEY</literal> is enforced with an
+	 <literal>EXCLUDE</literal> constraint rather than a <literal>UNIQUE</literal>
+	 constraint, backed by a GiST index. You may need to install the
+	 <xref linkend="btree-gist"/> extension to create temporal primary keys.
+    </para>
     </listitem>
    </varlistentry>
 
@@ -1019,8 +1042,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    <varlistentry>
     <term><literal>REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH <replaceable class="parameter">matchtype</replaceable> ] [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal> (column constraint)</term>
 
-   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] )
-    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] ) ]
+   <term><literal>FOREIGN KEY ( <replaceable class="parameter">column_name</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] )
+    REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> [, ... ] [, PERIOD <replaceable class="parameter">temporal_interval</replaceable> ] ) ]
     [ MATCH <replaceable class="parameter">matchtype</replaceable> ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ]
     [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ]</literal>
@@ -1031,11 +1054,29 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       These clauses specify a foreign key constraint, which requires
       that a group of one or more columns of the new table must only
       contain values that match values in the referenced
-      column(s) of some row of the referenced table.  If the <replaceable
+      column(s) of some row of the referenced table.
+      If the <replaceable
       class="parameter">refcolumn</replaceable> list is omitted, the
       primary key of the <replaceable class="parameter">reftable</replaceable>
       is used.  The referenced columns must be the columns of a non-deferrable
-      unique or primary key constraint in the referenced table.  The user
+      unique or primary key constraint in the referenced table.
+     </para>
+
+     <para>
+      If the last column is marked with <literal>PERIOD</literal>,
+      it must be a range column, and the referenced table
+      must have a temporal primary key.
+      The non-<literal>PERIOD</literal> columns are treated normally
+      (and there must be at least one of them),
+      but the <literal>PERIOD</literal> column is not compared for equality.
+      Instead the constraint is considered satisfied
+      if the referenced table has matching records whose combined ranges completely cover
+      the referencing record.
+      In other words, the reference must have a referent for its entire duration.
+     </para>
+
+     <para>
+      The user
       must have <literal>REFERENCES</literal> permission on the referenced table
       (either the whole table, or the specific referenced columns).  The
       addition of a foreign key constraint requires a
diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index dd39a086ce..47afc4ef7b 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -237,6 +237,7 @@ sub ParseData
 	# Scan the input file.
 	while (<$ifd>)
 	{
+    next if /^#/;
 		my $hash_ref;
 
 		if (/{/)
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 67144aa3c9..b0af555469 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2462,6 +2462,7 @@ StoreRelCheck(Relation rel, const char *ccname, Node *expr,
 							  is_local, /* conislocal */
 							  inhcount, /* coninhcount */
 							  is_no_inherit,	/* connoinherit */
+							  false,	/* contemporal */
 							  is_internal); /* internally constructed? */
 
 	pfree(ccbin);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0974f3e23a..af1a1ec6b3 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1788,6 +1788,7 @@ index_concurrently_set_dead(Oid heapId, Oid indexId)
  *		INDEX_CONSTR_CREATE_UPDATE_INDEX: update the pg_index row
  *		INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS: remove existing dependencies
  *			of index on table's columns
+ *		INDEX_CONSTR_CREATE_TEMPORAL: constraint is for a temporal primary key
  * allow_system_table_mods: allow table to be a system catalog
  * is_internal: index is constructed due to internal process
  */
@@ -1811,11 +1812,13 @@ index_constraint_create(Relation heapRelation,
 	bool		mark_as_primary;
 	bool		islocal;
 	bool		noinherit;
+	bool		is_temporal;
 	int			inhcount;
 
 	deferrable = (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) != 0;
 	initdeferred = (constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) != 0;
 	mark_as_primary = (constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY) != 0;
+	is_temporal = (constr_flags & INDEX_CONSTR_CREATE_TEMPORAL) != 0;
 
 	/* constraint creation support doesn't work while bootstrapping */
 	Assert(!IsBootstrapProcessingMode());
@@ -1890,6 +1893,7 @@ index_constraint_create(Relation heapRelation,
 								   islocal,
 								   inhcount,
 								   noinherit,
+								   is_temporal,	/* contemporal */
 								   is_internal);
 
 	/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 0d70cb0c3c..e64184b908 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -75,6 +75,7 @@ CreateConstraintEntry(const char *constraintName,
 					  bool conIsLocal,
 					  int conInhCount,
 					  bool conNoInherit,
+					  bool conTemporal,
 					  bool is_internal)
 {
 	Relation	conDesc;
@@ -185,6 +186,7 @@ CreateConstraintEntry(const char *constraintName,
 	values[Anum_pg_constraint_conislocal - 1] = BoolGetDatum(conIsLocal);
 	values[Anum_pg_constraint_coninhcount - 1] = Int32GetDatum(conInhCount);
 	values[Anum_pg_constraint_connoinherit - 1] = BoolGetDatum(conNoInherit);
+	values[Anum_pg_constraint_contemporal - 1] = BoolGetDatum(conTemporal);
 
 	if (conkeyArray)
 		values[Anum_pg_constraint_conkey - 1] = PointerGetDatum(conkeyArray);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 75552c64ed..a3a0bb100d 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1114,6 +1114,8 @@ DefineIndex(Oid relationId,
 		constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;
 	if (stmt->initdeferred)
 		constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;
+	if (stmt->istemporal)
+		constr_flags |= INDEX_CONSTR_CREATE_TEMPORAL;
 
 	indexRelationId =
 		index_create(rel, indexRelationName, indexRelationId, parentIndexId,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a29c14bf1c..a17fd41a89 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -336,16 +336,19 @@ static int	transformColumnNameList(Oid relId, List *colList,
 static int	transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 									   List **attnamelist,
 									   int16 *attnums, Oid *atttypids,
+										 Node **periodattname,
+										 int16 *periodattnums, Oid *periodatttypids,
 									   Oid *opclasses);
 static Oid	transformFkeyCheckAttrs(Relation pkrel,
 									int numattrs, int16 *attnums,
+									bool is_temporal, int16 *periodattnums,
 									Oid *opclasses);
 static void checkFkeyPermissions(Relation rel, int16 *attnums, int natts);
 static CoercionPathType findFkeyCast(Oid targetTypeId, Oid sourceTypeId,
 									 Oid *funcid);
 static void validateForeignKeyConstraint(char *conname,
 										 Relation rel, Relation pkrel,
-										 Oid pkindOid, Oid constraintOid);
+										 Oid pkindOid, Oid constraintOid, bool temporal);
 static void ATController(AlterTableStmt *parsetree,
 						 Relation rel, List *cmds, bool recurse, LOCKMODE lockmode,
 						 AlterTableUtilityContext *context);
@@ -450,12 +453,12 @@ static ObjectAddress addFkRecurseReferenced(List **wqueue, Constraint *fkconstra
 											Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 											int numfks, int16 *pkattnum, int16 *fkattnum,
 											Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-											bool old_check_ok);
+											bool old_check_ok, bool is_temporal);
 static void addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint,
 									Relation rel, Relation pkrel, Oid indexOid, Oid parentConstr,
 									int numfks, int16 *pkattnum, int16 *fkattnum,
 									Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-									bool old_check_ok, LOCKMODE lockmode);
+									bool old_check_ok, bool is_temporal, LOCKMODE lockmode);
 static void CloneForeignKeyConstraints(List **wqueue, Relation parentRel,
 									   Relation partitionRel);
 static void CloneFkReferenced(Relation parentRel, Relation partitionRel);
@@ -472,6 +475,12 @@ static bool tryAttachPartitionForeignKey(ForeignKeyCacheInfo *fk,
 										 Oid parentConstrOid, int numfks,
 										 AttrNumber *mapped_conkey, AttrNumber *confkey,
 										 Oid *conpfeqop);
+static void FindFKComparisonOperators(Constraint *fkconstraint,
+					AlteredTableInfo *tab, int i, int16 *fkattnum,
+					bool *old_check_ok, ListCell **old_pfeqop_item,
+					Oid pktype, Oid fktype, Oid opclass,
+					bool is_temporal, bool for_overlaps,
+					Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut);
 static void ATExecDropConstraint(Relation rel, const char *constrName,
 								 DropBehavior behavior,
 								 bool recurse, bool recursing,
@@ -5118,7 +5127,8 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
 
 				validateForeignKeyConstraint(fkconstraint->conname, rel, refrel,
 											 con->refindid,
-											 con->conid);
+											 con->conid,
+											 fkconstraint->fk_period != NULL);
 
 				/*
 				 * No need to mark the constraint row as validated, we did
@@ -8353,6 +8363,11 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	Oid			pfeqoperators[INDEX_MAX_KEYS];
 	Oid			ppeqoperators[INDEX_MAX_KEYS];
 	Oid			ffeqoperators[INDEX_MAX_KEYS];
+	bool		is_temporal = (fkconstraint->fk_period != NULL);
+	int16		pkperiodattnum = 0;
+	int16		fkperiodattnum = 0;
+	Oid			pkperiodtypoid = 0;
+	Oid			fkperiodtypoid = 0;
 	int			i;
 	int			numfks,
 				numpks;
@@ -8455,6 +8470,14 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	numfks = transformColumnNameList(RelationGetRelid(rel),
 									 fkconstraint->fk_attrs,
 									 fkattnum, fktypoid);
+	if (is_temporal)
+	{
+	  List *fk_period;
+	  fk_period = list_make1(fkconstraint->fk_period);
+	  transformColumnNameList(RelationGetRelid(rel),
+							  fk_period,
+							  &fkperiodattnum, &fkperiodtypoid);
+	}
 
 	/*
 	 * If the attribute list for the referenced table was omitted, lookup the
@@ -8467,6 +8490,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformFkeyGetPrimaryKey(pkrel, &indexOid,
 											&fkconstraint->pk_attrs,
 											pkattnum, pktypoid,
+											&fkconstraint->pk_period,
+											&pkperiodattnum, &pkperiodtypoid,
 											opclasses);
 	}
 	else
@@ -8474,8 +8499,15 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		numpks = transformColumnNameList(RelationGetRelid(pkrel),
 										 fkconstraint->pk_attrs,
 										 pkattnum, pktypoid);
+		if (is_temporal) {
+			List *pk_period = list_make1(fkconstraint->pk_period);
+			transformColumnNameList(RelationGetRelid(pkrel),
+									pk_period,
+									&pkperiodattnum, &pkperiodtypoid);
+		}
 		/* Look for an index matching the column list */
 		indexOid = transformFkeyCheckAttrs(pkrel, numpks, pkattnum,
+										   is_temporal, &pkperiodattnum,
 										   opclasses);
 	}
 
@@ -8525,6 +8557,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 				 errmsg("number of referencing and referenced columns for foreign key disagree")));
 
+	// TODO: Need a check that if one side has a PERIOD the other does too
+
 	/*
 	 * On the strength of a previous constraint, we might avoid scanning
 	 * tables to validate this one.  See below.
@@ -8534,187 +8568,27 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	for (i = 0; i < numpks; i++)
 	{
-		Oid			pktype = pktypoid[i];
-		Oid			fktype = fktypoid[i];
-		Oid			fktyped;
-		HeapTuple	cla_ht;
-		Form_pg_opclass cla_tup;
-		Oid			amid;
-		Oid			opfamily;
-		Oid			opcintype;
-		Oid			pfeqop;
-		Oid			ppeqop;
-		Oid			ffeqop;
-		int16		eqstrategy;
-		Oid			pfeqop_right;
-
-		/* We need several fields out of the pg_opclass entry */
-		cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclasses[i]));
-		if (!HeapTupleIsValid(cla_ht))
-			elog(ERROR, "cache lookup failed for opclass %u", opclasses[i]);
-		cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
-		amid = cla_tup->opcmethod;
-		opfamily = cla_tup->opcfamily;
-		opcintype = cla_tup->opcintype;
-		ReleaseSysCache(cla_ht);
-
-		/*
-		 * Check it's a btree; currently this can never fail since no other
-		 * index AMs support unique indexes.  If we ever did have other types
-		 * of unique indexes, we'd need a way to determine which operator
-		 * strategy number is equality.  (Is it reasonable to insist that
-		 * every such index AM use btree's number for equality?)
-		 */
-		if (amid != BTREE_AM_OID)
-			elog(ERROR, "only b-tree indexes are supported for foreign keys");
-		eqstrategy = BTEqualStrategyNumber;
-
-		/*
-		 * There had better be a primary equality operator for the index.
-		 * We'll use it for PK = PK comparisons.
-		 */
-		ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
-									 eqstrategy);
-
-		if (!OidIsValid(ppeqop))
-			elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
-				 eqstrategy, opcintype, opcintype, opfamily);
-
-		/*
-		 * Are there equality operators that take exactly the FK type? Assume
-		 * we should look through any domain here.
-		 */
-		fktyped = getBaseType(fktype);
-
-		pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
-									 eqstrategy);
-		if (OidIsValid(pfeqop))
-		{
-			pfeqop_right = fktyped;
-			ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
-										 eqstrategy);
-		}
-		else
-		{
-			/* keep compiler quiet */
-			pfeqop_right = InvalidOid;
-			ffeqop = InvalidOid;
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-		{
-			/*
-			 * Otherwise, look for an implicit cast from the FK type to the
-			 * opcintype, and if found, use the primary equality operator.
-			 * This is a bit tricky because opcintype might be a polymorphic
-			 * type such as ANYARRAY or ANYENUM; so what we have to test is
-			 * whether the two actual column types can be concurrently cast to
-			 * that type.  (Otherwise, we'd fail to reject combinations such
-			 * as int[] and point[].)
-			 */
-			Oid			input_typeids[2];
-			Oid			target_typeids[2];
-
-			input_typeids[0] = pktype;
-			input_typeids[1] = fktype;
-			target_typeids[0] = opcintype;
-			target_typeids[1] = opcintype;
-			if (can_coerce_type(2, input_typeids, target_typeids,
-								COERCION_IMPLICIT))
-			{
-				pfeqop = ffeqop = ppeqop;
-				pfeqop_right = opcintype;
-			}
-		}
-
-		if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
-			ereport(ERROR,
-					(errcode(ERRCODE_DATATYPE_MISMATCH),
-					 errmsg("foreign key constraint \"%s\" cannot be implemented",
-							fkconstraint->conname),
-					 errdetail("Key columns \"%s\" and \"%s\" "
-							   "are of incompatible types: %s and %s.",
-							   strVal(list_nth(fkconstraint->fk_attrs, i)),
-							   strVal(list_nth(fkconstraint->pk_attrs, i)),
-							   format_type_be(fktype),
-							   format_type_be(pktype))));
-
-		if (old_check_ok)
-		{
-			/*
-			 * When a pfeqop changes, revalidate the constraint.  We could
-			 * permit intra-opfamily changes, but that adds subtle complexity
-			 * without any concrete benefit for core types.  We need not
-			 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
-			 */
-			old_check_ok = (pfeqop == lfirst_oid(old_pfeqop_item));
-			old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
-									old_pfeqop_item);
-		}
-		if (old_check_ok)
-		{
-			Oid			old_fktype;
-			Oid			new_fktype;
-			CoercionPathType old_pathtype;
-			CoercionPathType new_pathtype;
-			Oid			old_castfunc;
-			Oid			new_castfunc;
-			Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
-												   fkattnum[i] - 1);
-
-			/*
-			 * Identify coercion pathways from each of the old and new FK-side
-			 * column types to the right (foreign) operand type of the pfeqop.
-			 * We may assume that pg_constraint.conkey is not changing.
-			 */
-			old_fktype = attr->atttypid;
-			new_fktype = fktype;
-			old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
-										&old_castfunc);
-			new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
-										&new_castfunc);
-
-			/*
-			 * Upon a change to the cast from the FK column to its pfeqop
-			 * operand, revalidate the constraint.  For this evaluation, a
-			 * binary coercion cast is equivalent to no cast at all.  While
-			 * type implementors should design implicit casts with an eye
-			 * toward consistency of operations like equality, we cannot
-			 * assume here that they have done so.
-			 *
-			 * A function with a polymorphic argument could change behavior
-			 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
-			 * when the cast destination is polymorphic, we only avoid
-			 * revalidation if the input type has not changed at all.  Given
-			 * just the core data types and operator classes, this requirement
-			 * prevents no would-be optimizations.
-			 *
-			 * If the cast converts from a base type to a domain thereon, then
-			 * that domain type must be the opcintype of the unique index.
-			 * Necessarily, the primary key column must then be of the domain
-			 * type.  Since the constraint was previously valid, all values on
-			 * the foreign side necessarily exist on the primary side and in
-			 * turn conform to the domain.  Consequently, we need not treat
-			 * domains specially here.
-			 *
-			 * Since we require that all collations share the same notion of
-			 * equality (which they do, because texteq reduces to bitwise
-			 * equality), we don't compare collation here.
-			 *
-			 * We need not directly consider the PK type.  It's necessarily
-			 * binary coercible to the opcintype of the unique index column,
-			 * and ri_triggers.c will only deal with PK datums in terms of
-			 * that opcintype.  Changing the opcintype also changes pfeqop.
-			 */
-			old_check_ok = (new_pathtype == old_pathtype &&
-							new_castfunc == old_castfunc &&
-							(!IsPolymorphicType(pfeqop_right) ||
-							 new_fktype == old_fktype));
-		}
+		FindFKComparisonOperators(
+				fkconstraint, tab, i, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pktypoid[i], fktypoid[i], opclasses[i],
+				is_temporal, false,
+				&pfeqoperators[i], &ppeqoperators[i], &ffeqoperators[i]);
+	}
+	if (is_temporal) {
+		pkattnum[numpks] = pkperiodattnum;
+		fkattnum[numpks] = fkperiodattnum;
+		pktypoid[numpks] = pkperiodtypoid;
+		fktypoid[numpks] = fkperiodtypoid;
 
-		pfeqoperators[i] = pfeqop;
-		ppeqoperators[i] = ppeqop;
-		ffeqoperators[i] = ffeqop;
+		FindFKComparisonOperators(
+				fkconstraint, tab, numpks, fkattnum,
+				&old_check_ok, &old_pfeqop_item,
+				pkperiodtypoid, fkperiodtypoid, opclasses[numpks],
+				is_temporal, true,
+				&pfeqoperators[numpks], &ppeqoperators[numpks], &ffeqoperators[numpks]);
+		numfks += 1;
+		numpks += 1;
 	}
 
 	/*
@@ -8730,7 +8604,8 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 									 pfeqoperators,
 									 ppeqoperators,
 									 ffeqoperators,
-									 old_check_ok);
+									 old_check_ok,
+									 is_temporal);
 
 	/* Now handle the referencing side. */
 	addFkRecurseReferencing(wqueue, fkconstraint, rel, pkrel,
@@ -8743,6 +8618,7 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 							ppeqoperators,
 							ffeqoperators,
 							old_check_ok,
+							is_temporal,
 							lockmode);
 
 	/*
@@ -8783,7 +8659,8 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 					   Relation pkrel, Oid indexOid, Oid parentConstr,
 					   int numfks,
 					   int16 *pkattnum, int16 *fkattnum, Oid *pfeqoperators,
-					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok)
+					   Oid *ppeqoperators, Oid *ffeqoperators, bool old_check_ok,
+					   bool is_temporal)
 {
 	ObjectAddress address;
 	Oid			constrOid;
@@ -8865,6 +8742,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  conislocal,	/* islocal */
 									  coninhcount,	/* inhcount */
 									  connoinherit, /* conNoInherit */
+									  is_temporal,
 									  false);	/* is_internal */
 
 	ObjectAddressSet(address, ConstraintRelationId, constrOid);
@@ -8939,7 +8817,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 								   partIndexId, constrOid, numfks,
 								   mapped_pkattnum, fkattnum,
 								   pfeqoperators, ppeqoperators, ffeqoperators,
-								   old_check_ok);
+								   old_check_ok, is_temporal);
 
 			/* Done -- clean up (but keep the lock) */
 			table_close(partRel, NoLock);
@@ -8988,7 +8866,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 						Relation pkrel, Oid indexOid, Oid parentConstr,
 						int numfks, int16 *pkattnum, int16 *fkattnum,
 						Oid *pfeqoperators, Oid *ppeqoperators, Oid *ffeqoperators,
-						bool old_check_ok, LOCKMODE lockmode)
+						bool old_check_ok, bool is_temporal, LOCKMODE lockmode)
 {
 	AssertArg(OidIsValid(parentConstr));
 
@@ -9133,6 +9011,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									  false,
 									  1,
 									  false,
+									  is_temporal,
 									  false);
 
 			/*
@@ -9159,6 +9038,7 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 									ppeqoperators,
 									ffeqoperators,
 									old_check_ok,
+									is_temporal,
 									lockmode);
 
 			table_close(partition, NoLock);
@@ -9342,7 +9222,8 @@ CloneFkReferenced(Relation parentRel, Relation partitionRel)
 							   conpfeqop,
 							   conppeqop,
 							   conffeqop,
-							   true);
+							   true,
+							   constrForm->contemporal);
 
 		table_close(fkRel, NoLock);
 		ReleaseSysCache(tuple);
@@ -9535,6 +9416,7 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								  false,	/* islocal */
 								  1,	/* inhcount */
 								  false,	/* conNoInherit */
+								  constrForm->contemporal,
 								  true);
 
 		/* Set up partition dependencies for the new constraint */
@@ -9564,11 +9446,214 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 								conppeqop,
 								conffeqop,
 								false,	/* no old check exists */
+								constrForm->contemporal,
 								AccessExclusiveLock);
 		table_close(pkrel, NoLock);
 	}
 }
 
+static void
+FindFKComparisonOperators(Constraint *fkconstraint,
+		AlteredTableInfo *tab,
+		int i,
+		int16 *fkattnum,
+		bool *old_check_ok,
+		ListCell **old_pfeqop_item,
+		Oid pktype, Oid fktype, Oid opclass,
+		bool is_temporal, bool for_overlaps,
+		Oid *pfeqopOut, Oid *ppeqopOut, Oid *ffeqopOut)
+{
+	Oid			fktyped;
+	HeapTuple	cla_ht;
+	Form_pg_opclass cla_tup;
+	Oid			amid;
+	Oid			opfamily;
+	Oid			opcintype;
+	Oid			pfeqop;
+	Oid			ppeqop;
+	Oid			ffeqop;
+	int16		eqstrategy;
+	Oid			pfeqop_right;
+
+	/* We need several fields out of the pg_opclass entry */
+	cla_ht = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclass));
+	if (!HeapTupleIsValid(cla_ht))
+		elog(ERROR, "cache lookup failed for opclass %u", opclass);
+	cla_tup = (Form_pg_opclass) GETSTRUCT(cla_ht);
+	amid = cla_tup->opcmethod;
+	opfamily = cla_tup->opcfamily;
+	opcintype = cla_tup->opcintype;
+	ReleaseSysCache(cla_ht);
+
+	if (is_temporal)
+	{
+		if (amid != GIST_AM_OID)
+			elog(ERROR, "only GiST indexes are supported for temporal foreign keys");
+		eqstrategy = for_overlaps ? RTOverlapStrategyNumber : RTEqualStrategyNumber;
+	}
+	else
+	{
+		/*
+		 * Check it's a btree; currently this can never fail since no other
+		 * index AMs support unique indexes.  If we ever did have other types
+		 * of unique indexes, we'd need a way to determine which operator
+		 * strategy number is equality.  (Is it reasonable to insist that
+		 * every such index AM use btree's number for equality?)
+		 */
+		if (amid != BTREE_AM_OID)
+			elog(ERROR, "only b-tree indexes are supported for foreign keys");
+		eqstrategy = BTEqualStrategyNumber;
+	}
+
+	/*
+	 * There had better be a primary equality operator for the index.
+	 * We'll use it for PK = PK comparisons.
+	 */
+	ppeqop = get_opfamily_member(opfamily, opcintype, opcintype,
+								 eqstrategy);
+
+	if (!OidIsValid(ppeqop))
+		elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
+			 eqstrategy, opcintype, opcintype, opfamily);
+
+	/*
+	 * Are there equality operators that take exactly the FK type? Assume
+	 * we should look through any domain here.
+	 */
+	fktyped = getBaseType(fktype);
+
+	pfeqop = get_opfamily_member(opfamily, opcintype, fktyped,
+								 eqstrategy);
+	if (OidIsValid(pfeqop))
+	{
+		pfeqop_right = fktyped;
+		ffeqop = get_opfamily_member(opfamily, fktyped, fktyped,
+									 eqstrategy);
+	}
+	else
+	{
+		/* keep compiler quiet */
+		pfeqop_right = InvalidOid;
+		ffeqop = InvalidOid;
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+	{
+		/*
+		 * Otherwise, look for an implicit cast from the FK type to the
+		 * opcintype, and if found, use the primary equality operator.
+		 * This is a bit tricky because opcintype might be a polymorphic
+		 * type such as ANYARRAY or ANYENUM; so what we have to test is
+		 * whether the two actual column types can be concurrently cast to
+		 * that type.  (Otherwise, we'd fail to reject combinations such
+		 * as int[] and point[].)
+		 */
+		Oid			input_typeids[2];
+		Oid			target_typeids[2];
+
+		input_typeids[0] = pktype;
+		input_typeids[1] = fktype;
+		target_typeids[0] = opcintype;
+		target_typeids[1] = opcintype;
+		if (can_coerce_type(2, input_typeids, target_typeids,
+							COERCION_IMPLICIT))
+		{
+			pfeqop = ffeqop = ppeqop;
+			pfeqop_right = opcintype;
+		}
+	}
+
+	if (!(OidIsValid(pfeqop) && OidIsValid(ffeqop)))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("foreign key constraint \"%s\" cannot be implemented",
+						fkconstraint->conname),
+				 errdetail("Key columns \"%s\" and \"%s\" "
+						   "are of incompatible types: %s and %s.",
+						   strVal(list_nth(fkconstraint->fk_attrs, i)),
+						   strVal(list_nth(fkconstraint->pk_attrs, i)),
+						   format_type_be(fktype),
+						   format_type_be(pktype))));
+
+	if (*old_check_ok)
+	{
+		/*
+		 * When a pfeqop changes, revalidate the constraint.  We could
+		 * permit intra-opfamily changes, but that adds subtle complexity
+		 * without any concrete benefit for core types.  We need not
+		 * assess ppeqop or ffeqop, which RI_Initial_Check() does not use.
+		 */
+		*old_check_ok = (pfeqop == lfirst_oid(*old_pfeqop_item));
+		*old_pfeqop_item = lnext(fkconstraint->old_conpfeqop,
+								*old_pfeqop_item);
+	}
+	if (*old_check_ok)
+	{
+		Oid			old_fktype;
+		Oid			new_fktype;
+		CoercionPathType old_pathtype;
+		CoercionPathType new_pathtype;
+		Oid			old_castfunc;
+		Oid			new_castfunc;
+		Form_pg_attribute attr = TupleDescAttr(tab->oldDesc,
+											   fkattnum[i] - 1);
+
+		/*
+		 * Identify coercion pathways from each of the old and new FK-side
+		 * column types to the right (foreign) operand type of the pfeqop.
+		 * We may assume that pg_constraint.conkey is not changing.
+		 */
+		old_fktype = attr->atttypid;
+		new_fktype = fktype;
+		old_pathtype = findFkeyCast(pfeqop_right, old_fktype,
+									&old_castfunc);
+		new_pathtype = findFkeyCast(pfeqop_right, new_fktype,
+									&new_castfunc);
+
+		/*
+		 * Upon a change to the cast from the FK column to its pfeqop
+		 * operand, revalidate the constraint.  For this evaluation, a
+		 * binary coercion cast is equivalent to no cast at all.  While
+		 * type implementors should design implicit casts with an eye
+		 * toward consistency of operations like equality, we cannot
+		 * assume here that they have done so.
+		 *
+		 * A function with a polymorphic argument could change behavior
+		 * arbitrarily in response to get_fn_expr_argtype().  Therefore,
+		 * when the cast destination is polymorphic, we only avoid
+		 * revalidation if the input type has not changed at all.  Given
+		 * just the core data types and operator classes, this requirement
+		 * prevents no would-be optimizations.
+		 *
+		 * If the cast converts from a base type to a domain thereon, then
+		 * that domain type must be the opcintype of the unique index.
+		 * Necessarily, the primary key column must then be of the domain
+		 * type.  Since the constraint was previously valid, all values on
+		 * the foreign side necessarily exist on the primary side and in
+		 * turn conform to the domain.  Consequently, we need not treat
+		 * domains specially here.
+		 *
+		 * Since we require that all collations share the same notion of
+		 * equality (which they do, because texteq reduces to bitwise
+		 * equality), we don't compare collation here.
+		 *
+		 * We need not directly consider the PK type.  It's necessarily
+		 * binary coercible to the opcintype of the unique index column,
+		 * and ri_triggers.c will only deal with PK datums in terms of
+		 * that opcintype.  Changing the opcintype also changes pfeqop.
+		 */
+		*old_check_ok = (new_pathtype == old_pathtype &&
+						new_castfunc == old_castfunc &&
+						(!IsPolymorphicType(pfeqop_right) ||
+						 new_fktype == old_fktype));
+
+	}
+
+	*pfeqopOut = pfeqop;
+	*ppeqopOut = ppeqop;
+	*ffeqopOut = ffeqop;
+}
+
 /*
  * When the parent of a partition receives [the referencing side of] a foreign
  * key, we must propagate that foreign key to the partition.  However, the
@@ -10108,10 +10193,12 @@ transformColumnNameList(Oid relId, List *colList,
  *
  *	Look up the names, attnums, and types of the primary key attributes
  *	for the pkrel.  Also return the index OID and index opclasses of the
- *	index supporting the primary key.
+ *	index supporting the primary key.  If this is a temporal primary key,
+ *	also set the WITHOUT OVERLAPS attribute name, attnum, and atttypid.
  *
  *	All parameters except pkrel are output parameters.  Also, the function
- *	return value is the number of attributes in the primary key.
+ *	return value is the number of attributes in the primary key,
+ *	not including the WITHOUT OVERLAPS if any.
  *
  *	Used when the column list in the REFERENCES specification is omitted.
  */
@@ -10119,6 +10206,8 @@ static int
 transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 						   List **attnamelist,
 						   int16 *attnums, Oid *atttypids,
+						   Node **periodattname,
+						   int16 *periodattnums, Oid *periodatttypids,
 						   Oid *opclasses)
 {
 	List	   *indexoidlist;
@@ -10186,35 +10275,50 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
 	/*
 	 * Now build the list of PK attributes from the indkey definition (we
 	 * assume a primary key cannot have expressional elements)
+	 * TODO: range expressions will be how we support PERIODs though.
 	 */
 	*attnamelist = NIL;
 	for (i = 0; i < indexStruct->indnkeyatts; i++)
 	{
 		int			pkattno = indexStruct->indkey.values[i];
 
-		attnums[i] = pkattno;
-		atttypids[i] = attnumTypeId(pkrel, pkattno);
-		opclasses[i] = indclass->values[i];
-		*attnamelist = lappend(*attnamelist,
-							   makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		if (i == indexStruct->indnkeyatts - 1 && indexStruct->indisexclusion)
+		{
+			periodattnums[0] = pkattno;
+			periodatttypids[0] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*periodattname = (Node *)makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno))));
+		}
+		else
+		{
+			attnums[i] = pkattno;
+			atttypids[i] = attnumTypeId(pkrel, pkattno);
+			opclasses[i] = indclass->values[i];
+			*attnamelist = lappend(*attnamelist,
+								  makeString(pstrdup(NameStr(*attnumAttName(pkrel, pkattno)))));
+		}
 	}
 
 	ReleaseSysCache(indexTuple);
 
-	return i;
+	if (indexStruct->indisexclusion) return i - 1;
+	else return i;
 }
 
 /*
  * transformFkeyCheckAttrs -
  *
  *	Make sure that the attributes of a referenced table belong to a unique
- *	(or primary key) constraint.  Return the OID of the index supporting
- *	the constraint, as well as the opclasses associated with the index
+ *	(or primary key) constraint.  Or if this is a temporal foreign key
+ *	the primary key should be an exclusion constraint instead.
+ *	Return the OID of the index supporting the constraint,
+ *	as well as the opclasses associated with the index
  *	columns.
  */
 static Oid
 transformFkeyCheckAttrs(Relation pkrel,
 						int numattrs, int16 *attnums,
+						bool is_temporal, int16 *periodattnums,
 						Oid *opclasses) /* output parameter */
 {
 	Oid			indexoid = InvalidOid;
@@ -10241,6 +10345,10 @@ transformFkeyCheckAttrs(Relation pkrel,
 						(errcode(ERRCODE_INVALID_FOREIGN_KEY),
 						 errmsg("foreign key referenced-columns list must not contain duplicates")));
 		}
+		if (is_temporal && attnums[i] == periodattnums[0])
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_FOREIGN_KEY),
+					 errmsg("foreign key referenced-columns list must not contain duplicates")));
 	}
 
 	/*
@@ -10262,12 +10370,16 @@ transformFkeyCheckAttrs(Relation pkrel,
 		indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		/*
-		 * Must have the right number of columns; must be unique and not a
+		 * Must have the right number of columns; must be unique
+		 * (or if temporal then exclusion instead) and not a
 		 * partial index; forget it if there are any expressions, too. Invalid
 		 * indexes are out as well.
 		 */
-		if (indexStruct->indnkeyatts == numattrs &&
-			indexStruct->indisunique &&
+		if ((is_temporal
+			  ? (indexStruct->indnkeyatts == numattrs + 1 &&
+				 indexStruct->indisexclusion)
+			  : (indexStruct->indnkeyatts == numattrs &&
+				 indexStruct->indisunique)) &&
 			indexStruct->indisvalid &&
 			heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
 			heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
@@ -10307,6 +10419,19 @@ transformFkeyCheckAttrs(Relation pkrel,
 				if (!found)
 					break;
 			}
+			if (is_temporal)
+			{
+				found = false;
+				for (j = 0; j < numattrs + 1; j++)
+				{
+					if (periodattnums[0] == indexStruct->indkey.values[j])
+					{
+						opclasses[numattrs] = indclass->values[j];
+						found = true;
+						break;
+					}
+				}
+			}
 
 			/*
 			 * Refuse to use a deferrable unique/primary key.  This is per SQL
@@ -10416,7 +10541,8 @@ validateForeignKeyConstraint(char *conname,
 							 Relation rel,
 							 Relation pkrel,
 							 Oid pkindOid,
-							 Oid constraintOid)
+							 Oid constraintOid,
+							 bool temporal)
 {
 	TupleTableSlot *slot;
 	TableScanDesc scan;
@@ -10446,8 +10572,10 @@ validateForeignKeyConstraint(char *conname,
 	/*
 	 * See if we can do it with a single LEFT JOIN query.  A false result
 	 * indicates we must proceed with the fire-the-trigger method.
+	 * We can't do a LEFT JOIN for temporal FKs yet,
+	 * but we can once we support temporal left joins.
 	 */
-	if (RI_Initial_Check(&trig, rel, pkrel))
+	if (!temporal && RI_Initial_Check(&trig, rel, pkrel))
 		return;
 
 	/*
@@ -10507,6 +10635,7 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 					 Oid constraintOid, Oid indexOid, bool on_insert)
 {
 	CreateTrigStmt *fk_trigger;
+	bool is_temporal = fkconstraint->fk_period;
 
 	/*
 	 * Note: for a self-referential FK (referencing and referenced tables are
@@ -10518,7 +10647,10 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	 * and "RI_ConstraintTrigger_c_NNNN" for the check triggers.
 	 */
 	fk_trigger = makeNode(CreateTrigStmt);
-	fk_trigger->trigname = "RI_ConstraintTrigger_c";
+	if (is_temporal)
+		fk_trigger->trigname = "TRI_ConstraintTrigger_c";
+	else
+		fk_trigger->trigname = "RI_ConstraintTrigger_c";
 	fk_trigger->relation = NULL;
 	fk_trigger->row = true;
 	fk_trigger->timing = TRIGGER_TYPE_AFTER;
@@ -10526,12 +10658,18 @@ CreateFKCheckTrigger(Oid myRelOid, Oid refRelOid, Constraint *fkconstraint,
 	/* Either ON INSERT or ON UPDATE */
 	if (on_insert)
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_ins");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_ins");
 		fk_trigger->events = TRIGGER_TYPE_INSERT;
 	}
 	else
 	{
-		fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
+		if (is_temporal)
+			fk_trigger->funcname = SystemFuncName("TRI_FKey_check_upd");
+		else
+			fk_trigger->funcname = SystemFuncName("RI_FKey_check_upd");
 		fk_trigger->events = TRIGGER_TYPE_UPDATE;
 	}
 
@@ -10577,37 +10715,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_del_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_del_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_del");
+				break;
+				/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_del");
+				break;
+				*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_del_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_del");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_del");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_del");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_del");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_del");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_del_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
@@ -10633,37 +10812,78 @@ createForeignKeyActionTriggers(Relation rel, Oid refRelOid, Constraint *fkconstr
 	fk_trigger->whenClause = NULL;
 	fk_trigger->isconstraint = true;
 	fk_trigger->constrrel = NULL;
-	switch (fkconstraint->fk_upd_action)
+	if (fkconstraint->fk_period != NULL)
 	{
-		case FKCONSTR_ACTION_NOACTION:
-			fk_trigger->deferrable = fkconstraint->deferrable;
-			fk_trigger->initdeferred = fkconstraint->initdeferred;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
-			break;
-		case FKCONSTR_ACTION_RESTRICT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
-			break;
-		case FKCONSTR_ACTION_CASCADE:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
-			break;
-		case FKCONSTR_ACTION_SETNULL:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
-			break;
-		case FKCONSTR_ACTION_SETDEFAULT:
-			fk_trigger->deferrable = false;
-			fk_trigger->initdeferred = false;
-			fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
-			break;
-		default:
-			elog(ERROR, "unrecognized FK action type: %d",
-				 (int) fkconstraint->fk_upd_action);
-			break;
+		/* Temporal foreign keys */
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_restrict_upd");
+				break;
+			/*
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("TRI_FKey_setdefault_upd");
+				break;
+			*/
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
+	}
+	else
+	{
+		switch (fkconstraint->fk_upd_action)
+		{
+			case FKCONSTR_ACTION_NOACTION:
+				fk_trigger->deferrable = fkconstraint->deferrable;
+				fk_trigger->initdeferred = fkconstraint->initdeferred;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_noaction_upd");
+				break;
+			case FKCONSTR_ACTION_RESTRICT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_restrict_upd");
+				break;
+			case FKCONSTR_ACTION_CASCADE:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_cascade_upd");
+				break;
+			case FKCONSTR_ACTION_SETNULL:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setnull_upd");
+				break;
+			case FKCONSTR_ACTION_SETDEFAULT:
+				fk_trigger->deferrable = false;
+				fk_trigger->initdeferred = false;
+				fk_trigger->funcname = SystemFuncName("RI_FKey_setdefault_upd");
+				break;
+			default:
+				elog(ERROR, "unrecognized FK action type: %d",
+					 (int) fkconstraint->fk_upd_action);
+				break;
+		}
 	}
 	fk_trigger->args = NIL;
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 092ac1646d..a02d63146d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -724,6 +724,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 											  true, /* islocal */
 											  0,	/* inhcount */
 											  true, /* noinherit */
+											  false, /* contemporal */
 											  isInternal);	/* is_internal */
 	}
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 483bb65ddc..81f877827b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -3128,6 +3128,7 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 							  true, /* is local */
 							  0,	/* inhcount */
 							  false,	/* connoinherit */
+							  false,	/* contemporal */
 							  false);	/* is_internal */
 	if (constrAddr)
 		ObjectAddressSet(*constrAddr, ConstraintRelationId, ccoid);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f589f9..a2d3603366 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1239,6 +1239,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
 	resultRelInfo->ri_projectReturning = NULL;
 	resultRelInfo->ri_onConflictArbiterIndexes = NIL;
 	resultRelInfo->ri_onConflict = NULL;
+	resultRelInfo->ri_forPortionOf = NULL;
 	resultRelInfo->ri_ReturningSlot = NULL;
 	resultRelInfo->ri_TrigOldSlot = NULL;
 	resultRelInfo->ri_TrigNewSlot = NULL;
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 29e07b7228..55e623638e 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -55,6 +55,7 @@
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/memutils.h"
+#include "utils/rangetypes.h"
 #include "utils/rel.h"
 
 
@@ -701,6 +702,127 @@ ExecInsert(ModifyTableState *mtstate,
 	return result;
 }
 
+/*
+ * Insert tuples for the untouched timespan of a row in a FOR PORTION OF UPDATE/DELETE
+ * TODO: figure out if I need to make a copy of slot somehow in order to insert it
+ */
+static void
+ExecForPortionOfLeftovers(ModifyTableState *mtstate, EState *estate, ResultRelInfo *resultRelInfo, ItemPointer tupleid, TupleTableSlot *slot, TupleTableSlot *planSlot)
+{
+
+	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+	ForPortionOfExpr *forPortionOf = (ForPortionOfExpr *) node->forPortionOf;
+	Datum	oldRange;
+	Datum	targetRange;
+	RangeType *oldRangeType;
+	RangeType *targetRangeType;
+	RangeType *leftoverRangeType1;
+	RangeType *leftoverRangeType2;
+	Oid		rangeTypeOid;
+	bool	isNull;
+	TypeCacheEntry *typcache;
+	TupleTableSlot *oldtupleSlot = resultRelInfo->ri_forPortionOf->fp_Existing;
+	TupleTableSlot *leftoverTuple1 = resultRelInfo->ri_forPortionOf->fp_Leftover1;
+	TupleTableSlot *leftoverTuple2 = resultRelInfo->ri_forPortionOf->fp_Leftover2;
+
+	/* Get the range of the existing pre-UPDATE/DELETE tuple */
+
+	/* TODO: Seems like we shouldn't have to do this, because the old tuple should
+	 * already be available somehow? But this is what triggers do
+	 * (Are you sure this is how they get the OLD tuple?)
+	 * And even if we do have to do this, is SnapshotAny really correct?
+	 * Shouldn't it be the snapshot of the UPDATE?
+	 */
+
+	if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, tupleid, SnapshotAny,
+									   oldtupleSlot))
+		elog(ERROR, "failed to fetch tuple for FOR PORTION OF");
+
+	oldRange = slot_getattr(oldtupleSlot, forPortionOf->range_attno, &isNull);
+	if (isNull)
+		elog(ERROR, "found a NULL range in a temporal table");
+
+	oldRangeType = DatumGetRangeTypeP(oldRange);
+
+	/* Evaluate the target range if we haven't yet */
+	if (resultRelInfo->ri_forPortionOf->fp_targetRange)
+	{
+		targetRangeType = resultRelInfo->ri_forPortionOf->fp_targetRange;
+	}
+	else
+	{
+		ExprState *exprState;
+
+		ExprContext *econtext = GetPerTupleExprContext(estate);
+		econtext->ecxt_scantuple = slot;
+
+		exprState = ExecPrepareExpr((Expr *) forPortionOf->targetRange, estate);
+		targetRange = ExecEvalExpr(exprState, econtext, &isNull);
+
+		if (isNull)
+			elog(ERROR, "Got a NULL FOR PORTION OF target range");
+
+		targetRangeType = DatumGetRangeTypeP(targetRange);
+		resultRelInfo->ri_forPortionOf->fp_targetRange = targetRangeType;
+	}
+
+
+	/*
+	 * Get the range's type cache entry. This is worth caching for the whole UPDATE
+	 * like range functions do.
+	 */
+
+	typcache = resultRelInfo->ri_forPortionOf->fp_rangetypcache;
+	if (typcache == NULL)
+	{
+		rangeTypeOid = RangeTypeGetOid(oldRangeType);
+		typcache = lookup_type_cache(rangeTypeOid, TYPECACHE_RANGE_INFO);
+
+		if (typcache->rngelemtype == NULL)
+			elog(ERROR, "type %u is not a range type", rangeTypeOid);
+
+		resultRelInfo->ri_forPortionOf->fp_rangetypcache = typcache;
+	}
+
+	/* Get the ranges to the left/right of the targeted range. */
+
+	range_leftover_internal(typcache, oldRangeType, targetRangeType, &leftoverRangeType1,
+			&leftoverRangeType2);
+
+	/* Insert a copy of the tuple with the lower leftover range */
+
+	if (!RangeIsEmpty(leftoverRangeType1))
+	{
+		MinimalTuple oldtuple = ExecFetchSlotMinimalTuple(oldtupleSlot, NULL);
+		ExecForceStoreMinimalTuple(oldtuple, leftoverTuple1, false);
+
+		leftoverTuple1->tts_values[forPortionOf->range_attno - 1] = RangeTypePGetDatum(leftoverRangeType1);
+		leftoverTuple1->tts_isnull[forPortionOf->range_attno - 1] = false;
+
+		ExecMaterializeSlot(leftoverTuple1);
+
+		// TODO: tuple routing?
+		ExecInsert(mtstate, mtstate->rootResultRelInfo, leftoverTuple1, planSlot,
+				   estate, node->canSetTag);
+	}
+
+	/* Insert a copy of the tuple with the upper leftover range */
+	if (!RangeIsEmpty(leftoverRangeType2))
+	{
+		MinimalTuple oldtuple = ExecFetchSlotMinimalTuple(oldtupleSlot, NULL);
+		ExecForceStoreMinimalTuple(oldtuple, leftoverTuple2, false);
+
+		leftoverTuple2->tts_values[forPortionOf->range_attno - 1] = RangeTypePGetDatum(leftoverRangeType2);
+		leftoverTuple2->tts_isnull[forPortionOf->range_attno - 1] = false;
+
+		ExecMaterializeSlot(leftoverTuple2);
+
+		// TODO: tuple routing?
+		ExecInsert(mtstate, mtstate->rootResultRelInfo, leftoverTuple2, planSlot,
+				   estate, node->canSetTag);
+	}
+}
+
 /* ----------------------------------------------------------------
  *		ExecDelete
  *
@@ -742,6 +864,8 @@ ExecDelete(ModifyTableState *mtstate,
 	TM_FailureData tmfd;
 	TupleTableSlot *slot = NULL;
 	TransitionCaptureState *ar_delete_trig_tcs;
+	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+	ForPortionOfExpr *forPortionOf = (ForPortionOfExpr *) node->forPortionOf;
 
 	if (tupleDeleted)
 		*tupleDeleted = false;
@@ -1006,6 +1130,13 @@ ldelete:;
 		ar_delete_trig_tcs = NULL;
 	}
 
+	/*
+	 * Compute leftovers in FOR PORTION OF
+	 */
+	// TODO: Skip this for FDW deletes?
+	if (forPortionOf)
+		ExecForPortionOfLeftovers(mtstate, estate, resultRelInfo, tupleid, slot, planSlot);
+
 	/* AFTER ROW DELETE Triggers */
 	ExecARDeleteTriggers(estate, resultRelInfo, tupleid, oldtuple,
 						 ar_delete_trig_tcs);
@@ -1219,6 +1350,8 @@ ExecUpdate(ModifyTableState *mtstate,
 	TM_Result	result;
 	TM_FailureData tmfd;
 	List	   *recheckIndexes = NIL;
+	ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
+	ForPortionOfExpr *forPortionOf = (ForPortionOfExpr *) node->forPortionOf;
 
 	/*
 	 * abort the operation if not running transactions
@@ -1237,7 +1370,11 @@ ExecUpdate(ModifyTableState *mtstate,
 			return NULL;		/* "do nothing" */
 	}
 
-	/* INSTEAD OF ROW UPDATE Triggers */
+	/* TODO: Is there an argument that we should set the temporal bounds
+	 * before calling the INSTEAD OF trigger?? What do other dbs do?
+	 * INSTEAD OF ROW UPDATE Triggers
+	 */
+
 	if (resultRelInfo->ri_TrigDesc &&
 		resultRelInfo->ri_TrigDesc->trig_update_instead_row)
 	{
@@ -1521,6 +1658,14 @@ lreplace:;
 	if (canSetTag)
 		(estate->es_processed)++;
 
+	/*
+	 * Compute leftovers in FOR PORTION OF
+	 * TODO: Skip this for FDW updates?
+	 */
+
+	if (forPortionOf)
+		ExecForPortionOfLeftovers(mtstate, estate, resultRelInfo, tupleid, slot, planSlot);
+
 	/* AFTER ROW UPDATE Triggers */
 	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, slot,
 						 recheckIndexes,
@@ -2284,7 +2429,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		 * query.
 		 */
 		if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
-			operation != CMD_DELETE &&
+			(operation != CMD_DELETE || node->forPortionOf != NULL) &&
 			resultRelInfo->ri_IndexRelationDescs == NULL)
 			ExecOpenIndices(resultRelInfo,
 							node->onConflictAction != ONCONFLICT_NONE);
@@ -2505,6 +2650,31 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		}
 	}
 
+	/*
+	 * If needed, initialize ... TODO ... for FOR PORTION OF.
+	 */
+	if (node->forPortionOf)
+	{
+		// TODO: Is tupDesc the right thing?
+		TupleDesc tupDesc = resultRelInfo->ri_RelationDesc->rd_att;
+
+		/* create state for FOR PORTION OF operation */
+		resultRelInfo->ri_forPortionOf = makeNode(ForPortionOfState);
+
+		/* initialize slot for the existing tuple */
+		resultRelInfo->ri_forPortionOf->fp_Existing =
+			table_slot_create(resultRelInfo->ri_RelationDesc,
+					&mtstate->ps.state->es_tupleTable);
+
+		/* Create the tuple slots for INSERTing the leftovers. */
+		resultRelInfo->ri_forPortionOf->fp_Leftover1 =
+			ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+								   &TTSOpsVirtual);
+		resultRelInfo->ri_forPortionOf->fp_Leftover2 =
+			ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc,
+								   &TTSOpsVirtual);
+	}
+
 	/*
 	 * If we have any secondary relations in an UPDATE or DELETE, they need to
 	 * be treated like non-locked relations in SELECT FOR UPDATE, ie, the
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2b4d7654cc..b6a5383a2d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -214,6 +214,7 @@ _copyModifyTable(const ModifyTable *from)
 	COPY_NODE_FIELD(rowMarks);
 	COPY_SCALAR_FIELD(epqParam);
 	COPY_SCALAR_FIELD(onConflictAction);
+	COPY_NODE_FIELD(forPortionOf);
 	COPY_NODE_FIELD(arbiterIndexes);
 	COPY_NODE_FIELD(onConflictSet);
 	COPY_NODE_FIELD(onConflictWhere);
@@ -2246,6 +2247,28 @@ _copyOnConflictExpr(const OnConflictExpr *from)
 	return newnode;
 }
 
+/*
+ * _copyForPortionOfExpr
+ */
+static ForPortionOfExpr *
+_copyForPortionOfExpr(const ForPortionOfExpr *from)
+{
+	ForPortionOfExpr *newnode = makeNode(ForPortionOfExpr);
+
+	COPY_SCALAR_FIELD(range_attno);
+	COPY_STRING_FIELD(range_name);
+	COPY_NODE_FIELD(range);
+	COPY_NODE_FIELD(startCol);
+	COPY_NODE_FIELD(endCol);
+	COPY_NODE_FIELD(targetStart);
+	COPY_NODE_FIELD(targetEnd);
+	COPY_NODE_FIELD(targetRange);
+	COPY_NODE_FIELD(overlapsExpr);
+	COPY_NODE_FIELD(rangeSet);
+
+	return newnode;
+}
+
 /* ****************************************************************
  *						pathnodes.h copy functions
  *
@@ -2586,6 +2609,19 @@ _copyOnConflictClause(const OnConflictClause *from)
 	return newnode;
 }
 
+static ForPortionOfClause *
+_copyForPortionOfClause(const ForPortionOfClause *from)
+{
+	ForPortionOfClause *newnode = makeNode(ForPortionOfClause);
+
+	COPY_STRING_FIELD(range_name);
+	COPY_SCALAR_FIELD(range_name_location);
+	COPY_NODE_FIELD(target_start);
+	COPY_NODE_FIELD(target_end);
+
+	return newnode;
+}
+
 static CommonTableExpr *
 _copyCommonTableExpr(const CommonTableExpr *from)
 {
@@ -2972,12 +3008,15 @@ _copyConstraint(const Constraint *from)
 	COPY_NODE_FIELD(where_clause);
 	COPY_NODE_FIELD(pktable);
 	COPY_NODE_FIELD(fk_attrs);
+	COPY_NODE_FIELD(fk_period);
 	COPY_NODE_FIELD(pk_attrs);
+	COPY_NODE_FIELD(pk_period);
 	COPY_SCALAR_FIELD(fk_matchtype);
 	COPY_SCALAR_FIELD(fk_upd_action);
 	COPY_SCALAR_FIELD(fk_del_action);
 	COPY_NODE_FIELD(old_conpfeqop);
 	COPY_SCALAR_FIELD(old_pktable_oid);
+	COPY_NODE_FIELD(without_overlaps);
 	COPY_SCALAR_FIELD(skip_validation);
 	COPY_SCALAR_FIELD(initially_valid);
 
@@ -3523,6 +3562,7 @@ _copyIndexStmt(const IndexStmt *from)
 	COPY_SCALAR_FIELD(unique);
 	COPY_SCALAR_FIELD(primary);
 	COPY_SCALAR_FIELD(isconstraint);
+	COPY_SCALAR_FIELD(istemporal);
 	COPY_SCALAR_FIELD(deferrable);
 	COPY_SCALAR_FIELD(initdeferred);
 	COPY_SCALAR_FIELD(transformed);
@@ -5142,6 +5182,9 @@ copyObjectImpl(const void *from)
 		case T_OnConflictExpr:
 			retval = _copyOnConflictExpr(from);
 			break;
+		case T_ForPortionOfExpr:
+			retval = _copyForPortionOfExpr(from);
+			break;
 
 			/*
 			 * RELATION NODES
@@ -5670,6 +5713,9 @@ copyObjectImpl(const void *from)
 		case T_OnConflictClause:
 			retval = _copyOnConflictClause(from);
 			break;
+		case T_ForPortionOfClause:
+			retval = _copyForPortionOfClause(from);
+			break;
 		case T_CommonTableExpr:
 			retval = _copyCommonTableExpr(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index e2d1b987bf..e4ea88e52b 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -818,6 +818,23 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b)
 	return true;
 }
 
+static bool
+_equalForPortionOfExpr(const ForPortionOfExpr *a, const ForPortionOfExpr *b)
+{
+	COMPARE_SCALAR_FIELD(range_attno);
+	COMPARE_STRING_FIELD(range_name);
+	COMPARE_NODE_FIELD(range);
+	COMPARE_NODE_FIELD(startCol);
+	COMPARE_NODE_FIELD(endCol);
+	COMPARE_NODE_FIELD(targetStart);
+	COMPARE_NODE_FIELD(targetEnd);
+	COMPARE_NODE_FIELD(targetRange);
+	COMPARE_NODE_FIELD(overlapsExpr);
+	COMPARE_NODE_FIELD(rangeSet);
+
+	return true;
+}
+
 /*
  * Stuff from pathnodes.h
  */
@@ -2830,6 +2847,17 @@ _equalOnConflictClause(const OnConflictClause *a, const OnConflictClause *b)
 	return true;
 }
 
+static bool
+_equalForPortionOfClause(const ForPortionOfClause *a, const ForPortionOfClause *b)
+{
+	COMPARE_STRING_FIELD(range_name);
+	COMPARE_SCALAR_FIELD(range_name_location);
+	COMPARE_NODE_FIELD(target_start);
+	COMPARE_NODE_FIELD(target_end);
+
+	return true;
+}
+
 static bool
 _equalCommonTableExpr(const CommonTableExpr *a, const CommonTableExpr *b)
 {
@@ -3206,6 +3234,9 @@ equal(const void *a, const void *b)
 		case T_OnConflictExpr:
 			retval = _equalOnConflictExpr(a, b);
 			break;
+		case T_ForPortionOfExpr:
+			retval = _equalForPortionOfExpr(a, b);
+			break;
 		case T_JoinExpr:
 			retval = _equalJoinExpr(a, b);
 			break;
@@ -3724,6 +3755,9 @@ equal(const void *a, const void *b)
 		case T_OnConflictClause:
 			retval = _equalOnConflictClause(a, b);
 			break;
+		case T_ForPortionOfClause:
+			retval = _equalForPortionOfClause(a, b);
+			break;
 		case T_CommonTableExpr:
 			retval = _equalCommonTableExpr(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 08a049232e..2f646691f5 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -415,9 +415,11 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
 	WRITE_NODE_FIELD(rowMarks);
 	WRITE_INT_FIELD(epqParam);
 	WRITE_ENUM_FIELD(onConflictAction, OnConflictAction);
+	WRITE_NODE_FIELD(forPortionOf);
 	WRITE_NODE_FIELD(arbiterIndexes);
 	WRITE_NODE_FIELD(onConflictSet);
 	WRITE_NODE_FIELD(onConflictWhere);
+	// TODO: add things for ForPortionOf
 	WRITE_UINT_FIELD(exclRelRTI);
 	WRITE_NODE_FIELD(exclRelTlist);
 }
@@ -1707,6 +1709,23 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node)
 	WRITE_NODE_FIELD(exclRelTlist);
 }
 
+static void
+_outForPortionOfExpr(StringInfo str, const ForPortionOfExpr *node)
+{
+	WRITE_NODE_TYPE("FORPORTIONOFEXPR");
+
+	WRITE_INT_FIELD(range_attno);
+	WRITE_STRING_FIELD(range_name);
+	WRITE_NODE_FIELD(range);
+	WRITE_NODE_FIELD(startCol);
+	WRITE_NODE_FIELD(endCol);
+	WRITE_NODE_FIELD(targetStart);
+	WRITE_NODE_FIELD(targetEnd);
+	WRITE_NODE_FIELD(targetRange);
+	WRITE_NODE_FIELD(overlapsExpr);
+	WRITE_NODE_FIELD(rangeSet);
+}
+
 /*****************************************************************************
  *
  *	Stuff from pathnodes.h.
@@ -4002,6 +4021,9 @@ outNode(StringInfo str, const void *obj)
 			case T_OnConflictExpr:
 				_outOnConflictExpr(str, obj);
 				break;
+			case T_ForPortionOfExpr:
+				_outForPortionOfExpr(str, obj);
+				break;
 			case T_Path:
 				_outPath(str, obj);
 				break;
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ab7b535caa..89309f00fd 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1374,6 +1374,27 @@ _readAppendRelInfo(void)
  *	Stuff from parsenodes.h.
  */
 
+/*
+ * _readForPortionOfExpr
+ */
+static ForPortionOfExpr *
+_readForPortionOfExpr(void)
+{
+	READ_LOCALS(ForPortionOfExpr);
+
+	READ_INT_FIELD(range_attno);
+	READ_STRING_FIELD(range_name);
+	READ_NODE_FIELD(range);
+	READ_NODE_FIELD(startCol);
+	READ_NODE_FIELD(endCol);
+	READ_NODE_FIELD(targetStart);
+	READ_NODE_FIELD(targetEnd);
+	READ_NODE_FIELD(targetRange);
+	READ_NODE_FIELD(overlapsExpr);
+	READ_NODE_FIELD(rangeSet);
+	READ_DONE();
+}
+
 /*
  * _readRangeTblEntry
  */
@@ -1646,6 +1667,7 @@ _readModifyTable(void)
 	READ_NODE_FIELD(rowMarks);
 	READ_INT_FIELD(epqParam);
 	READ_ENUM_FIELD(onConflictAction, OnConflictAction);
+	READ_NODE_FIELD(forPortionOf);
 	READ_NODE_FIELD(arbiterIndexes);
 	READ_NODE_FIELD(onConflictSet);
 	READ_NODE_FIELD(onConflictWhere);
@@ -2754,6 +2776,8 @@ parseNodeString(void)
 		return_value = _readOnConflictExpr();
 	else if (MATCH("APPENDRELINFO", 13))
 		return_value = _readAppendRelInfo();
+	else if (MATCH("FORPORTIONOFEXPR", 16))
+		return_value = _readForPortionOfExpr();
 	else if (MATCH("RTE", 3))
 		return_value = _readRangeTblEntry();
 	else if (MATCH("RANGETBLFUNCTION", 16))
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 94280a730c..96c327943d 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -297,7 +297,8 @@ static ModifyTable *make_modifytable(PlannerInfo *root,
 									 bool partColsUpdated,
 									 List *resultRelations, List *subplans, List *subroots,
 									 List *withCheckOptionLists, List *returningLists,
-									 List *rowMarks, OnConflictExpr *onconflict, int epqParam);
+									 List *rowMarks, OnConflictExpr *onconflict,
+									 ForPortionOfExpr *forPortionOf, int epqParam);
 static GatherMerge *create_gather_merge_plan(PlannerInfo *root,
 											 GatherMergePath *best_path);
 
@@ -2674,6 +2675,7 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path)
 							best_path->returningLists,
 							best_path->rowMarks,
 							best_path->onconflict,
+							best_path->forPortionOf,
 							best_path->epqParam);
 
 	copy_generic_path_info(&plan->plan, &best_path->path);
@@ -6784,7 +6786,8 @@ make_modifytable(PlannerInfo *root,
 				 bool partColsUpdated,
 				 List *resultRelations, List *subplans, List *subroots,
 				 List *withCheckOptionLists, List *returningLists,
-				 List *rowMarks, OnConflictExpr *onconflict, int epqParam)
+				 List *rowMarks, OnConflictExpr *onconflict,
+				 ForPortionOfExpr *forPortionOf, int epqParam)
 {
 	ModifyTable *node = makeNode(ModifyTable);
 	List	   *fdw_private_list;
@@ -6839,6 +6842,7 @@ make_modifytable(PlannerInfo *root,
 		node->exclRelRTI = onconflict->exclRelIndex;
 		node->exclRelTlist = onconflict->exclRelTlist;
 	}
+	node->forPortionOf = (Node *) forPortionOf;
 	node->withCheckOptionLists = withCheckOptionLists;
 	node->returningLists = returningLists;
 	node->rowMarks = rowMarks;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 986d7a52e3..4a2888134d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1794,6 +1794,7 @@ inheritance_planner(PlannerInfo *root)
 									 returningLists,
 									 rowMarks,
 									 NULL,
+									 parse->forPortionOf,
 									 assign_special_exec_param(root)));
 }
 
@@ -2378,6 +2379,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update,
 										returningLists,
 										rowMarks,
 										parse->onConflict,
+										parse->forPortionOf,
 										assign_special_exec_param(root));
 		}
 
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 5281a2f998..6aeeba01a8 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3538,7 +3538,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 						List *subroots,
 						List *withCheckOptionLists, List *returningLists,
 						List *rowMarks, OnConflictExpr *onconflict,
-						int epqParam)
+						ForPortionOfExpr *forPortionOf, int epqParam)
 {
 	ModifyTablePath *pathnode = makeNode(ModifyTablePath);
 	double		total_size;
@@ -3612,6 +3612,7 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel,
 	pathnode->returningLists = returningLists;
 	pathnode->rowMarks = rowMarks;
 	pathnode->onconflict = onconflict;
+	pathnode->forPortionOf = forPortionOf;
 	pathnode->epqParam = epqParam;
 
 	return pathnode;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index c159fb2957..9f3dc64683 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -42,9 +42,12 @@
 #include "parser/parse_param.h"
 #include "parser/parse_relation.h"
 #include "parser/parse_target.h"
+#include "parser/parser.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/lsyscache.h"
 #include "utils/rel.h"
+#include "utils/syscache.h"
 
 
 /* Hook for plugins to get control at end of parse analysis */
@@ -58,6 +61,9 @@ static List *transformInsertRow(ParseState *pstate, List *exprlist,
 								bool strip_indirection);
 static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
 												 OnConflictClause *onConflictClause);
+static ForPortionOfExpr *transformForPortionOfClause(ParseState *pstate,
+													 int rtindex,
+													 ForPortionOfClause *forPortionOfClause);
 static int	count_rowexpr_columns(ParseState *pstate, Node *expr);
 static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
 static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
@@ -69,7 +75,8 @@ static void determineRecursiveColTypes(ParseState *pstate,
 static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
 static List *transformReturningList(ParseState *pstate, List *returningList);
 static List *transformUpdateTargetList(ParseState *pstate,
-									   List *targetList);
+									   List *targetList,
+									   ForPortionOfExpr *forPortionOf);
 static Query *transformDeclareCursorStmt(ParseState *pstate,
 										 DeclareCursorStmt *stmt);
 static Query *transformExplainStmt(ParseState *pstate,
@@ -398,6 +405,7 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
 	ParseNamespaceItem *nsitem;
+	Node	   *whereClause;
 	Node	   *qual;
 
 	qry->commandType = CMD_DELETE;
@@ -436,7 +444,20 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	nsitem->p_lateral_only = false;
 	nsitem->p_lateral_ok = true;
 
-	qual = transformWhereClause(pstate, stmt->whereClause,
+	if (stmt->forPortionOf)
+		qry->forPortionOf = transformForPortionOfClause(pstate, qry->resultRelation, stmt->forPortionOf);
+
+	// TODO: DRY with UPDATE
+	if (stmt->forPortionOf)
+	{
+		if (stmt->whereClause)
+			whereClause = (Node *) makeBoolExpr(AND_EXPR, list_make2(qry->forPortionOf->overlapsExpr, stmt->whereClause), -1);
+		else
+			whereClause = qry->forPortionOf->overlapsExpr;
+	}
+	else
+		whereClause = stmt->whereClause;
+	qual = transformWhereClause(pstate, whereClause,
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -1044,7 +1065,7 @@ transformOnConflictClause(ParseState *pstate,
 		 * Now transform the UPDATE subexpressions.
 		 */
 		onConflictSet =
-			transformUpdateTargetList(pstate, onConflictClause->targetList);
+			transformUpdateTargetList(pstate, onConflictClause->targetList, NULL);
 
 		onConflictWhere = transformWhereClause(pstate,
 											   onConflictClause->whereClause,
@@ -1066,6 +1087,215 @@ transformOnConflictClause(ParseState *pstate,
 	return result;
 }
 
+/*
+ * transformForPortionOfClause
+ *	  transforms a ForPortionOfClause in an UPDATE/DELETE statement
+ */
+static ForPortionOfExpr *
+transformForPortionOfClause(ParseState *pstate,
+							int rtindex,
+							ForPortionOfClause *forPortionOf)
+{
+	Relation targetrel = pstate->p_target_relation;
+	RangeTblEntry *target_rte = pstate->p_target_nsitem->p_rte;
+	char *range_name = forPortionOf->range_name;
+	char *range_type_name;
+	int	range_attno;
+	ForPortionOfExpr *result;
+	List *targetList;
+	FuncCall *fc;
+
+	result = makeNode(ForPortionOfExpr);
+
+	/*
+	 * First look for a range column, then look for a period.
+	 */
+	range_attno = attnameAttNum(targetrel, range_name, true);
+	if (range_attno != InvalidAttrNumber)
+	{
+		Oid pkoid;
+		HeapTuple indexTuple;
+		Var *v;
+		Form_pg_index pk;
+		Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, range_attno - 1);
+
+		/* TODO: check attr->attisdropped */
+
+		/* Make sure it's a range column */
+
+		if (!type_is_range(attr->atttypid))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("column \"%s\" of relation \"%s\" is not a range type",
+							range_name,
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+		/* Make sure the table has a primary key */
+		pkoid = RelationGetPrimaryKeyIndex(targetrel);
+		if (pkoid == InvalidOid)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("relation \"%s\" does not have a temporal primary key",
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+		/* Make sure the primary key is a temporal key */
+		// TODO: need a lock here?
+		indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(pkoid));
+		if (!HeapTupleIsValid(indexTuple))	/* should not happen */
+			elog(ERROR, "cache lookup failed for index %u", pkoid);
+
+		pk = (Form_pg_index) GETSTRUCT(indexTuple);
+		ReleaseSysCache(indexTuple);
+
+		/*
+		 * Only temporal pkey indexes have both isprimary and isexclusion.
+		 * Checking those saves us from scanning pg_constraint
+		 * like in RelationGetExclusionInfo.
+		 */
+		if (!(pk->indisprimary && pk->indisexclusion))
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("relation \"%s\" does not have a temporal primary key",
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+		}
+
+		/* Make sure the range attribute is the last part of the pkey. */
+		if (range_attno != pk->indkey.values[pk->indnkeyatts - 1])
+		{
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("column \"%s\" is not the temporal part of the primary key for relation \"%s\"",
+							range_name,
+							RelationGetRelationName(pstate->p_target_relation)),
+					 parser_errposition(pstate, forPortionOf->range_name_location)));
+		}
+
+		v = makeVar(
+				rtindex,
+				range_attno,
+				attr->atttypid,
+				attr->atttypmod,
+				attr->attcollation,
+				0);
+		v->location = forPortionOf->range_name_location;
+		result->range = (Expr *) v;
+		range_type_name = get_typname(attr->atttypid);
+
+	} else {
+		// TODO: Try to find a period,
+		// and set result->range to an Expr like tsrange(period->start_col, period->end_col)
+		// Probably we can make an A_Expr and call transformExpr on it, right?
+
+		/*
+		 * We need to choose a range type based on the period's columns' type.
+		 * Normally inferring a range type from an element type is not allowed,
+		 * because there might be more than one.
+		 * In this case SQL:2011 only has periods for timestamp, timestamptz, and date,
+		 * which all have built-in range types.
+		 * Let's just take the first range we have for that type,
+		 * ordering by oid, so that we get built-in range types first.
+		 */
+
+		// TODO: set result->range
+		// TODO: set range_type_name
+	}
+
+	if (range_attno == InvalidAttrNumber)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("column or period \"%s\" of relation \"%s\" does not exist",
+						range_name,
+						RelationGetRelationName(pstate->p_target_relation)),
+				 parser_errposition(pstate, forPortionOf->range_name_location)));
+
+	/*
+	 * targetStart and End are literal strings
+	 * that we'll coerce to the range's element type later.
+	 * But if they are "Infinity" or "-Infinity" we should set them to NULL,
+	 * because ranges treat NULL as "further" than +/-Infinity.
+	 */
+	if (pg_strcasecmp(((A_Const *) forPortionOf->target_start)->val.val.str,
+					  "-Infinity") == 0)
+	{
+		A_Const *n = makeNode(A_Const);
+		n->val.type = T_Null;
+		n->location = ((A_Const*)forPortionOf->target_start)->location;
+		result->targetStart = (Node *) n;
+	}
+	else
+		result->targetStart = forPortionOf->target_start;
+
+	if (pg_strcasecmp(((A_Const *) forPortionOf->target_end)->val.val.str,
+					  "Infinity") == 0)
+	{
+		A_Const *n = makeNode(A_Const);
+		n->val.type = T_Null;
+		n->location = ((A_Const*)forPortionOf->target_end)->location;
+		result->targetEnd = (Node *) n;
+	}
+	else
+		result->targetEnd = forPortionOf->target_end;
+
+	fc = makeFuncCall(SystemFuncName(range_type_name),
+								list_make2(result->targetStart,
+										   result->targetEnd),
+								// TODO: FROM...TO... location instead?:
+								forPortionOf->range_name_location);
+	result->targetRange = transformExpr(pstate, (Node *) fc, EXPR_KIND_UPDATE_PORTION);
+
+	/* overlapsExpr is something we can add to the whereClause */
+	result->overlapsExpr = (Node *) makeSimpleA_Expr(AEXPR_OP, "&&",
+			// TODO: Maybe need a copy here?:
+			(Node *) result->range, (Node *) fc,
+			forPortionOf->range_name_location);
+
+	/*
+	 * Now make sure we update the start/end time of the record.
+	 * For a range col (r) this is `r = r * targetRange`.
+	 * For a PERIOD with cols (s, e) this is `s = lower(tsrange(s, r) * targetRange)`
+	 * and `e = upper(tsrange(e, r) * targetRange` (of course not necessarily with
+	 * tsrange, but with whatever range type is used there)).
+	 *
+	 * We also compute the possible left-behind bits at the start and end of the tuple,
+	 * so that we can INSERT them if necessary.
+	 */
+	targetList = NIL;
+	if (range_attno != InvalidAttrNumber)
+	{
+		TargetEntry *tle;
+
+		/* TODO: Maybe need a copy here?*/
+		Expr *rangeSetExpr = (Expr *) makeSimpleA_Expr(AEXPR_OP, "*",
+				(Node *) result->range, (Node *) fc,
+				forPortionOf->range_name_location);
+
+		rangeSetExpr = (Expr *) transformExpr(pstate, (Node *) rangeSetExpr, EXPR_KIND_UPDATE_PORTION);
+		tle = makeTargetEntry(rangeSetExpr,
+							  range_attno,
+							  range_name,
+							  false);
+
+		targetList = lappend(targetList, tle);
+	}
+	else
+	{
+		/* TODO: Set up targetList for PERIODs */
+	}
+	result->rangeSet = targetList;
+
+	/* Mark the range column as requiring update permissions */
+	target_rte->updatedCols = bms_add_member(target_rte->updatedCols,
+											 range_attno - FirstLowInvalidHeapAttributeNumber);
+
+	result->range_attno = range_attno;
+	result->range_name = range_name;
+
+	return result;
+}
 
 /*
  * BuildOnConflictExcludedTargetlist
@@ -2230,6 +2460,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
 	ParseNamespaceItem *nsitem;
+	Node	   *whereClause;
 	Node	   *qual;
 
 	qry->commandType = CMD_UPDATE;
@@ -2247,6 +2478,10 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 										 stmt->relation->inh,
 										 true,
 										 ACL_UPDATE);
+
+	if (stmt->forPortionOf)
+		qry->forPortionOf = transformForPortionOfClause(pstate, qry->resultRelation, stmt->forPortionOf);
+
 	nsitem = pstate->p_target_nsitem;
 
 	/* subqueries in FROM cannot access the result relation */
@@ -2263,7 +2498,16 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	nsitem->p_lateral_only = false;
 	nsitem->p_lateral_ok = true;
 
-	qual = transformWhereClause(pstate, stmt->whereClause,
+	if (stmt->forPortionOf)
+	{
+		if (stmt->whereClause)
+			whereClause = (Node *) makeBoolExpr(AND_EXPR, list_make2(qry->forPortionOf->overlapsExpr, stmt->whereClause), -1);
+		else
+			whereClause = qry->forPortionOf->overlapsExpr;
+	}
+	else
+		whereClause = stmt->whereClause;
+	qual = transformWhereClause(pstate, whereClause,
 								EXPR_KIND_WHERE, "WHERE");
 
 	qry->returningList = transformReturningList(pstate, stmt->returningList);
@@ -2272,7 +2516,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	 * Now we are done with SELECT-like processing, and can get on with
 	 * transforming the target list to match the UPDATE target columns.
 	 */
-	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList);
+	qry->targetList = transformUpdateTargetList(pstate, stmt->targetList, qry->forPortionOf);
 
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
@@ -2290,7 +2534,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
  *	handle SET clause in UPDATE/INSERT ... ON CONFLICT UPDATE
  */
 static List *
-transformUpdateTargetList(ParseState *pstate, List *origTlist)
+transformUpdateTargetList(ParseState *pstate, List *origTlist, ForPortionOfExpr *forPortionOf)
 {
 	List	   *tlist = NIL;
 	RangeTblEntry *target_rte;
@@ -2341,6 +2585,9 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 							RelationGetRelationName(pstate->p_target_relation)),
 					 parser_errposition(pstate, origTarget->location)));
 
+		/* TODO: Make sure user isn't trying to SET the range attribute directly --- TODO or permit it?? */
+
+
 		updateTargetListEntry(pstate, tle, origTarget->name,
 							  attrno,
 							  origTarget->indirection,
@@ -2357,6 +2604,23 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 
 	fill_extraUpdatedCols(target_rte, tupdesc);
 
+	/*
+	 * Record in extraUpdatedCols the temporal bounds if using FOR PORTION OF.
+	 * Since these are part of the primary key this ensures we get the right lock type,
+	 * and it also tells column-specific triggers on those columns to fire.
+	 */
+	if (forPortionOf)
+	{
+		foreach(tl, forPortionOf->rangeSet)
+		{
+			TargetEntry *tle = (TargetEntry *) lfirst(tl);
+			// TODO: I probably don't want to do this until after rewriting, or maybe not even until the ModifyTable node (which seems to be how generated columns work).
+			tlist = lappend(tlist, tle);
+			target_rte->extraUpdatedCols = bms_add_member(target_rte->extraUpdatedCols,
+														  tle->resno - FirstLowInvalidHeapAttributeNumber);
+		}
+	}
+
 	return tlist;
 }
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 480d168346..2418662f91 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -237,6 +237,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	RangeVar			*range;
 	IntoClause			*into;
 	WithClause			*with;
+	ForPortionOfClause	*forportionof;
 	InferClause			*infer;
 	OnConflictClause	*onconflict;
 	A_Indices			*aind;
@@ -483,10 +484,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <node>	TableElement TypedTableElement ConstraintElem TableFuncElement
 %type <node>	columnDef columnOptions
 %type <defelt>	def_elem reloption_elem old_aggr_elem operator_def_elem
-%type <node>	def_arg columnElem where_clause where_or_current_clause
+%type <node>	def_arg columnElem withoutOverlapsClause optionalPeriodName
+				where_clause where_or_current_clause
 				a_expr b_expr c_expr AexprConst indirection_el opt_slice_bound
 				columnref in_expr having_clause func_table xmltable array_expr
 				ExclusionWhereClause operator_def_arg
+%type <list>	opt_column_and_period_list
 %type <list>	rowsfrom_item rowsfrom_list opt_col_def_list
 %type <boolean> opt_ordinality
 %type <list>	ExclusionConstraintList ExclusionConstraintElem
@@ -506,6 +509,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <jexpr>	joined_table
 %type <range>	relation_expr
 %type <range>	relation_expr_opt_alias
+%type <forportionof> for_portion_of_clause
 %type <node>	tablesample_clause opt_repeatable_clause
 %type <target>	target_el set_target insert_column_item
 
@@ -682,8 +686,8 @@ 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
-	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PERIOD PLACING PLANS POLICY
+	PORTION POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
 	QUOTE
@@ -3507,6 +3511,7 @@ ColConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NULL;
+					n->without_overlaps = NULL;
 					n->options = $3;
 					n->indexname = NULL;
 					n->indexspace = $4;
@@ -3723,18 +3728,19 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| PRIMARY KEY '(' columnList ')' opt_c_include opt_definition OptConsTableSpace
+			| PRIMARY KEY '(' columnList withoutOverlapsClause ')' opt_c_include opt_definition OptConsTableSpace
 				ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = $4;
-					n->including = $6;
-					n->options = $7;
+					n->without_overlaps = $5;
+					n->including = $7;
+					n->options = $8;
 					n->indexname = NULL;
-					n->indexspace = $8;
-					processCASbits($9, @9, "PRIMARY KEY",
+					n->indexspace = $9;
+					processCASbits($10, @10, "PRIMARY KEY",
 								   &n->deferrable, &n->initdeferred, NULL,
 								   NULL, yyscanner);
 					$$ = (Node *)n;
@@ -3745,6 +3751,7 @@ ConstraintElem:
 					n->contype = CONSTR_PRIMARY;
 					n->location = @1;
 					n->keys = NIL;
+					n->without_overlaps = NULL;
 					n->including = NIL;
 					n->options = NIL;
 					n->indexname = $3;
@@ -3773,19 +3780,21 @@ ConstraintElem:
 								   NULL, yyscanner);
 					$$ = (Node *)n;
 				}
-			| FOREIGN KEY '(' columnList ')' REFERENCES qualified_name
-				opt_column_list key_match key_actions ConstraintAttributeSpec
+			| FOREIGN KEY '(' columnList optionalPeriodName ')' REFERENCES qualified_name
+				opt_column_and_period_list key_match key_actions ConstraintAttributeSpec
 				{
 					Constraint *n = makeNode(Constraint);
 					n->contype = CONSTR_FOREIGN;
 					n->location = @1;
-					n->pktable			= $7;
+					n->pktable			= $8;
 					n->fk_attrs			= $4;
-					n->pk_attrs			= $8;
-					n->fk_matchtype		= $9;
-					n->fk_upd_action	= (char) ($10 >> 8);
-					n->fk_del_action	= (char) ($10 & 0xFF);
-					processCASbits($11, @11, "FOREIGN KEY",
+					n->fk_period		= $5;
+					n->pk_attrs			= linitial($9);
+					n->pk_period		= lsecond($9);
+					n->fk_matchtype		= $10;
+					n->fk_upd_action	= (char) ($11 >> 8);
+					n->fk_del_action	= (char) ($11 & 0xFF);
+					processCASbits($12, @12, "FOREIGN KEY",
 								   &n->deferrable, &n->initdeferred,
 								   &n->skip_validation, NULL,
 								   yyscanner);
@@ -3808,6 +3817,21 @@ columnList:
 			| columnList ',' columnElem				{ $$ = lappend($1, $3); }
 		;
 
+withoutOverlapsClause:
+			',' columnElem WITHOUT OVERLAPS { $$ = $2; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+optionalPeriodName:
+			',' PERIOD columnElem { $$ = $3; }
+			| /*EMPTY*/               { $$ = NULL; }
+	;
+
+opt_column_and_period_list:
+			'(' columnList optionalPeriodName ')'			{ $$ = list_make2($2, $3); }
+			| /*EMPTY*/								{ $$ = list_make2(NIL, NULL); }
+		;
+
 columnElem: ColId
 				{
 					$$ = (Node *) makeString($1);
@@ -10950,13 +10974,15 @@ returning_clause:
  *****************************************************************************/
 
 DeleteStmt: opt_with_clause DELETE_P FROM relation_expr_opt_alias
+			for_portion_of_clause
 			using_clause where_or_current_clause returning_clause
 				{
 					DeleteStmt *n = makeNode(DeleteStmt);
 					n->relation = $4;
-					n->usingClause = $5;
-					n->whereClause = $6;
-					n->returningList = $7;
+					n->forPortionOf = $5;
+					n->usingClause = $6;
+					n->whereClause = $7;
+					n->returningList = $8;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
@@ -11019,6 +11045,7 @@ opt_nowait_or_skip:
  *****************************************************************************/
 
 UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
+			for_portion_of_clause
 			SET set_clause_list
 			from_clause
 			where_or_current_clause
@@ -11026,10 +11053,11 @@ UpdateStmt: opt_with_clause UPDATE relation_expr_opt_alias
 				{
 					UpdateStmt *n = makeNode(UpdateStmt);
 					n->relation = $3;
-					n->targetList = $5;
-					n->fromClause = $6;
-					n->whereClause = $7;
-					n->returningList = $8;
+					n->forPortionOf = $4;
+					n->targetList = $6;
+					n->fromClause = $7;
+					n->whereClause = $8;
+					n->returningList = $9;
 					n->withClause = $1;
 					$$ = (Node *)n;
 				}
@@ -12186,6 +12214,20 @@ relation_expr_opt_alias: relation_expr					%prec UMINUS
 				}
 		;
 
+for_portion_of_clause:
+			FOR PORTION OF ColId FROM Sconst TO Sconst
+				{
+					ForPortionOfClause *n = makeNode(ForPortionOfClause);
+					n->range_name = $4;
+					n->range_name_location = @4;
+					n->target_start = makeStringConst($6, @6);
+					n->target_end = makeStringConst($8, @8);
+					$$ = n;
+				}
+			| /*EMPTY*/					{ $$ = NULL; }
+		;
+
+
 /*
  * TABLESAMPLE decoration in a FROM item
  */
@@ -15242,6 +15284,7 @@ unreserved_keyword:
 			| PASSWORD
 			| PLANS
 			| POLICY
+			| PORTION
 			| PRECEDING
 			| PREPARE
 			| PREPARED
@@ -15523,6 +15566,7 @@ reserved_keyword:
 			| ONLY
 			| OR
 			| ORDER
+			| PERIOD
 			| PLACING
 			| PRIMARY
 			| REFERENCES
@@ -15811,9 +15855,11 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERIOD
 			| PLACING
 			| PLANS
 			| POLICY
+			| PORTION
 			| POSITION
 			| PRECEDING
 			| PREPARE
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f5165863d7..11d075759c 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -3501,6 +3501,8 @@ ParseExprKindName(ParseExprKind exprKind)
 		case EXPR_KIND_UPDATE_SOURCE:
 		case EXPR_KIND_UPDATE_TARGET:
 			return "UPDATE";
+		case EXPR_KIND_UPDATE_PORTION:
+			return "FOR PORTION OF";
 		case EXPR_KIND_GROUP_BY:
 			return "GROUP BY";
 		case EXPR_KIND_ORDER_BY:
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 015b0538e3..3332b52cca 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1546,6 +1546,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
 	index->oldFirstRelfilenodeSubid = InvalidSubTransactionId;
 	index->unique = idxrec->indisunique;
 	index->primary = idxrec->indisprimary;
+	index->istemporal = idxrec->indisprimary && idxrec->indisexclusion;
 	index->transformed = true;	/* don't need transformIndexStmt */
 	index->concurrent = false;
 	index->if_not_exists = false;
@@ -2088,7 +2089,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
 	index = makeNode(IndexStmt);
 
-	index->unique = (constraint->contype != CONSTR_EXCLUSION);
+	index->unique = (constraint->contype != CONSTR_EXCLUSION && constraint->without_overlaps == NULL);
 	index->primary = (constraint->contype == CONSTR_PRIMARY);
 	if (index->primary)
 	{
@@ -2106,6 +2107,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 		 */
 	}
 	index->isconstraint = true;
+	index->istemporal = constraint->without_overlaps != NULL;
 	index->deferrable = constraint->deferrable;
 	index->initdeferred = constraint->initdeferred;
 
@@ -2486,6 +2488,153 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 				notnullcmd->name = pstrdup(key);
 				notnullcmds = lappend(notnullcmds, notnullcmd);
 			}
+
+			if (constraint->without_overlaps != NULL)
+			{
+				/*
+				 * We are building the index like for an EXCLUSION constraint,
+				 * so use the equality operator for these elements.
+				 */
+				List *opname = list_make1(makeString("="));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+			}
+		}
+
+		/*
+		 * Anything in without_overlaps should be included,
+		 * but with the overlaps operator (&&) instead of equality.
+		 */
+		if (constraint->without_overlaps != NULL) {
+			// char *without_overlaps_str = nodeToString(constraint->without_overlaps);
+			char *without_overlaps_str = strVal(constraint->without_overlaps);
+			IndexElem *iparam = makeNode(IndexElem);
+
+			/*
+			 * Iterate through the table's columns
+			 * (like just a little bit above).
+			 * If we find one whose name is the same as without_overlaps,
+			 * validate that it's a range type.
+			 *
+			 * Otherwise iterate through the table's non-system PERIODs,
+			 * and if we find one then use its start/end columns
+			 * to construct a range expression.
+			 *
+			 * Otherwise report an error.
+			 */
+			bool		found = false;
+			ColumnDef  *column = NULL;
+			ListCell   *columns;
+			if (cxt->isalter)
+			{
+				// TODO: DRY this up with the non-ALTER case:
+				Relation rel = cxt->rel;
+				/*
+				 * Look up columns on existing table.
+				 */
+				for (int i = 0; i < rel->rd_att->natts; i++)
+				{
+					Form_pg_attribute attr = TupleDescAttr(rel->rd_att, i);
+					const char *attname = NameStr(attr->attname);
+					if (strcmp(attname, without_overlaps_str) == 0)
+					{
+						if (type_is_range(attr->atttypid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Look up columns on the being-created table.
+				 */
+				foreach(columns, cxt->columns)
+				{
+					column = castNode(ColumnDef, lfirst(columns));
+					if (strcmp(column->colname, without_overlaps_str) == 0)
+					{
+						Oid colTypeOid = typenameTypeId(NULL, column->typeName);
+						if (type_is_range(colTypeOid))
+						{
+							found = true;
+							break;
+						}
+						else
+						{
+							ereport(ERROR,
+									(errcode(ERRCODE_DATATYPE_MISMATCH),
+									 errmsg("column \"%s\" named in WITHOUT OVERLAPS is not a range type",
+											without_overlaps_str)));
+						}
+					}
+				}
+			}
+			if (found)
+			{
+				AlterTableCmd *notnullcmd;
+				iparam->name = pstrdup(without_overlaps_str);
+				iparam->expr = NULL;
+
+				/*
+				 * Force the column to NOT NULL since it is part of the primary key.
+				 */
+				notnullcmd = makeNode(AlterTableCmd);
+
+				notnullcmd->subtype = AT_SetNotNull;
+				notnullcmd->name = pstrdup(without_overlaps_str);
+				notnullcmds = lappend(notnullcmds, notnullcmd);
+			}
+			else {
+				found = false;
+				/*
+				 * TODO: Search for a non-system PERIOD with the right name.
+				 */
+				if (found)
+				{
+					iparam->name = NULL;
+					/*
+					 * TODO: Build up a parse tree to cast the period to a range.
+					 * See transformExpr (called below and defined in parser/parse_expr.c.
+					 */
+					/*
+					TypeCast *expr = makeNode(TypeCast);
+					expr->arg = constraint->without_overlaps;
+					expr->typeName = "....";		// TODO: need to look up which range type to use
+					expr->location = -1;
+					iparam->expr = transformExpr(..., expr, EXPR_KIND_INDEX_EXPRESSION);
+					*/
+				}
+				else
+				{
+					ereport(ERROR,
+							(errcode(ERRCODE_UNDEFINED_COLUMN),
+							 errmsg("range or PERIOD \"%s\" named in WITHOUT OVERLAPS does not exist",
+									without_overlaps_str)));
+				}
+			}
+			{
+				List *opname;
+				iparam->indexcolname = NULL;
+				iparam->collation = NIL;
+				iparam->opclass = NIL;
+				iparam->ordering = SORTBY_DEFAULT;
+				iparam->nulls_ordering = SORTBY_NULLS_DEFAULT;
+				index->indexParams = lappend(index->indexParams, iparam);
+
+				opname = list_make1(makeString("&&"));
+				index->excludeOpNames = lappend(index->excludeOpNames, opname);
+				index->accessMethod = "gist";
+				constraint->access_method = "gist";
+			}
 		}
 	}
 
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 1faaafab08..d12ebe9f12 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -2988,6 +2988,18 @@ rewriteTargetView(Query *parsetree, Relation view)
 			}
 		}
 
+		if (parsetree->forPortionOf)
+		{
+			foreach(lc, parsetree->forPortionOf->rangeSet)
+			{
+				TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+				if (!tle->resjunk)
+					modified_cols = bms_add_member(modified_cols,
+												   tle->resno - FirstLowInvalidHeapAttributeNumber);
+			}
+		}
+
 		auto_update_detail = view_cols_are_auto_updatable(viewquery,
 														  modified_cols,
 														  NULL,
diff --git a/src/backend/utils/adt/rangetypes.c b/src/backend/utils/adt/rangetypes.c
index 01ad8bc240..2016b5ac91 100644
--- a/src/backend/utils/adt/rangetypes.c
+++ b/src/backend/utils/adt/rangetypes.c
@@ -2490,3 +2490,46 @@ datum_write(Pointer ptr, Datum datum, bool typbyval, char typalign,
 
 	return ptr;
 }
+
+/*
+ * range_leftover_internal - Sets output1 and output2 to the remaining parts of r1
+ * after subtracting r2, or if nothing is left then to the empty range.
+ * output1 will always be "before" r2 and output2 "after".
+ */
+void
+range_leftover_internal(TypeCacheEntry *typcache, const RangeType *r1,
+						const RangeType *r2, RangeType **output1, RangeType **output2)
+{
+	RangeBound	lower1,
+				lower2;
+	RangeBound	upper1,
+				upper2;
+	bool		empty1,
+				empty2;
+
+	range_deserialize(typcache, r1, &lower1, &upper1, &empty1);
+	range_deserialize(typcache, r2, &lower2, &upper2, &empty2);
+
+	if (range_cmp_bounds(typcache, &lower1, &lower2) < 0)
+	{
+		lower2.inclusive = !lower2.inclusive;
+		lower2.lower = false;
+		*output1 = make_range(typcache, &lower1, &lower2, false);
+	}
+	else
+	{
+		*output1 = make_empty_range(typcache);
+	}
+
+	if (range_cmp_bounds(typcache, &upper1, &upper2) > 0)
+	{
+		upper2.inclusive = !upper2.inclusive;
+		upper2.lower = true;
+		*output2 = make_range(typcache, &upper2, &upper1, false);
+	}
+	else
+	{
+		*output2 = make_empty_range(typcache);
+	}
+}
+
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 7e2b2e3dd6..49f48f3f04 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -108,6 +108,7 @@ typedef struct RI_ConstraintInfo
 	char		confupdtype;	/* foreign key's ON UPDATE action */
 	char		confdeltype;	/* foreign key's ON DELETE action */
 	char		confmatchtype;	/* foreign key's match type */
+	bool		temporal;		/* if the foreign key is temporal */
 	int			nkeys;			/* number of key columns */
 	int16		pk_attnums[RI_MAX_NUMKEYS]; /* attnums of referenced cols */
 	int16		fk_attnums[RI_MAX_NUMKEYS]; /* attnums of referencing cols */
@@ -191,7 +192,7 @@ static int	ri_NullCheck(TupleDesc tupdesc, TupleTableSlot *slot,
 static void ri_BuildQueryKey(RI_QueryKey *key,
 							 const RI_ConstraintInfo *riinfo,
 							 int32 constr_queryno);
-static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+static bool ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static bool ri_AttributesEqual(Oid eq_opr, Oid typeid,
 							   Datum oldvalue, Datum newvalue);
@@ -351,18 +352,46 @@ RI_FKey_check(TriggerData *trigdata)
 
 		/* ----------
 		 * The query string built is
-		 *	SELECT 1 FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
-		 *		   FOR KEY SHARE OF x
+		 *	SELECT 1
+		 *	FROM [ONLY] <pktable> x WHERE pkatt1 = $1 [AND ...]
+		 *	FOR KEY SHARE OF x
 		 * The type id's for the $ parameters are those of the
 		 * corresponding FK attributes.
+		 *
+		 * But for temporal FKs we need to make sure
+		 * the FK's range is completely covered.
+		 * So we use this query instead:
+		 *  SELECT 1
+		 *  FROM (
+		 *	    SELECT	range_agg(r) AS r
+		 *	    FROM	(
+		 *			SELECT pkperiodatt AS r
+		 *			FROM   [ONLY] pktable x
+		 *			WHERE  pkatt1 = $1 [AND ...]
+		 *			FOR KEY SHARE OF x
+		 *		) x1
+		 *  ) x2
+		 *  WHERE $n <@ x2.r
+		 * Note if FOR KEY SHARE ever allows aggregate functions
+		 * we can make this a bit simpler.
 		 * ----------
 		 */
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
 		quoteRelationName(pkrelname, pk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 pk_only, pkrelname);
+		if (riinfo->temporal)
+		{
+			quoteOneName(attname,
+					RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			appendStringInfo(&querybuf,
+					"SELECT 1 FROM (SELECT range_agg(r) AS r FROM (SELECT %s AS r FROM %s%s x",
+					attname, pk_only, pkrelname);
+		}
+		else {
+			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
+							 pk_only, pkrelname);
+		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -380,6 +409,8 @@ RI_FKey_check(TriggerData *trigdata)
 			queryoids[i] = fk_type;
 		}
 		appendStringInfoString(&querybuf, " FOR KEY SHARE OF x");
+		if (riinfo->temporal)
+			appendStringInfo(&querybuf, ") x1) x2 WHERE $%d <@ x2.r", riinfo->nkeys);
 
 		/* Prepare and save the plan */
 		qplan = ri_PlanCheck(querybuf.data, riinfo->nkeys, queryoids,
@@ -1174,7 +1205,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
 		return false;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (newslot && ri_KeysEqual(pk_rel, oldslot, newslot, riinfo, true))
+	if (newslot && ri_KeysStable(pk_rel, oldslot, newslot, riinfo, true))
 		return false;
 
 	/* Else we need to fire the trigger. */
@@ -1267,13 +1298,135 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 		return true;
 
 	/* If all old and new key values are equal, no check is needed */
-	if (ri_KeysEqual(fk_rel, oldslot, newslot, riinfo, false))
+	if (ri_KeysStable(fk_rel, oldslot, newslot, riinfo, false))
 		return false;
 
 	/* Else we need to fire the trigger. */
 	return true;
 }
 
+/* ----------
+ * TRI_FKey_check_ins -
+ *
+ *	Check temporal foreign key existence at insert event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_ins(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_ins", RI_TRIGTYPE_INSERT);
+
+	/*
+	 * Share code with UPDATE case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_check_upd -
+ *
+ *	Check temporal foreign key existence at update event on FK table.
+ * ----------
+ */
+Datum
+TRI_FKey_check_upd(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "RI_FKey_check_upd", RI_TRIGTYPE_UPDATE);
+
+	/*
+	 * Share code with INSERT case.
+	 */
+	return RI_FKey_check((TriggerData *) fcinfo->context);
+}
+
+
+/* ----------
+ * TRI_FKey_noaction_del -
+ *
+ *	Give an error and roll back the current transaction if the
+ *	delete has resulted in a violation of the given temporal
+ *	referential integrity constraint.
+ * ----------
+ */
+Datum
+TRI_FKey_noaction_del(PG_FUNCTION_ARGS)
+{
+	/*
+	 * Check that this is a valid trigger call on the right time and event.
+	 */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_del", RI_TRIGTYPE_DELETE);
+
+	/*
+	 * Share code with RESTRICT/UPDATE cases.
+	 */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_del -
+ *
+ * Restrict delete from PK table to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the delete is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_del(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_del", RI_TRIGTYPE_DELETE);
+
+	/* Share code with NO ACTION/UPDATE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+/*
+ * TRI_FKey_noaction_upd -
+ *
+ * Give an error and roll back the current transaction if the
+ * update has resulted in a violation of the given referential
+ * integrity constraint.
+ */
+Datum
+TRI_FKey_noaction_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_noaction_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with RESTRICT/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, true);
+}
+
+/*
+ * TRI_FKey_restrict_upd -
+ *
+ * Restrict update of PK to rows unreferenced by foreign key.
+ *
+ * The SQL standard intends that this referential action occur exactly when
+ * the update is performed, rather than after.  This appears to be
+ * the only difference between "NO ACTION" and "RESTRICT".  In Postgres
+ * we still implement this as an AFTER trigger, but it's non-deferrable.
+ */
+Datum
+TRI_FKey_restrict_upd(PG_FUNCTION_ARGS)
+{
+	/* Check that this is a valid trigger call on the right time and event. */
+	ri_CheckTrigger(fcinfo, "TRI_FKey_restrict_upd", RI_TRIGTYPE_UPDATE);
+
+	/* Share code with NO ACTION/DELETE cases. */
+	return ri_restrict((TriggerData *) fcinfo->context, false);
+}
+
+
 /*
  * RI_Initial_Check -
  *
@@ -2059,6 +2212,7 @@ ri_LoadConstraintInfo(Oid constraintOid)
 	riinfo->confupdtype = conForm->confupdtype;
 	riinfo->confdeltype = conForm->confdeltype;
 	riinfo->confmatchtype = conForm->confmatchtype;
+	riinfo->temporal = conForm->contemporal;
 
 	DeconstructFkConstraintRow(tup,
 							   &riinfo->nkeys,
@@ -2654,9 +2808,12 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
 
 
 /*
- * ri_KeysEqual -
+ * ri_KeysStable -
  *
- * Check if all key values in OLD and NEW are equal.
+ * Check if all key values in OLD and NEW are "equivalent":
+ * For normal FKs we check for equality.
+ * For temporal FKs we check that the PK side is a superset of its old value,
+ * or the FK side is a subset.
  *
  * Note: at some point we might wish to redefine this as checking for
  * "IS NOT DISTINCT" rather than "=", that is, allow two nulls to be
@@ -2664,7 +2821,7 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan)
  * previously found at least one of the rows to contain no nulls.
  */
 static bool
-ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
+ri_KeysStable(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -2697,29 +2854,43 @@ ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot,
 
 		if (rel_is_pk)
 		{
-			/*
-			 * If we are looking at the PK table, then do a bytewise
-			 * comparison.  We must propagate PK changes if the value is
-			 * changed to one that "looks" different but would compare as
-			 * equal using the equality operator.  This only makes a
-			 * difference for ON UPDATE CASCADE, but for consistency we treat
-			 * all changes to the PK the same.
-			 */
-			Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, newvalue, oldvalue));
+			}
+			else
+			{
+				/*
+				 * If we are looking at the PK table, then do a bytewise
+				 * comparison.  We must propagate PK changes if the value is
+				 * changed to one that "looks" different but would compare as
+				 * equal using the equality operator.  This only makes a
+				 * difference for ON UPDATE CASCADE, but for consistency we treat
+				 * all changes to the PK the same.
+				 */
+				Form_pg_attribute att = TupleDescAttr(oldslot->tts_tupleDescriptor, attnums[i] - 1);
 
-			if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
-				return false;
+				if (!datum_image_eq(oldvalue, newvalue, att->attbyval, att->attlen))
+					return false;
+			}
 		}
 		else
 		{
-			/*
-			 * For the FK table, compare with the appropriate equality
-			 * operator.  Changes that compare equal will still satisfy the
-			 * constraint after the update.
-			 */
-			if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
-									oldvalue, newvalue))
-				return false;
+			if (riinfo->temporal)
+			{
+				return DatumGetBool(DirectFunctionCall2(range_contains, oldvalue, newvalue));
+			}
+			else
+			{
+				/*
+				 * For the FK table, compare with the appropriate equality
+				 * operator.  Changes that compare equal will still satisfy the
+				 * constraint after the update.
+				 */
+				if (!ri_AttributesEqual(riinfo->ff_eq_oprs[i], RIAttType(rel, attnums[i]),
+										oldvalue, newvalue))
+					return false;
+			}
 		}
 	}
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 6c656586e8..97bfa896a4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -331,7 +331,7 @@ static char *pg_get_viewdef_worker(Oid viewoid,
 								   int prettyFlags, int wrapColumn);
 static char *pg_get_triggerdef_worker(Oid trigid, bool pretty);
 static int	decompile_column_index_array(Datum column_index_array, Oid relId,
-										 StringInfo buf);
+										 bool withoutOverlaps, bool withPeriod, StringInfo buf);
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
 									const Oid *excludeOps,
@@ -2009,6 +2009,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			{
 				Datum		val;
 				bool		isnull;
+				bool		hasperiod;
 				const char *string;
 
 				/* Start off the constraint definition */
@@ -2021,7 +2022,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it is a temporal foreign key
+				 * then it uses PERIOD.
+				 */
+				hasperiod = DatumGetBool(SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_contemporal, &isnull));
+				decompile_column_index_array(val, conForm->conrelid, false, hasperiod, &buf);
 
 				/* add foreign relation name */
 				appendStringInfo(&buf, ") REFERENCES %s(",
@@ -2035,7 +2042,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null confkey for constraint %u",
 						 constraintId);
 
-				decompile_column_index_array(val, conForm->confrelid, &buf);
+				decompile_column_index_array(val, conForm->confrelid, false, hasperiod, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2136,7 +2143,13 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					elog(ERROR, "null conkey for constraint %u",
 						 constraintId);
 
-				keyatts = decompile_column_index_array(val, conForm->conrelid, &buf);
+				/*
+				 * If it has exclusion-style operator OIDs
+				 * then it uses WITHOUT OVERLAPS.
+				 */
+				SysCacheGetAttr(CONSTROID, tup,
+						  Anum_pg_constraint_conexclop, &isnull);
+				keyatts = decompile_column_index_array(val, conForm->conrelid, !isnull, false, &buf);
 
 				appendStringInfoChar(&buf, ')');
 
@@ -2338,7 +2351,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
  */
 static int
 decompile_column_index_array(Datum column_index_array, Oid relId,
-							 StringInfo buf)
+							 bool withoutOverlaps, bool withPeriod, StringInfo buf)
 {
 	Datum	   *keys;
 	int			nKeys;
@@ -2356,9 +2369,21 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
+		{
 			appendStringInfoString(buf, quote_identifier(colName));
+		}
+		else if (withoutOverlaps && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", %s WITHOUT OVERLAPS", quote_identifier(colName));
+		}
+		else if (withPeriod && j == nKeys - 1)
+		{
+			appendStringInfo(buf, ", PERIOD %s", quote_identifier(colName));
+		}
 		else
+		{
 			appendStringInfo(buf, ", %s", quote_identifier(colName));
+		}
 	}
 
 	return nKeys;
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 140339073b..e15078398e 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2076,6 +2076,32 @@ get_typisdefined(Oid typid)
 		return false;
 }
 
+/*
+ * get_typname
+ *
+ *		Returns the name of a given type
+ *
+ * Returns a palloc'd copy of the string, or NULL if no such type.
+ */
+char *
+get_typname(Oid typid)
+{
+	HeapTuple	tp;
+
+	tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+	if (HeapTupleIsValid(tp))
+	{
+		Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
+		char	   *result;
+
+		result = pstrdup(NameStr(typtup->typname));
+		ReleaseSysCache(tp);
+		return result;
+	}
+	else
+		return NULL;
+}
+
 /*
  * get_typlen
  *
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 9061af81a3..2e36fa7274 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4567,11 +4567,17 @@ RelationGetIndexList(Relation relation)
 		 * interesting for either oid indexes or replication identity indexes,
 		 * so don't check them.
 		 */
-		if (!index->indisvalid || !index->indisunique ||
-			!index->indimmediate ||
+		if (!index->indisvalid || !index->indimmediate ||
 			!heap_attisnull(htup, Anum_pg_index_indpred, NULL))
 			continue;
 
+		/*
+		 * Non-unique indexes aren't interesting either,
+		 * except when they are temporal primary keys.
+		 */
+		if (!index->indisunique && !index->indisprimary)
+			continue;
+
 		/* remember primary key index if any */
 		if (index->indisprimary)
 			pkeyIndex = index->indexrelid;
@@ -5173,8 +5179,9 @@ restart:
  * RelationGetExclusionInfo -- get info about index's exclusion constraint
  *
  * This should be called only for an index that is known to have an
- * associated exclusion constraint.  It returns arrays (palloc'd in caller's
- * context) of the exclusion operator OIDs, their underlying functions'
+ * associated exclusion constraint or temporal primary key.
+ * It returns arrays (palloc'd in caller's * context)
+ * of the exclusion operator OIDs, their underlying functions'
  * OIDs, and their strategy numbers in the index's opclasses.  We cache
  * all this information since it requires a fair amount of work to get.
  */
@@ -5240,7 +5247,12 @@ RelationGetExclusionInfo(Relation indexRelation,
 		int			nelem;
 
 		/* We want the exclusion constraint owning the index */
-		if (conform->contype != CONSTRAINT_EXCLUSION ||
+		/*
+		 * TODO: Is this too permissive?
+		 * Maybe it needs to be (!= CONSTRAINT_PRIMARY || !has_excl_operators)
+		 */
+		if ((conform->contype != CONSTRAINT_EXCLUSION &&
+					conform->contype != CONSTRAINT_PRIMARY) ||
 			conform->conindid != RelationGetRelid(indexRelation))
 			continue;
 
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ff45e3fb8c..a88f1395f4 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -7028,7 +7028,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_withoutoverlaps;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -7089,7 +7090,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "c.conexclop IS NOT NULL AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -7128,7 +7130,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7163,7 +7166,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7194,7 +7198,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7228,7 +7233,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "null AS withoutoverlaps "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7268,6 +7274,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_withoutoverlaps = PQfnumber(res, "withoutoverlaps");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7331,6 +7338,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				constrinfo[j].condeferred = *(PQgetvalue(res, j, i_condeferred)) == 't';
 				constrinfo[j].conislocal = true;
 				constrinfo[j].separate = true;
+				constrinfo[j].withoutoverlaps = *(PQgetvalue(res, j, i_withoutoverlaps)) == 't';
 
 				indxinfo[j].indexconstraint = constrinfo[j].dobj.dumpId;
 			}
@@ -16663,9 +16671,22 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 					break;
 				attname = getAttrName(indkey, tbinfo);
 
-				appendPQExpBuffer(q, "%s%s",
-								  (k == 0) ? "" : ", ",
-								  fmtId(attname));
+				if (k == 0)
+				{
+					appendPQExpBuffer(q, "%s",
+										fmtId(attname));
+				}
+				else if (k == indxinfo->indnkeyattrs - 1 &&
+						coninfo->withoutoverlaps)
+				{
+					appendPQExpBuffer(q, ", %s WITHOUT OVERLAPS",
+										fmtId(attname));
+				}
+				else
+				{
+					appendPQExpBuffer(q, ", %s",
+										fmtId(attname));
+				}
 			}
 
 			if (indxinfo->indnkeyattrs < indxinfo->indnattrs)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e0b42e8391..0fe82ab8aa 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -450,6 +450,7 @@ typedef struct _constraintInfo
 	bool		condeferred;	/* true if constraint is INITIALLY DEFERRED */
 	bool		conislocal;		/* true if constraint has local definition */
 	bool		separate;		/* true if must dump as separate item */
+	bool		withoutoverlaps;	/* true if the last elem is WITHOUT OVERLAPS */
 } ConstraintInfo;
 
 typedef struct _procLangInfo
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index ec63662060..86b2e19ec5 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -638,6 +638,28 @@ my %tests = (
 		},
 	},
 
+	'ALTER TABLE ONLY test_table ADD CONSTRAINT ... PRIMARY KEY (..., ... WITHOUT OVERLAPS)' => {
+		create_sql  => 'CREATE TABLE dump_test.test_table_tpk (
+							col1 int4range,
+							col2 tstzrange,
+							CONSTRAINT test_table_tpk_pkey PRIMARY KEY
+								(col1, col2 WITHOUT OVERLAPS));',
+		regexp => qr/^
+			\QALTER TABLE ONLY dump_test.test_table_tpk\E \n^\s+
+			\QADD CONSTRAINT test_table_tpk_pkey PRIMARY KEY (col1, col2 WITHOUT OVERLAPS);\E
+			/xm,
+		like => {
+			%full_runs,
+			%dump_test_schema_runs,
+			section_post_data    => 1,
+			exclude_test_table       => 1,
+		},
+		unlike => {
+			only_dump_test_table => 1,
+			exclude_dump_test_schema => 1,
+		},
+	},
+
 	'ALTER TABLE (partitioned) ADD CONSTRAINT ... FOREIGN KEY' => {
 		create_order => 4,
 		create_sql   => 'CREATE TABLE dump_test.test_table_fk (
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 07d640021c..5063833e28 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2391,6 +2391,8 @@ describeOneTableDetails(const char *schemaname,
 						}
 
 						/* Everything after "USING" is echoed verbatim */
+						// TODO: Show WITHOUT OVERLAPS info here?
+						// It is not really part of the *index*.
 						indexdef = PQgetvalue(result, i, 5);
 						usingpos = strstr(indexdef, " USING ");
 						if (usingpos)
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index f58e8675f3..6cc5857565 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -77,6 +77,7 @@ extern Oid	index_create(Relation heapRelation,
 #define	INDEX_CONSTR_CREATE_INIT_DEFERRED	(1 << 2)
 #define	INDEX_CONSTR_CREATE_UPDATE_INDEX	(1 << 3)
 #define	INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS	(1 << 4)
+#define	INDEX_CONSTR_CREATE_TEMPORAL		(1 << 5)
 
 extern Oid	index_concurrently_create_copy(Relation heapRelation,
 										   Oid oldIndexId,
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 9600ece93c..1740578e37 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -102,6 +102,12 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	/* Has a local definition and cannot be inherited */
 	bool		connoinherit;
 
+	/*
+	 * For primary and foreign keys, signifies the last column is a range 
+	 * and should use overlaps instead of equals.
+	 */
+	bool		contemporal;
+
 #ifdef CATALOG_VARLEN			/* variable-length fields start here */
 
 	/*
@@ -116,26 +122,26 @@ CATALOG(pg_constraint,2606,ConstraintRelationId)
 	int16		confkey[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = FK equality operators for each
+	 * If a foreign key, the OIDs of the PK = FK comparison operators for each
 	 * column of the constraint
 	 */
 	Oid			conpfeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the PK = PK equality operators for each
+	 * If a foreign key, the OIDs of the PK = PK comparison operators for each
 	 * column of the constraint (i.e., equality for the referenced columns)
 	 */
 	Oid			conppeqop[1];
 
 	/*
-	 * If a foreign key, the OIDs of the FK = FK equality operators for each
+	 * If a foreign key, the OIDs of the FK = FK comparison operators for each
 	 * column of the constraint (i.e., equality for the referencing columns)
 	 */
 	Oid			conffeqop[1];
 
 	/*
 	 * If an exclusion constraint, the OIDs of the exclusion operators for
-	 * each column of the constraint
+	 * each column of the constraint. Also set for temporal primary keys.
 	 */
 	Oid			conexclop[1];
 
@@ -210,6 +216,7 @@ extern Oid	CreateConstraintEntry(const char *constraintName,
 								  bool conIsLocal,
 								  int conInhCount,
 								  bool conNoInherit,
+								  bool conTemporal,
 								  bool is_internal);
 
 extern void RemoveConstraintById(Oid conId);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbcac69d48..76a8e0fd75 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3768,6 +3768,50 @@
   prorettype => 'trigger', proargtypes => '',
   prosrc => 'RI_FKey_noaction_upd' },
 
+# Temporal referential integrity constraint triggers
+{ oid => '6122', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_ins', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_ins' },
+{ oid => '6123', descr => 'temporal referential integrity FOREIGN KEY ... REFERENCES',
+  proname => 'TRI_FKey_check_upd', provolatile => 'v', prorettype => 'trigger',
+  proargtypes => '', prosrc => 'TRI_FKey_check_upd' },
+# { oid => '6124', descr => 'temporal referential integrity ON DELETE CASCADE',
+#   proname => 'TRI_FKey_cascade_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_del' },
+# { oid => '6125', descr => 'temporal referential integrity ON UPDATE CASCADE',
+#   proname => 'TRI_FKey_cascade_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_cascade_upd' },
+{ oid => '6126', descr => 'temporal referential integrity ON DELETE RESTRICT',
+  proname => 'TRI_FKey_restrict_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_del' },
+{ oid => '6127', descr => 'temporal referential integrity ON UPDATE RESTRICT',
+  proname => 'TRI_FKey_restrict_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_restrict_upd' },
+# { oid => '6128', descr => 'temporal referential integrity ON DELETE SET NULL',
+#   proname => 'TRI_FKey_setnull_del', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_del' },
+# { oid => '6129', descr => 'temporal referential integrity ON UPDATE SET NULL',
+#   proname => 'TRI_FKey_setnull_upd', provolatile => 'v', prorettype => 'trigger',
+#   proargtypes => '', prosrc => 'TRI_FKey_setnull_upd' },
+# { oid => '6130', descr => 'temporal referential integrity ON DELETE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_del', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_del' },
+# { oid => '6131', descr => 'temporal referential integrity ON UPDATE SET DEFAULT',
+#   proname => 'TRI_FKey_setdefault_upd', provolatile => 'v',
+#   prorettype => 'trigger', proargtypes => '',
+#   prosrc => 'TRI_FKey_setdefault_upd' },
+{ oid => '6132', descr => 'temporal referential integrity ON DELETE NO ACTION',
+  proname => 'TRI_FKey_noaction_del', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_del' },
+{ oid => '6133', descr => 'temporal referential integrity ON UPDATE NO ACTION',
+  proname => 'TRI_FKey_noaction_upd', provolatile => 'v',
+  prorettype => 'trigger', proargtypes => '',
+  prosrc => 'TRI_FKey_noaction_upd' },
+
 { oid => '1666',
   proname => 'varbiteq', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'varbit varbit', prosrc => 'biteq' },
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6c0a7d68d6..599be9ab1d 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -25,12 +25,14 @@
 #include "storage/condition_variable.h"
 #include "utils/hsearch.h"
 #include "utils/queryenvironment.h"
+#include "utils/rangetypes.h"
 #include "utils/reltrigger.h"
 #include "utils/sharedtuplestore.h"
 #include "utils/snapshot.h"
 #include "utils/sortsupport.h"
 #include "utils/tuplesort.h"
 #include "utils/tuplestore.h"
+#include "utils/typcache.h"
 
 struct PlanState;				/* forward references in this file */
 struct ParallelHashJoinState;
@@ -387,6 +389,22 @@ typedef struct OnConflictSetState
 	ExprState  *oc_WhereClause; /* state for the WHERE clause */
 } OnConflictSetState;
 
+/*
+ * ForPortionOfState
+ *
+ * Executor state of a FOR PORTION OF operation.
+ */
+typedef struct ForPortionOfState
+{
+	NodeTag		type;
+
+	TypeCacheEntry	*fp_rangetypcache;	/* type cache entry of the range */
+	RangeType		*fp_targetRange;	/* the range from FOR PORTION OF */
+	TupleTableSlot	*fp_Existing;	/* slot to store existing target tuple in */
+	TupleTableSlot	*fp_Leftover1;	/* slot to store leftover below the target range */
+	TupleTableSlot	*fp_Leftover2;	/* slot to store leftover above the target range */
+} ForPortionOfState;
+
 /*
  * ResultRelInfo
  *
@@ -476,6 +494,9 @@ typedef struct ResultRelInfo
 	/* ON CONFLICT evaluation state */
 	OnConflictSetState *ri_onConflict;
 
+	/* FOR PORTION OF evaluation state */
+	ForPortionOfState *ri_forPortionOf;
+
 	/* partition check expression state (NULL if not set up yet) */
 	ExprState  *ri_PartitionCheckExpr;
 
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 7ddd8c011b..ac53f29f58 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -35,6 +35,7 @@ typedef enum NodeTag
 	T_ProjectionInfo,
 	T_JunkFilter,
 	T_OnConflictSetState,
+	T_ForPortionOfState,
 	T_ResultRelInfo,
 	T_EState,
 	T_TupleTableSlot,
@@ -197,6 +198,7 @@ typedef enum NodeTag
 	T_JoinExpr,
 	T_FromExpr,
 	T_OnConflictExpr,
+	T_ForPortionOfExpr,
 	T_IntoClause,
 
 	/*
@@ -471,6 +473,7 @@ typedef enum NodeTag
 	T_WithClause,
 	T_InferClause,
 	T_OnConflictClause,
+	T_ForPortionOfClause,
 	T_CommonTableExpr,
 	T_RoleSpec,
 	T_TriggerTransition,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 60c2f45466..ea97893df9 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -122,6 +122,8 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
 
+	ForPortionOfExpr *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
+
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
@@ -1386,6 +1388,19 @@ typedef struct RowMarkClause
 	bool		pushedDown;		/* pushed down from higher query level? */
 } RowMarkClause;
 
+/*
+ * ForPortionOfClause
+ *		representation of FOR PORTION OF <period-name> FROM <t1> TO <t2>
+ */
+typedef struct ForPortionOfClause
+{
+	NodeTag		type;
+	char	   *range_name;
+	int			range_name_location;
+	Node	   *target_start;
+	Node	   *target_end;
+} ForPortionOfClause;
+
 /*
  * WithClause -
  *	   representation of WITH clause
@@ -1541,12 +1556,13 @@ typedef struct InsertStmt
  */
 typedef struct DeleteStmt
 {
-	NodeTag		type;
-	RangeVar   *relation;		/* relation to delete from */
-	List	   *usingClause;	/* optional using clause for more tables */
-	Node	   *whereClause;	/* qualifications */
-	List	   *returningList;	/* list of expressions to return */
-	WithClause *withClause;		/* WITH clause */
+	NodeTag				type;
+	RangeVar		   *relation;		/* relation to delete from */
+	ForPortionOfClause *forPortionOf;	/* FOR PORTION OF clause */
+	List			   *usingClause;	/* optional using clause for more tables */
+	Node			   *whereClause;	/* qualifications */
+	List			   *returningList;	/* list of expressions to return */
+	WithClause		   *withClause;		/* WITH clause */
 } DeleteStmt;
 
 /* ----------------------
@@ -1555,13 +1571,14 @@ typedef struct DeleteStmt
  */
 typedef struct UpdateStmt
 {
-	NodeTag		type;
-	RangeVar   *relation;		/* relation to update */
-	List	   *targetList;		/* the target list (of ResTarget) */
-	Node	   *whereClause;	/* qualifications */
-	List	   *fromClause;		/* optional from clause for more tables */
-	List	   *returningList;	/* list of expressions to return */
-	WithClause *withClause;		/* WITH clause */
+	NodeTag				type;
+	RangeVar		   *relation;		/* relation to update */
+	ForPortionOfClause *forPortionOf;	/* FOR PORTION OF clause */
+	List			   *targetList;		/* the target list (of ResTarget) */
+	Node			   *whereClause;	/* qualifications */
+	List			   *fromClause;		/* optional from clause for more tables */
+	List			   *returningList;	/* list of expressions to return */
+	WithClause		   *withClause;		/* WITH clause */
 } UpdateStmt;
 
 /* ----------------------
@@ -2187,7 +2204,9 @@ typedef struct Constraint
 	/* Fields used for FOREIGN KEY constraints: */
 	RangeVar   *pktable;		/* Primary key table */
 	List	   *fk_attrs;		/* Attributes of foreign key */
+	Node	   *fk_period;		/* String node naming Period or range column */
 	List	   *pk_attrs;		/* Corresponding attrs in PK table */
+	Node	   *pk_period;		/* String node naming Period or range column */
 	char		fk_matchtype;	/* FULL, PARTIAL, SIMPLE */
 	char		fk_upd_action;	/* ON UPDATE action */
 	char		fk_del_action;	/* ON DELETE action */
@@ -2195,6 +2214,9 @@ typedef struct Constraint
 	Oid			old_pktable_oid;	/* pg_constraint.confrelid of my former
 									 * self */
 
+  /* Fields used for temporal PRIMARY KEY and FOREIGN KEY constraints: */
+	Node	   *without_overlaps; /* String node naming PERIOD or range column */
+
 	/* Fields used for constraints that allow a NOT VALID specification */
 	bool		skip_validation;	/* skip validation of existing rows? */
 	bool		initially_valid;	/* mark the new constraint as valid? */
@@ -2793,6 +2815,7 @@ typedef struct IndexStmt
 	bool		unique;			/* is index unique? */
 	bool		primary;		/* is index a primary key? */
 	bool		isconstraint;	/* is it for a pkey/unique constraint? */
+	bool		istemporal;		/* is it for a temporal pkey? */
 	bool		deferrable;		/* is the constraint DEFERRABLE? */
 	bool		initdeferred;	/* is the constraint INITIALLY DEFERRED? */
 	bool		transformed;	/* true when transformIndexStmt is finished */
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 3dd16b9ad5..d39b63802f 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1821,6 +1821,7 @@ typedef struct ModifyTablePath
 	List	   *returningLists; /* per-target-table RETURNING tlists */
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	OnConflictExpr *onconflict; /* ON CONFLICT clause, or NULL */
+	ForPortionOfExpr *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 } ModifyTablePath;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 7e6b10f86b..951771b4e8 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -226,6 +226,8 @@ typedef struct ModifyTable
 	List	   *rowMarks;		/* PlanRowMarks (non-locking only) */
 	int			epqParam;		/* ID of Param for EvalPlanQual re-eval */
 	OnConflictAction onConflictAction;	/* ON CONFLICT action */
+	// TODO: Instead of re-using Expr here, break it into pieces like onConflict{Action,Set,Where}?
+	Node	   *forPortionOf;	/* FOR PORTION OF clause for UPDATE/DELETE */
 	List	   *arbiterIndexes; /* List of ON CONFLICT arbiter index OIDs  */
 	List	   *onConflictSet;	/* SET for INSERT ON CONFLICT DO UPDATE */
 	Node	   *onConflictWhere;	/* WHERE for ON CONFLICT UPDATE */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index fd65ee8f9c..b2d9abe3c4 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1541,4 +1541,25 @@ typedef struct OnConflictExpr
 	List	   *exclRelTlist;	/* tlist of the EXCLUDED pseudo relation */
 } OnConflictExpr;
 
+/*----------
+ * ForPortionOfExpr - represents a FOR PORTION OF ... expression
+ *
+ * TODO: more notes as needed
+ *----------
+ */
+typedef struct ForPortionOfExpr
+{
+	NodeTag		type;
+	int			range_attno;	/* Range column number */
+	char	   *range_name;		/* Range name */
+	Expr	   *range;			/* Range column or expression */
+	Node	   *startCol;		/* Start column if using a PERIOD */
+	Node	   *endCol;			/* End column if using a PERIOD */
+	Node	   *targetStart;	/* Same type as the range's elements */
+	Node	   *targetEnd;		/* Same type as the range's elements */
+	Node	   *targetRange;	/* A range from targetStart to targetEnd */
+	Node	   *overlapsExpr;	/* range && targetRange */
+	List	   *rangeSet;		/* List of TargetEntrys to set the time column(s) */
+} ForPortionOfExpr;
+
 #endif							/* PRIMNODES_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 715a24ad29..cbdb984136 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -264,7 +264,7 @@ extern ModifyTablePath *create_modifytable_path(PlannerInfo *root,
 												List *subroots,
 												List *withCheckOptionLists, List *returningLists,
 												List *rowMarks, OnConflictExpr *onconflict,
-												int epqParam);
+												ForPortionOfExpr *forPortionOf, int epqParam);
 extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
 									Path *subpath,
 									Node *limitOffset, Node *limitCount,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..4915e858f4 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,9 +306,11 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("period", PERIOD, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("portion", PORTION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("position", POSITION, COL_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("preceding", PRECEDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("precision", PRECISION, COL_NAME_KEYWORD, AS_LABEL)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d25819aa28..9911d96120 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -55,6 +55,7 @@ typedef enum ParseExprKind
 	EXPR_KIND_INSERT_TARGET,	/* INSERT target list item */
 	EXPR_KIND_UPDATE_SOURCE,	/* UPDATE assignment source item */
 	EXPR_KIND_UPDATE_TARGET,	/* UPDATE assignment target item */
+	EXPR_KIND_UPDATE_PORTION,	/* UPDATE FOR PORTION OF item */
 	EXPR_KIND_GROUP_BY,			/* GROUP BY */
 	EXPR_KIND_ORDER_BY,			/* ORDER BY */
 	EXPR_KIND_DISTINCT_ON,		/* DISTINCT ON */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index fecfe1f4f6..1990732119 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -137,6 +137,7 @@ extern char get_rel_persistence(Oid relid);
 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 int16 get_typlen(Oid typid);
 extern bool get_typbyval(Oid typid);
 extern void get_typlenbyval(Oid typid, int16 *typlen, bool *typbyval);
diff --git a/src/include/utils/rangetypes.h b/src/include/utils/rangetypes.h
index b77c41cf1b..b60f0863be 100644
--- a/src/include/utils/rangetypes.h
+++ b/src/include/utils/rangetypes.h
@@ -135,5 +135,8 @@ extern int	range_cmp_bound_values(TypeCacheEntry *typcache, const RangeBound *b1
 extern bool bounds_adjacent(TypeCacheEntry *typcache, RangeBound bound1,
 							RangeBound bound2);
 extern RangeType *make_empty_range(TypeCacheEntry *typcache);
+extern void range_leftover_internal(TypeCacheEntry *typcache, const RangeType *r1,
+									const RangeType *r2, RangeType **output1,
+									RangeType **output2);
 
 #endif							/* RANGETYPES_H */
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index 192445878d..2b71f53c5f 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -48,6 +48,7 @@ f_star|f
 fast_emp4000|t
 float4_tbl|f
 float8_tbl|f
+for_portion_of_test|t
 func_index_heap|t
 hash_f8_heap|t
 hash_i4_heap|t
@@ -172,6 +173,7 @@ quad_poly_tbl|t
 radix_text_tbl|t
 ramp|f
 real_city|f
+referencing_period_test|t
 road|t
 shighway|t
 slow_emp4000|f
@@ -213,6 +215,7 @@ trigger_parted_p1|t
 trigger_parted_p1_1|t
 varchar_tbl|f
 view_base_table|t
+without_overlaps_test|t
 -- restore normal output mode
 \a\t
 --
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7f0b..6c71eeeae4 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -107,12 +107,12 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml without_overlaps
 
 # ----------
 # Another group of parallel tests
 # ----------
-test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain
+test: partition_join partition_prune reloptions hash_part indexing partition_aggregate partition_info tuplesort explain without_overlaps for_portion_of
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 525bdc804f..0caf585e64 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -21,6 +21,8 @@ test: enum
 test: money
 test: rangetypes
 test: pg_lsn
+test: without_overlaps
+test: for_portion_of
 test: regproc
 test: strings
 test: numerology
#31Paul Jungwirth
pj@illuminatedcomputing.com
In reply to: Ibrar Ahmed (#30)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On 10/27/20 7:11 AM, Ibrar Ahmed wrote:

I have spent some more time on the patch and did a lot of cleanup along
with some fixes, compilation errors, and warnings.

Thank you for taking a look at this! I've been swamped with ordinary
work and haven't had a chance to focus on it for a while, but I'm hoping
to make some improvements over the coming holidays, especially based on
feedback from my talk at PgCon. There are a handful of small specific
things I'd like to do, and then one big thing: add support for PERIODs.
Vik said I could include his old patch for PERIODs, so I'd like to get
that working on the latest master, and then rebase my own work on top of
it. Then we can accept either ranges or PERIODs in various places
(marked by TODOs in the code).

Vik also pointed out a way to check foreign keys without using
range_agg. He thinks it may even be more efficient. On the other hand
it's a much more complicated SQL statement. I'd like to do a performance
comparison to get concrete numbers, but if we did use his query, then
this patch wouldn't depend on multiranges anymore---which seems like a
big aid to moving it forward. Assuming multiranges gets committed, we
can always swap in the range_agg query depending on the performance
comparison results.

I apologize for the slow progress here, and thank you for your help!

Yours,

--
Paul ~{:-)
pj@illuminatedcomputing.com

#32David Steele
david@pgmasters.net
In reply to: Paul Jungwirth (#31)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On 10/27/20 12:34 PM, Paul Jungwirth wrote:

On 10/27/20 7:11 AM, Ibrar Ahmed wrote:

I have spent some more time on the patch and did a lot of
cleanup along with some fixes, compilation errors, and warnings.

Thank you for taking a look at this! I've been swamped with ordinary
work and haven't had a chance to focus on it for a while, but I'm hoping
to make some improvements over the coming holidays, especially based on
feedback from my talk at PgCon. There are a handful of small specific
things I'd like to do, and then one big thing: add support for PERIODs.
Vik said I could include his old patch for PERIODs, so I'd like to get
that working on the latest master, and then rebase my own work on top of
it. Then we can accept either ranges or PERIODs in various places
(marked by TODOs in the code).

Vik also pointed out a way to check foreign keys without using
range_agg. He thinks it may even be more efficient. On the other hand
it's a much more complicated SQL statement. I'd like to do a performance
comparison to get concrete numbers, but if we did use his query, then
this patch wouldn't depend on multiranges anymore---which seems like a
big aid to moving it forward. Assuming multiranges gets committed, we
can always swap in the range_agg query depending on the performance
comparison results.

I apologize for the slow progress here, and thank you for your help!

Looks like Ibrar reopened this patch in the 2020-09 CF rather than
moving it to a new one. Given that Paul has not had a chance to look at
it since then I'm setting it back to RwF.

Paul, you can submit to the next CF when you are ready with a new patch.

Regards,
--
-David
david@pgmasters.net

#33Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: David Steele (#32)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Thu, Apr 8, 2021 at 7:22 AM David Steele <david@pgmasters.net> wrote:

Paul, you can submit to the next CF when you are ready with a new patch.

Thanks David! I've made a lot of progress but still need to finish
support for CASCADE on temporal foreign keys. I've been swamped with
other things, but hopefully I can get something during this current
CF.

Paul

#34David Steele
david@pgmasters.net
In reply to: Paul A Jungwirth (#33)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On 4/8/21 7:40 PM, Paul A Jungwirth wrote:

On Thu, Apr 8, 2021 at 7:22 AM David Steele <david@pgmasters.net> wrote:

Paul, you can submit to the next CF when you are ready with a new patch.

Thanks David! I've made a lot of progress but still need to finish
support for CASCADE on temporal foreign keys. I've been swamped with
other things, but hopefully I can get something during this current
CF.

The next CF starts on July 1 so you have some time.

Regards,
--
-David
david@pgmasters.net

#35Ibrar Ahmed
ibrar.ahmad@gmail.com
In reply to: David Steele (#34)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Fri, Apr 9, 2021 at 4:54 PM David Steele <david@pgmasters.net> wrote:

On 4/8/21 7:40 PM, Paul A Jungwirth wrote:

On Thu, Apr 8, 2021 at 7:22 AM David Steele <david@pgmasters.net> wrote:

Paul, you can submit to the next CF when you are ready with a new patch.

Thanks David! I've made a lot of progress but still need to finish
support for CASCADE on temporal foreign keys. I've been swamped with
other things, but hopefully I can get something during this current
CF.

The next CF starts on July 1 so you have some time.

Regards,
--
-David
david@pgmasters.net

Based on last comments of Paul and David S I am changing the status to
"Waiting on Author".

--
Ibrar Ahmed

#36Paul A Jungwirth
pj@illuminatedcomputing.com
In reply to: Ibrar Ahmed (#35)
Re: SQL:2011 PERIODS vs Postgres Ranges?

On Thu, Jul 15, 2021 at 6:21 AM Ibrar Ahmed <ibrar.ahmad@gmail.com> wrote:

Based on last comments of Paul and David S I am changing the status to "Waiting on Author".

I thought the subject was quite out of date, so I sent my last patch
here: /messages/by-id/CA+renyUApHgSZF9-nd-a0+OPGharLQLO=mDHcY4_qQ0+noCUVg@mail.gmail.com

I also added that thread to the commitfest item.

I'm going to change the commitfest entry back to Needs Review, but
please let me know if you disagree. Sorry for the confusion!

Yours,
Paul