Check constraint on domain over an array not executed for array literals

Lists: pgsql-hackers
From: "Florian G(dot) Pflug" <fgp(at)phlo(dot)org>
To: Postgresql-Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Check constraint on domain over an array not executed for array literals
Date: 2009-11-12 19:02:20
Message-ID: 4AFC5BBC.90202@phlo.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-hackers

Hi

While trying to create a domain over an array type to enforce a certain
shape or certain contents of an array (like the array being only
one-dimensional or not containing NULLs), I've stumbled over what I
believe to be a bug in postgresql 8.4

It seems that check constraints on domains are *not* executed for
literals of the domain-over-array-type - in other words, for expressions
like:
array[...]::<my-domain-over-array-type>.

They are, however, executed if I first force the array to be of the base
type, and then cast it to the array type.

Here is an example that reproduces the problem:
----------------------------------------
create domain myintarray as int[] check (
-- Check that the array is neither null, nor empty,
-- nor multi-dimensional
(value is not null) and
(array_length(value,1) is not null) and
(array_length(value,1) > 0) and
(array_length(value,2) is null)
);

select null::myintarray; -- Fails (Right)

select array[]::myintarray; -- Succeeds (Wrong)
select array[]::int[]::myintarray; -- Fails (Right)

select array[1]::myintarray; -- Succeeds (Right)
select array[1]::int[]::myintarray; -- Succeeds (Right)

select array[array[1]]::myintarray; -- Succeeds (Wrong)
select array[array[1]]::int[][]::myintarray; -- Fails (Right)
----------------------------------------

I guess the reason is that the "::arraytype" part of
"array[...]::arraytype" isn't really a cast at all, but instead part of
the array literal syntax. Hence, array[]::myintarray probably creates an
empty myintarray instance, and then adds the elements between the square
brackets (none) - with none of this steps triggering a run of the check
constraint.

I still have the feeling that this a bug, though. First, because it
leaves you with no way at guarantee that values of a given domain always
fulfill certain constraints. And second because "array[...]::arraytype"
at least *looks* like a cast, and hence should behave like one too.

best regards,
Florian Pflug


From: Heikki Linnakangas <heikki(dot)linnakangas(at)enterprisedb(dot)com>
To: "Florian G(dot) Pflug" <fgp(at)phlo(dot)org>
Cc: Postgresql-Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Check constraint on domain over an array not executed for array literals
Date: 2009-11-13 09:23:43
Message-ID: 4AFD259F.4080602@enterprisedb.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-hackers

Florian G. Pflug wrote:
> While trying to create a domain over an array type to enforce a certain
> shape or certain contents of an array (like the array being only
> one-dimensional or not containing NULLs), I've stumbled over what I
> believe to be a bug in postgresql 8.4
>
> It seems that check constraints on domains are *not* executed for
> literals of the domain-over-array-type - in other words, for expressions
> like:
> array[...]::<my-domain-over-array-type>.
>
> They are, however, executed if I first force the array to be of the base
> type, and then cast it to the array type.
> ...
> I still have the feeling that this a bug, though. First, because it
> leaves you with no way at guarantee that values of a given domain always
> fulfill certain constraints. And second because "array[...]::arraytype"
> at least *looks* like a cast, and hence should behave like one too.

Agreed, it's a bug. A simpler example is just:

postgres=# create domain myintarray as int[] check (value[1] < 10);
CREATE DOMAIN
postgres=# SELECT array['20']::myintarray; -- should fail
array
───────
{20}
(1 row)

There's a special case in transformExpr function to handle the
"ARRAY[...]::arraytype" construct, which skips the usual type-casting
and just constructs an ArrayExpr with the right target type. However,
it's not taking into account that the target type can be a domain.

Attached patch fixes that. Anyone see a problem with it?

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com

Attachment Content-Type Size
array-domain-fix-1.patch text/x-diff 759 bytes

From: "Florian G(dot) Pflug" <fgp(at)phlo(dot)org>
To: Heikki Linnakangas <heikki(dot)linnakangas(at)enterprisedb(dot)com>
Cc: Postgresql-Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Check constraint on domain over an array not executed for array literals
Date: 2009-11-13 15:46:32
Message-ID: 4AFD7F58.3040406@phlo.org
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-hackers

Heikki Linnakangas wrote:
> Agreed, it's a bug. A simpler example is just: [snipped]

Will this fix for this be included in 8.4.2 (or .3), or will it have to
wait for 8.4 because it changes behavior?

> There's a special case in transformExpr function to handle the
> "ARRAY[...]::arraytype" construct, which skips the usual type-casting
> and just constructs an ArrayExpr with the right target type.
> However, it's not taking into account that the target type can be a
> domain.
>
> Attached patch fixes that. Anyone see a problem with it?
I'm not familiar with the parser so I can't really judge this. However,
I've applied the patch to my development db and it seems to work fine,
and fixes the bug.

Thanks for the quick response!

best regards,
Florian Pflug


From: Heikki Linnakangas <heikki(dot)linnakangas(at)enterprisedb(dot)com>
To: "Florian G(dot) Pflug" <fgp(at)phlo(dot)org>
Cc: Postgresql-Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Check constraint on domain over an array not executed for array literals
Date: 2009-11-13 16:11:32
Message-ID: 4AFD8534.4010707@enterprisedb.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-hackers

Florian G. Pflug wrote:
> Heikki Linnakangas wrote:
>> Agreed, it's a bug. A simpler example is just: [snipped]
>
> Will this fix for this be included in 8.4.2 (or .3), or will it have to
> wait for 8.4 because it changes behavior?

It's a regression; 8.3 and earlier used to check the domain constraint
correctly.

I just committed it to CVS HEAD and REL8_4_STABLE. Thanks for the report!

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com


From: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
To: Heikki Linnakangas <heikki(dot)linnakangas(at)enterprisedb(dot)com>
Cc: "Florian G(dot) Pflug" <fgp(at)phlo(dot)org>, Postgresql-Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Check constraint on domain over an array not executed for array literals
Date: 2009-11-13 16:29:06
Message-ID: 3861.1258129746@sss.pgh.pa.us
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-hackers

Heikki Linnakangas <heikki(dot)linnakangas(at)enterprisedb(dot)com> writes:
> Florian G. Pflug wrote:
>> It seems that check constraints on domains are *not* executed for
>> literals of the domain-over-array-type - in other words, for expressions
>> like:
>> array[...]::<my-domain-over-array-type>.

> There's a special case in transformExpr function to handle the
> "ARRAY[...]::arraytype" construct, which skips the usual type-casting
> and just constructs an ArrayExpr with the right target type. However,
> it's not taking into account that the target type can be a domain.

> Attached patch fixes that. Anyone see a problem with it?

Hm. I concur that this special-case code is failing to consider the
possibility that the target type is domain-over-array-type rather than
just array-type. I think though that this patch is a bit of a kluge,
because it delivers a mislabeled expression tree. The result of the
transformArrayExpr() is not really of type myintarray. What it is is
a plain int[] value; we shouldn't label it as being already myintarray,
because it hasn't passed the domain checks. At the moment there is
probably not any visible effect of that, but in the future it could
lead to misoptimization, so I think it's important to get it right.

My inclination is to apply getBaseTypeAndTypmod to the targetType and
pass that as the array type to transformArrayExpr, then instead of
considering the job done, fall through to transformTypeCast, which will
either do nothing or attach a domain coercion node. I'm not sure about
the typmod handling (need more caffeine to work that out).

Do you want to have another go at it, or shall I?

regards, tom lane


From: Heikki Linnakangas <heikki(dot)linnakangas(at)enterprisedb(dot)com>
To: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Cc: "Florian G(dot) Pflug" <fgp(at)phlo(dot)org>, Postgresql-Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Check constraint on domain over an array not executed for array literals
Date: 2009-11-13 17:18:43
Message-ID: 4AFD94F3.4000009@enterprisedb.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-hackers

Tom Lane wrote:
> Hm. I concur that this special-case code is failing to consider the
> possibility that the target type is domain-over-array-type rather than
> just array-type. I think though that this patch is a bit of a kluge,
> because it delivers a mislabeled expression tree. The result of the
> transformArrayExpr() is not really of type myintarray. What it is is
> a plain int[] value; we shouldn't label it as being already myintarray,
> because it hasn't passed the domain checks. At the moment there is
> probably not any visible effect of that, but in the future it could
> lead to misoptimization, so I think it's important to get it right.
>
> My inclination is to apply getBaseTypeAndTypmod to the targetType and
> pass that as the array type to transformArrayExpr, then instead of
> considering the job done, fall through to transformTypeCast, which will
> either do nothing or attach a domain coercion node.

Hmm, yeah that's more accurate.

> I'm not sure about
> the typmod handling (need more caffeine to work that out).

Seems straightforward to me. The ArrayExpr should have the typmod of the
base type, as returned by getBaseTypeAndTypmod, and domains don't have
typmods.

> Do you want to have another go at it, or shall I?

I'll give it a shot.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com


From: Heikki Linnakangas <heikki(dot)linnakangas(at)enterprisedb(dot)com>
To: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Cc: "Florian G(dot) Pflug" <fgp(at)phlo(dot)org>, Postgresql-Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Check constraint on domain over an array not executed for array literals
Date: 2009-11-13 19:50:11
Message-ID: 4AFDB873.8030404@enterprisedb.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Lists: pgsql-hackers

Heikki Linnakangas wrote:
> Tom Lane wrote:
>> Hm. I concur that this special-case code is failing to consider the
>> possibility that the target type is domain-over-array-type rather than
>> just array-type. I think though that this patch is a bit of a kluge,
>> because it delivers a mislabeled expression tree. The result of the
>> transformArrayExpr() is not really of type myintarray. What it is is
>> a plain int[] value; we shouldn't label it as being already myintarray,
>> because it hasn't passed the domain checks. At the moment there is
>> probably not any visible effect of that, but in the future it could
>> lead to misoptimization, so I think it's important to get it right.
>>
>> My inclination is to apply getBaseTypeAndTypmod to the targetType and
>> pass that as the array type to transformArrayExpr, then instead of
>> considering the job done, fall through to transformTypeCast, which will
>> either do nothing or attach a domain coercion node.
>
> Hmm, yeah that's more accurate.

Ok, committed another patch doing exactly that.

--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com