| From: | Paul A Jungwirth <pj(at)illuminatedcomputing(dot)com> |
|---|---|
| To: | jian he <jian(dot)universality(at)gmail(dot)com> |
| Cc: | PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org> |
| Subject: | Re: domain for WITHOUT OVERLAPS |
| Date: | 2025-12-23 03:25:46 |
| Message-ID: | CA+renyW+ia6Uewa5+9f+cgTinndj8wt7yiG=3yZ_FG-hmiHn3w@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
On Wed, Dec 10, 2025 at 8:23 PM jian he <jian(dot)universality(at)gmail(dot)com> wrote:
> polished tests for
> > - WITHOUT OVERLAPS on a rangetype whose subtype has a domain
> > - WITHOUT OVERLAPS on a rangetype with a domain on itself
> > - WITHOUT OVERLAPS on a multirangetype whose subtype has a domain
> > - WITHOUT OVERLAPS on a multirangetype whose rangetype has a domain
> > - WITHOUT OVERLAPS on a multirangetype with a domain on itself
>
> no tests for
> > - WITHOUT OVERLAPS on a multirangetype whose rangetype has a domain
> now.
>
> because the expected behavior assumes that CREATE DOMAIN also generates a
> corresponding multirange type for the domain range type, which is not true.
>
> --example, we expect create domain also create a multirange type for xxint4
> CREATE DOMAIN xxint4 AS int4range CHECK (VALUE <> '[10,11)');
> SELECT typname FROM pg_type WHERE typname ~* 'xxint4';
I'm not sure whether creating a domain on a range should create a
multirange or not. I asked the list on another thread. Since it does
not create a new range constructor either (or an entry in pg_range),
maybe not creating a new multirange is correct. Anyway I think we can
fix the WITHOUT OVERLAPS issue without dealing with that.
> diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
> index 6a347698edf..e27ca1466f2 100644
> --- a/src/backend/utils/cache/typcache.c
> +++ b/src/backend/utils/cache/typcache.c
> @@ -944,6 +944,8 @@ lookup_type_cache(Oid type_id, int flags)
> typentry->domainBaseTypmod = -1;
> typentry->domainBaseType =
> getBaseTypeAndTypmod(type_id, &typentry->domainBaseTypmod);
> + typentry->domainBaseTyptype =
> + get_typtype(typentry->domainBaseType);
> }
> if ((flags & TYPECACHE_DOMAIN_CONSTR_INFO) &&
> (typentry->flags & TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS) == 0 &&
Is there any performance concern about adding this lookup?
From what I can tell we don't use TYPECACHE_DOMAIN_BASE_INFO very
often, so it is probably okay.
We can get here in the executor from ExecEvalWholeRowVar, but that
seems acceptable to me.
> +--
> +-- tests for range over domain, multirange over a domain, custom range type over
> +-- domain.
> +--
> +CREATE DOMAIN int4_d as integer check (value <> 10);
> +CREATE TYPE int4_d_range as range (subtype = int4_d);
> +CREATE DOMAIN int4multirange_d as int4multirange check (value <> '{[10,11)}');
> +CREATE DOMAIN d_int4range1 AS int4range CHECK (VALUE <> '[10,11)');
> +CREATE DOMAIN d_textrange2 AS textrange2 CHECK (VALUE <> '[c,d)');
> +CREATE DOMAIN d_textrange2c AS d_textrange2;
> +CREATE DOMAIN textmultirange2_d AS textmultirange2 CHECK (VALUE <> '{[c,d)}');
The comment doesn't seem to match. It mentions three scenarios, but I'm seeing:
- int4_d_range is a range over a domain.
- int4multirange_d is a domain over a multirange.
- d_int4range1 is a domain over a range.
- d_textrange2 is a domain over a custom range.
- d_textrange2c is a domain over a domain, with no extra constraint.
What is this one for?
- textmultirange2_d is a domain over a custom multirange.
I think the naming is confusing. Sometimes you prepend "d_" and
sometimes you append "_d".
Consistency helps here because I can infer what the name means without
having to look it up.
What do you think of always appending "_d"?
Why "d_int4range1"? I don't see a "d_int4range2".
> +CREATE TABLE temporal_rng4mrng (
> + id d_int4range1,
> + valid_at int4multirange_d,
> + CONSTRAINT temporal_rng4_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
> +);
> +INSERT INTO temporal_rng4mrng VALUES ('[1,2)', '{[10,11)}'); --error
> +ERROR: value for domain int4multirange_d violates check constraint "int4multirange_d_check"
> +INSERT INTO temporal_rng4mrng VALUES ('[1,2)', '{[10,13)}'), ('[1,2)', '{[2,13)}'); --error
> +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), {[10,13)}).
> +DROP TABLE temporal_rng4mrng;
Okay, so we test a domain over a multirange here.
I don't understand the table name temporal_rng4mrng. It might be a
typo for temporal_mltrng4?
> +CREATE TABLE temporal_rng4 (
> + id d_int4range1,
> + id1 int4_d_range,
> + valid_at d_textrange2c,
> + CONSTRAINT temporal_rng4_pk PRIMARY KEY (id, id1, valid_at WITHOUT OVERLAPS)
> +);
> +INSERT INTO temporal_rng4 VALUES ('[1,2)', '[1,2)', '[c,d)'); --error
> +ERROR: value for domain d_textrange2c violates check constraint "d_textrange2_check"
> +INSERT INTO temporal_rng4 VALUES ('[1,2)', '[10,12)', '[a,g)'); --error
> +ERROR: value for domain int4_d violates check constraint "int4_d_check"
> +LINE 1: INSERT INTO temporal_rng4 VALUES ('[1,2)', '[10,12)', '[a,g)...
> + ^
Here we test a domain over a custom rangetype (d_textrange2c).
Does using domains for the non-WITHOUT OVERLAPS parts add anything to this test?
Or should we have separate tests where those are in the WITHOUT
OVERLAPS position?
We get a line number for the second failure above but not the first.
Is that something we can fix?
> +CREATE TABLE temporal_rngfk (
> + parent_id d_int4range1,
> + id int4range,
> + id1 int4_d_range,
> + valid_at d_textrange2);
> +ALTER TABLE temporal_rngfk
> + ADD CONSTRAINT temporal_rngfk_fk
> + FOREIGN KEY (parent_id, id1, PERIOD valid_at)
> + REFERENCES temporal_rng4;
> +INSERT INTO temporal_rngfk VALUES ('[1,2)', '[2,3)', '[9,11)', '[d,e)');
> +TABLE temporal_rng4;
> + id | id1 | valid_at
> +-------+--------+----------
> + [1,2) | [9,11) | [a,g)
> +(1 row)
> +
> +TABLE temporal_rngfk;
> + parent_id | id | id1 | valid_at
> +-----------+-------+--------+----------
> + [1,2) | [2,3) | [9,11) | [d,e)
> +(1 row)
> +
> +UPDATE temporal_rng4 SET valid_at = '[c,d)'; --error
> +ERROR: value for domain d_textrange2c violates check constraint "d_textrange2_check"
> +UPDATE temporal_rng4 SET valid_at = '[a,h)';
> +UPDATE temporal_rng4 SET valid_at = '[f,g)'; --error
> +ERROR: update or delete on table "temporal_rng4" violates foreign key constraint "temporal_rngfk_fk" on table "temporal_rngfk"
> +DETAIL: Key (id, id1, valid_at)=([1,2), [9,11), [a,h)) is still referenced from table "temporal_rngfk".
> +DROP TABLE temporal_rng4, temporal_rngfk;
Here we test foreign keys with a domain over a custom rangetype.
Again I'm not sure it adds anything to add int4_d_range.
What about a domain referencing a non-domain and a non-domain
referencing a domain (in the WITHOUT OVERLAPS position)?
We allow that for regular FK parts, so we should allow it for the
WITHOUT OVERLAPS part as well.
> +-- domain on a multirange
> +CREATE TABLE temporal_mltrng4 (
> + id d_int4range1,
> + valid_at textmultirange2_d,
> + CONSTRAINT temporal_rng4_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS)
> +);
> +INSERT INTO temporal_mltrng4 VALUES ('[1,2)', '{[c,d)}'); --error
> +ERROR: value for domain textmultirange2_d violates check constraint "textmultirange2_d_check"
> +INSERT INTO temporal_mltrng4 VALUES ('[1,2)', '{[a,g)}');
> +INSERT INTO temporal_mltrng4 VALUES ('[1,2)', '{[b,c)}'); --error
> +ERROR: conflicting key value violates exclusion constraint "temporal_rng4_pk"
> +DETAIL: Key (id, valid_at)=([1,2), {[b,c)}) conflicts with existing key (id, valid_at)=([1,2), {[a,g)}).
> +INSERT INTO temporal_mltrng4 VALUES ('[2,3)', '{[B,C)}'), ('[2,3)', '{[A,C)}'); --error
> +ERROR: conflicting key value violates exclusion constraint "temporal_rng4_pk"
> +DETAIL: Key (id, valid_at)=([2,3), {[A,C)}) conflicts with existing key (id, valid_at)=([2,3), {[B,C)}).
> +CREATE TABLE temporal_mltrngfk (parent_id d_int4range1, id int4range,
> +valid_at textmultirange2_d);
> +ALTER TABLE temporal_mltrngfk
> + ADD CONSTRAINT temporal_mltrngfk_fk
> + FOREIGN KEY (parent_id, PERIOD valid_at)
> + REFERENCES temporal_mltrng4;
> +INSERT INTO temporal_mltrngfk VALUES ('[1,2)', '[2,3)', '{[d,e)}');
> +TABLE temporal_mltrng4;
> + id | valid_at
> +-------+----------
> + [1,2) | {[a,g)}
> +(1 row)
> +
> +TABLE temporal_mltrngfk;
> + parent_id | id | valid_at
> +-----------+-------+----------
> + [1,2) | [2,3) | {[d,e)}
> +(1 row)
> +
> +UPDATE temporal_mltrng4 SET valid_at = '{[c,d)}'; --error
> +ERROR: value for domain textmultirange2_d violates check constraint "textmultirange2_d_check"
> +UPDATE temporal_mltrng4 SET valid_at = '{[a,h)}';
> +UPDATE temporal_mltrng4 SET valid_at = '{[f,g)}'; --error
> +ERROR: update or delete on table "temporal_mltrng4" violates foreign key constraint "temporal_mltrngfk_fk" on table "temporal_mltrngfk"
> +DETAIL: Key (id, valid_at)=([1,2), {[a,h)}) is still referenced from table "temporal_mltrngfk".
> +DROP TABLE temporal_mltrng4, temporal_mltrngfk;
This is similar to the test above using table temporal_rng4mrng.
The only difference is that the multirange uses a custom range type.
That's great, but maybe the comment can express that, and the two test
groups could be adjacent?
> @@ -309,6 +419,27 @@ CREATE TABLE temporal_rng3 (
> );
> ALTER TABLE temporal_rng3 DROP CONSTRAINT temporal_rng3_uq;
> DROP TABLE temporal_rng3;
> +CREATE TABLE temporal_rng4 (
> + id d_int4range1,
> + valid_at d_textrange2c,
> + CONSTRAINT temporal_rng4_pk UNIQUE (id, valid_at WITHOUT OVERLAPS)
> +);
> +INSERT INTO temporal_rng4 VALUES ('[1,2)', NULL), (NULL, '[1,2)');
> +INSERT INTO temporal_rng4 VALUES ('[1,2)', '[c,d)'); --error
> +ERROR: value for domain d_textrange2c violates check constraint "d_textrange2_check"
> +INSERT INTO temporal_rng4 VALUES ('[1,2)', '[a,d)');
> +INSERT INTO temporal_rng4 VALUES ('[1,2)', '[b,c)'); --error
> +ERROR: conflicting key value violates exclusion constraint "temporal_rng4_pk"
> +DETAIL: Key (id, valid_at)=([1,2), [b,c)) conflicts with existing key (id, valid_at)=([1,2), [a,d)).
> +INSERT INTO temporal_rng4 VALUES ('[10,11)', NULL); --error
> +ERROR: value for domain d_int4range1 violates check constraint "d_int4range1_check"
> +INSERT INTO temporal_rng4 VALUES ('[2,3)', '[B,C)'), ('[2,3)', '[A,C)'); --error
> +ERROR: conflicting key value violates exclusion constraint "temporal_rng4_pk"
> +DETAIL: Key (id, valid_at)=([2,3), [A,C)) conflicts with existing key (id, valid_at)=([2,3), [B,C)).
> +DROP TABLE temporal_rng4;
> +DROP TYPE int4_d_range;
> +DROP DOMAIN d_int4range1, d_textrange2c, d_textrange2,
> + textmultirange2_d, int4multirange_d, int4_d;
> DROP TYPE textrange2;
> --
> -- test ALTER TABLE ADD CONSTRAINT
Here we test a domain over a custom rangetype. Great! But again a
comment explaining the intent would be good.
The order of the new test groups seems odd to me. I would have
expected to go from simple to complex, e.g. domain over range, domain
over custom range, domain over multirange, domain over custom
multirange.
Also testing a range over a domain (in WITHOUT OVERLAPS position) would be good.
Yours,
--
Paul ~{:-)
pj(at)illuminatedcomputing(dot)com
| From | Date | Subject | |
|---|---|---|---|
| Next Message | zengman | 2025-12-23 03:39:14 | Re: Refactor query normalization into core query jumbling |
| Previous Message | cca5507 | 2025-12-23 03:04:55 | Re: Why is_admin_of_role() use ROLERECURSE_MEMBERS rather than ROLERECURSE_PRIVS? |