]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Fix WITHOUT OVERLAPS' interaction with domains.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 7 Apr 2026 18:45:33 +0000 (14:45 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 7 Apr 2026 18:45:37 +0000 (14:45 -0400)
UNIQUE/PRIMARY KEY ... WITHOUT OVERLAPS requires the no-overlap
column to be a range or multirange, but it should allow a domain
over such a type too.  This requires minor adjustments in both
the parser and executor.

In passing, fix a nearby break-instead-of-continue thinko in
transformIndexConstraint.  This had the effect of disabling
parse-time validation of the no-overlap column's type in the context
of ALTER TABLE ADD CONSTRAINT, if it follows a dropped column.
We'd still complain appropriately at runtime though.

Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Paul A Jungwirth <pj@illuminatedcomputing.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/CACJufxGoAmN_0iJ=hjTG0vGpOSOyy-vYyfE+-q0AWxrq2_p5XQ@mail.gmail.com
Backpatch-through: 18

src/backend/executor/execIndexing.c
src/backend/parser/parse_utilcmd.c
src/test/regress/expected/without_overlaps.out
src/test/regress/sql/without_overlaps.sql

index 4363e154c0f27376c9b2199f102f6c3c9b0cd152..eb383812901aad56f22e56453543687772745c40 100644 (file)
 #include "nodes/nodeFuncs.h"
 #include "storage/lmgr.h"
 #include "utils/injection_point.h"
+#include "utils/lsyscache.h"
 #include "utils/multirangetypes.h"
 #include "utils/rangetypes.h"
 #include "utils/snapmgr.h"
@@ -753,11 +754,18 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
                {
                        TupleDesc       tupdesc = RelationGetDescr(heap);
                        Form_pg_attribute att = TupleDescAttr(tupdesc, attno - 1);
-                       TypeCacheEntry *typcache = lookup_type_cache(att->atttypid, 0);
+                       TypeCacheEntry *typcache = lookup_type_cache(att->atttypid,
+                                                                                                                TYPECACHE_DOMAIN_BASE_INFO);
+                       char            typtype;
+
+                       if (OidIsValid(typcache->domainBaseType))
+                               typtype = get_typtype(typcache->domainBaseType);
+                       else
+                               typtype = typcache->typtype;
 
                        ExecWithoutOverlapsNotEmpty(heap, att->attname,
                                                                                values[indnkeyatts - 1],
-                                                                               typcache->typtype, att->atttypid);
+                                                                               typtype, att->atttypid);
                }
        }
 
index e135c91a6adfc923567d68aaea3af9a37c04d505..347f0259e5d584c3c2864b7db18717e54633593e 100644 (file)
@@ -2760,7 +2760,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
 
                        /*
                         * The WITHOUT OVERLAPS part (if any) must be a range or
-                        * multirange type.
+                        * multirange type, or a domain over such a type.
                         */
                        if (constraint->without_overlaps && lc == list_last_cell(constraint->keys))
                        {
@@ -2778,8 +2778,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
                                                const char *attname;
 
                                                if (attr->attisdropped)
-                                                       break;
-
+                                                       continue;
                                                attname = NameStr(attr->attname);
                                                if (strcmp(attname, key) == 0)
                                                {
@@ -2791,10 +2790,16 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
                                }
                                if (found)
                                {
+                                       /* Look up column type if we didn't already */
                                        if (!OidIsValid(typid) && column)
-                                               typid = typenameTypeId(NULL, column->typeName);
-
-                                       if (!OidIsValid(typid) || !(type_is_range(typid) || type_is_multirange(typid)))
+                                               typid = typenameTypeId(cxt->pstate,
+                                                                                          column->typeName);
+                                       /* Look through any domain */
+                                       if (OidIsValid(typid))
+                                               typid = getBaseType(typid);
+                                       /* Complain if not range/multirange */
+                                       if (!OidIsValid(typid) ||
+                                               !(type_is_range(typid) || type_is_multirange(typid)))
                                                ereport(ERROR,
                                                                (errcode(ERRCODE_DATATYPE_MISMATCH),
                                                                 errmsg("column \"%s\" in WITHOUT OVERLAPS is not a range or multirange type", key),
index 6f145b27c4dd9953fa758a5d18a67c746f977c01..de2f8bc4786f2d04bcbf3dfd0707d6de127dcf07 100644 (file)
@@ -314,6 +314,45 @@ ALTER TABLE temporal_rng3 DROP CONSTRAINT temporal_rng3_uq;
 DROP TABLE temporal_rng3;
 DROP TYPE textrange2;
 --
+-- test PRIMARY KEY and UNIQUE constraints' interaction with domains
+--
+-- range over domain:
+CREATE DOMAIN int4_d as integer check (value <> 10);
+CREATE TYPE int4_d_range as range (subtype = int4_d);
+CREATE TABLE temporal_rng4 (
+  id int4range,
+  valid_at int4_d_range,
+  CONSTRAINT temporal_rng4_pk PRIMARY KEY(id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO temporal_rng4 VALUES ('[1,11)', '[9,10)'); -- start bound violates domain
+ERROR:  value for domain int4_d violates check constraint "int4_d_check"
+LINE 1: INSERT INTO temporal_rng4 VALUES ('[1,11)', '[9,10)');
+                                                    ^
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[10,11)'); -- end bound violates domain
+ERROR:  value for domain int4_d violates check constraint "int4_d_check"
+LINE 1: INSERT INTO temporal_rng4 VALUES ('[1,2)', '[10,11)');
+                                                   ^
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,13)'), ('[1,2)', '[2,5)'); -- overlaps
+ERROR:  conflicting key value violates exclusion constraint "temporal_rng4_pk"
+DETAIL:  Key (id, valid_at)=([1,2), [2,5)) conflicts with existing key (id, valid_at)=([1,2), [1,13)).
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,13)'), ('[1,2)', '[20,23)'); -- okay
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[30,)'); -- null bound is okay
+DROP TABLE temporal_rng4;
+-- domain over range:
+CREATE DOMAIN int4range_d AS int4range CHECK (VALUE <> '[10,11)');
+CREATE TABLE temporal_rng4 (
+  id int4range,
+  valid_at int4range_d,
+  CONSTRAINT temporal_rng4_pk UNIQUE (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[10,11)'); -- violates domain
+ERROR:  value for domain int4range_d violates check constraint "int4range_d_check"
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,13)'), ('[1,2)', '[2,13)'); -- overlaps
+ERROR:  conflicting key value violates exclusion constraint "temporal_rng4_pk"
+DETAIL:  Key (id, valid_at)=([1,2), [2,13)) conflicts with existing key (id, valid_at)=([1,2), [1,13)).
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,13)'), ('[1,2)', '[20,23)'); -- okay
+DROP TABLE temporal_rng4;
+--
 -- test ALTER TABLE ADD CONSTRAINT
 --
 CREATE TABLE temporal_rng (
index b15679d675eab697b910ac53c6db63080433b352..4833b8ac5f07be208265a69e144e64cad995099c 100644 (file)
@@ -180,6 +180,37 @@ ALTER TABLE temporal_rng3 DROP CONSTRAINT temporal_rng3_uq;
 DROP TABLE temporal_rng3;
 DROP TYPE textrange2;
 
+--
+-- test PRIMARY KEY and UNIQUE constraints' interaction with domains
+--
+
+-- range over domain:
+CREATE DOMAIN int4_d as integer check (value <> 10);
+CREATE TYPE int4_d_range as range (subtype = int4_d);
+CREATE TABLE temporal_rng4 (
+  id int4range,
+  valid_at int4_d_range,
+  CONSTRAINT temporal_rng4_pk PRIMARY KEY(id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO temporal_rng4 VALUES ('[1,11)', '[9,10)'); -- start bound violates domain
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[10,11)'); -- end bound violates domain
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,13)'), ('[1,2)', '[2,5)'); -- overlaps
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,13)'), ('[1,2)', '[20,23)'); -- okay
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[30,)'); -- null bound is okay
+DROP TABLE temporal_rng4;
+
+-- domain over range:
+CREATE DOMAIN int4range_d AS int4range CHECK (VALUE <> '[10,11)');
+CREATE TABLE temporal_rng4 (
+  id int4range,
+  valid_at int4range_d,
+  CONSTRAINT temporal_rng4_pk UNIQUE (id, valid_at WITHOUT OVERLAPS)
+);
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[10,11)'); -- violates domain
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,13)'), ('[1,2)', '[2,13)'); -- overlaps
+INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,13)'), ('[1,2)', '[20,23)'); -- okay
+DROP TABLE temporal_rng4;
+
 --
 -- test ALTER TABLE ADD CONSTRAINT
 --