Thread: BUG #6701: IS NOT NULL doesn't work on complex composites

BUG #6701: IS NOT NULL doesn't work on complex composites

From
rikard.pavelic@zg.htnet.hr
Date:
The following bug has been logged on the website:

Bug reference:      6701
Logged by:          Rikard Pavelic
Email address:      rikard.pavelic@zg.htnet.hr
PostgreSQL version: 9.1.3
Operating system:   Windows 7
Description:=20=20=20=20=20=20=20=20

create type t AS (i int);
create type complex as (t t, i int);
create table bad(i int, c complex);

insert into bad values(1, null);
insert into bad values(1, ROW(null, 2));
insert into bad values(1, ROW(ROW(1), 2));

select * from bad;
select * from bad where c is null;

--This doesn't work as expected
select * from bad where c is not null;

--Fortunately a workaround
select * from bad where NOT c is null;

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Pavel Stehule
Date:
Hello

it is not a bug - see
http://archives.postgresql.org/pgsql-hackers/2009-07/msg01525.php

Regards

Pavel Stehule

2012/6/20  <rikard.pavelic@zg.htnet.hr>:
> The following bug has been logged on the website:
>
> Bug reference: =C2=A0 =C2=A0 =C2=A06701
> Logged by: =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Rikard Pavelic
> Email address: =C2=A0 =C2=A0 =C2=A0rikard.pavelic@zg.htnet.hr
> PostgreSQL version: 9.1.3
> Operating system: =C2=A0 Windows 7
> Description:
>
> create type t AS (i int);
> create type complex as (t t, i int);
> create table bad(i int, c complex);
>
> insert into bad values(1, null);
> insert into bad values(1, ROW(null, 2));
> insert into bad values(1, ROW(ROW(1), 2));
>
> select * from bad;
> select * from bad where c is null;
>
> --This doesn't work as expected
> select * from bad where c is not null;
>
> --Fortunately a workaround
> select * from bad where NOT c is null;
>
>
>
> --
> Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-bugs

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
"Kevin Grittner"
Date:
<rikard.pavelic@zg.htnet.hr> wrote:

> --This doesn't work as expected
> select * from bad where c is not null;

Are you seeing any behavior which does not match the documentation
and the standard?

http://www.postgresql.org/docs/current/interactive/functions-comparison.html

says:

| Note: If the expression is row-valued, then IS NULL is true when
| the row expression itself is null or when all the row's fields are
| null, while IS NOT NULL is true when the row expression itself is
| non-null and all the row's fields are non-null. Because of this
| behavior, IS NULL and IS NOT NULL do not always return inverse
| results for row-valued expressions, i.e., a row-valued expression
| that contains both NULL and non-null values will return false for
| both tests. This definition conforms to the SQL standard, and is a
| change from the inconsistent behavior exhibited by PostgreSQL
| versions prior to 8.2.

When using a NULL test with a row-value, it can help to imagine the
word "ENTIRELY" right after the word IS.  The above query will only
return a row from "bad" if the row value "c" IS [ENTIRELY] NOT NULL
-- in other words, any NULL in the row causes it to be excluded.
Moving the NOT in front of the IS results in a test for rows from
"bad" where it is NOT true that row value "c" IS [ENTIRELY] NULL.

That works for me, anyway.  Some find the rules around NULL
illogical and argue for just memorizing them as a set of facts
rather than trying to make sense of them.

-Kevin

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Tom Lane
Date:
rikard.pavelic@zg.htnet.hr writes:
> create type t AS (i int);
> create type complex as (t t, i int);
> create table bad(i int, c complex);

> insert into bad values(1, null);
> insert into bad values(1, ROW(null, 2));
> insert into bad values(1, ROW(ROW(1), 2));

> select * from bad;
> select * from bad where c is null;

> --This doesn't work as expected
> select * from bad where c is not null;

What do you consider to be "expected"?  Have you read the documentation
where it points out that IS NULL and IS NOT NULL are not inverses for
composite values?
http://www.postgresql.org/docs/9.1/static/functions-comparison.html

(I'm not that thrilled with this behavior either, but it is per SQL
standard AFAICT.)

            regards, tom lane

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Rikard Pavelic
Date:
On 20.6.2012. 20:55, Pavel Stehule wrote:
> Hello
>
> it is not a bug - see
> http://archives.postgresql.org/pgsql-hackers/2009-07/msg01525.php
>
> Regards
>
> Pavel Stehule
>

I found that in documentation after reporting bug.
I'm sorry for not searching some more, but didn't know where to look exactly.

Well, at least is counter intuitive if not a bug ;(

Thanks.

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Pavel Stehule
Date:
2012/6/20 Rikard Pavelic <rikard.pavelic@zg.htnet.hr>:
> On 20.6.2012. 20:55, Pavel Stehule wrote:
>> Hello
>>
>> it is not a bug - see
>> http://archives.postgresql.org/pgsql-hackers/2009-07/msg01525.php
>>
>> Regards
>>
>> Pavel Stehule
>>
>
> I found that in documentation after reporting bug.
> I'm sorry for not searching some more, but didn't know where to look exactly.
>
> Well, at least is counter intuitive if not a bug ;(

I had a problem with this issue too, but it is well designed - I believe now.

Regards

Pavel


>
> Thanks.

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Rikard Pavelic
Date:
On 20.6.2012. 21:10, Tom Lane wrote:
> rikard.pavelic@zg.htnet.hr writes:
>> create type t AS (i int); create type complex as (t t, i int); create table bad(i int, c complex); --This doesn't
workas expected select * from bad where c is not null;  
> What do you consider to be "expected"? Have you read the documentation where it points out that IS NULL and IS NOT
NULLare not inverses for composite values? 
> http://www.postgresql.org/docs/9.1/static/functions-comparison.html (I'm not that thrilled with this behavior either,
butit is per SQL standard AFAICT.) regards, tom lane 

I understand the concept behind if one composite property is null then then IS NULL check returns NULL (instead true or
false).
I can even understand IS NULL check returning false.

I can use ::text to get what I expected,
but Postgres still seems inconsistent in handling NULL checks.

create type complex as (i int, j int);
create table t (i int, c complex not null);

--error as expected
insert into t values(1, null);

--unexpected - passed!?
insert into t values(1, (null,4));

-- this is false - I think it would be better if it's null, but lets move on
select (null, 4) is not null

--lets try again with check constraint
alter table t add check(c is not null);

--error as expected from is not null check above
insert into t values(1, (null,4));

It seems that check constraint behaves differently.
Docs say: (http://www.postgresql.org/docs/9.1/static/ddl-constraints.html)
"A not-null constraint is functionally equivalent to creating a check constraint CHECK (column_name IS NOT NULL)"
And at least there is more notes required ;(

Let's try some more.

create table x (i int, c complex);

insert into x values(1,null);
insert into x values(2,(1,null));
insert into x values(3,(1,2));

--first row - ok
select * from x where c is null;

--last row - ok
select * from x where c is not null;

--unexpected result again
select c is null from x;

I must admit I was expecting
true
null
false

Regards,
Rikard

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Pavel Stehule
Date:
2012/6/21 Rikard Pavelic <rikard.pavelic@zg.htnet.hr>:
> On 20.6.2012. 21:10, Tom Lane wrote:
>> rikard.pavelic@zg.htnet.hr writes:
>>> create type t AS (i int); create type complex as (t t, i int); create table bad(i int, c complex); --This doesn't
workas expected select * from bad where c is not null; 
>> What do you consider to be "expected"? Have you read the documentation where it points out that IS NULL and IS NOT
NULLare not inverses for composite values? 
>> http://www.postgresql.org/docs/9.1/static/functions-comparison.html (I'm not that thrilled with this behavior
either,but it is per SQL standard AFAICT.) regards, tom lane 
>
> I understand the concept behind if one composite property is null then then IS NULL check returns NULL (instead true
orfalse). 
> I can even understand IS NULL check returning false.
>
> I can use ::text to get what I expected,
> but Postgres still seems inconsistent in handling NULL checks.
>
> create type complex as (i int, j int);
> create table t (i int, c complex not null);
>
> --error as expected
> insert into t values(1, null);
>
> --unexpected - passed!?
> insert into t values(1, (null,4));
>
> -- this is false - I think it would be better if it's null, but lets move on
> select (null, 4) is not null
>
> --lets try again with check constraint
> alter table t add check(c is not null);
>
> --error as expected from is not null check above
> insert into t values(1, (null,4));
>
> It seems that check constraint behaves differently.
> Docs say: (http://www.postgresql.org/docs/9.1/static/ddl-constraints.html)
> "A not-null constraint is functionally equivalent to creating a check constraint CHECK (column_name IS NOT NULL)"
> And at least there is more notes required ;(
>
> Let's try some more.
>
> create table x (i int, c complex);
>
> insert into x values(1,null);
> insert into x values(2,(1,null));
> insert into x values(3,(1,2));
>
> --first row - ok
> select * from x where c is null;
>
> --last row - ok
> select * from x where c is not null;
>
> --unexpected result again
> select c is null from x;
>
> I must admit I was expecting
> true
> null
> false

but C is not one value - it is composite - and composite in SQL is not
pointer like C or C++, but it is list of values - and composite is
null (list is null) when all fields are null.

Regards

Pavel

>
> Regards,
> Rikard
>
> --
> Sent via pgsql-bugs mailing list (pgsql-bugs@postgresql.org)
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgsql-bugs

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Rikard Pavelic
Date:
On 21.6.2012. 6:03, Pavel Stehule wrote:
> 2012/6/21 Rikard Pavelic <rikard.pavelic@zg.htnet.hr>:
>> On 20.6.2012. 21:10, Tom Lane wrote:
>>> rikard.pavelic@zg.htnet.hr writes:
>>>> create type t AS (i int); create type complex as (t t, i int); create table bad(i int, c complex); --This doesn't
workas expected select * from bad where c is not null; 
>>> What do you consider to be "expected"? Have you read the documentation where it points out that IS NULL and IS NOT
NULLare not inverses for composite values? 
>>> http://www.postgresql.org/docs/9.1/static/functions-comparison.html (I'm not that thrilled with this behavior
either,but it is per SQL standard AFAICT.) regards, tom lane 
>> I understand the concept behind if one composite property is null then then IS NULL check returns NULL (instead true
orfalse). 
>> I can even understand IS NULL check returning false.
>>
>> I can use ::text to get what I expected,
>> but Postgres still seems inconsistent in handling NULL checks.
>>
>> create type complex as (i int, j int);
>> create table t (i int, c complex not null);
>>
>> --error as expected
>> insert into t values(1, null);
>>
>> --unexpected - passed!?
>> insert into t values(1, (null,4));
>>
>> -- this is false - I think it would be better if it's null, but lets move on
>> select (null, 4) is not null
>>
>> --lets try again with check constraint
>> alter table t add check(c is not null);
>>
>> --error as expected from is not null check above
>> insert into t values(1, (null,4));
>>
>> It seems that check constraint behaves differently.
>> Docs say: (http://www.postgresql.org/docs/9.1/static/ddl-constraints.html)
>> "A not-null constraint is functionally equivalent to creating a check constraint CHECK (column_name IS NOT NULL)"
>> And at least there is more notes required ;(
>>
>> Let's try some more.
>>
>> create table x (i int, c complex);
>>
>> insert into x values(1,null);
>> insert into x values(2,(1,null));
>> insert into x values(3,(1,2));
>>
>> --first row - ok
>> select * from x where c is null;
>>
>> --last row - ok
>> select * from x where c is not null;
>>
>> --unexpected result again
>> select c is null from x;
>>
>> I must admit I was expecting
>> true
>> null
>> false
> but C is not one value - it is composite - and composite in SQL is not
> pointer like C or C++, but it is list of values - and composite is
> null (list is null) when all fields are null.
>
> Regards
>
> Pavel
>

Yeah, I said I'm fine with this behavior.
The only inconsistent thing is check constraint, which behaves as
NOT column IS NULL instead of column IS NOT NULL as docs says.
I even prefer that behavior.

Thanks,
Rikard

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
"Kevin Grittner"
Date:
Rikard Pavelic <rikard.pavelic@zg.htnet.hr> wrote:

> The only inconsistent thing is check constraint, which behaves as
> NOT column IS NULL instead of column IS NOT NULL as docs says.

So currently a NOT NULL constraint on a column with a composite type
is equivalent to:

CHECK (NOT c IS NULL)

and the question is whether that is correct, or whether it should be
equivalent to:

CHECK (c IS NOT NULL)

> I even prefer that behavior.

I think I prefer current behavior, too; but I'm inclined to be
guided by the SQL spec if it is unambiguous about which is correct.
(I haven't checked yet -- does anyone already know without having to
dig through the spec?)  Either way, it probably deserves some brief
mention in the docs.  FWIW, a strict reading of the current
PostgreSQL docs ("The column is not allowed to contain null
values.") matches the current behavior, since the other way would
need to be stated as something like "The column can only contain
non-null values."

-Kevin

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Tom Lane
Date:
"Kevin Grittner" <Kevin.Grittner@wicourts.gov> writes:
> Rikard Pavelic <rikard.pavelic@zg.htnet.hr> wrote:
>> The only inconsistent thing is check constraint, which behaves as
>> NOT column IS NULL instead of column IS NOT NULL as docs says.

> So currently a NOT NULL constraint on a column with a composite type
> is equivalent to:
> CHECK (NOT c IS NULL)

I don't believe this statement is accurate.  What's really happening
is that a column-not-null constraint is a datatype-independent check
for whether the datum per se is null or not.  In the case of a composite
column, it's possible that the datum is a heaptuple all of whose fields
are null.  IS NULL will say "true" for such a value, per SQL spec, but
the attnotnull code will not reject it.  So actually the attnotnull
check doesn't exactly correspond to either IS NOT NULL or NOT IS NULL,
when you're talking about composite types.

There are two ways we could make that more consistent:

1. Force all-null heaptuple datums to become real nulls.  This is not
terribly attractive IMV; for one thing it loses any opportunity to carry
the rowtype's OID, which is something I think we need at least in some
contexts.  We could narrow the scope for such problems by delaying the
application of the conversion until storage time, but then it would have
to be checked in places that now are datatype-independent, which is
unpleasant from both modularity and performance standpoints.  Another
issue is that, while the spec seems not to distinguish between NULL and
ROW(NULL,NULL,...), it is far from clear that we should seek to suppress
the difference.  They are different in I/O representation for instance.

2. Change the attnotnull checking code to be datatype-dependent
so that it could peer into a composite value to check the field values.
This is unpleasant for the same modularity and performance reasons
mentioned above.

We've been over this ground before, and not come to any consensus about
changing the behavior.  Somebody who really cares about having the
SQL-spec definition can write a CHECK constraint as suggested above,
and then he'll get the composite-type-aware behavior, so it's not like
there's no way to get that.

BTW, the same inconsistency exists for function-argument strictness
checks: those will consider a heaptuple-of-all-nulls to be something
you can call a strict function with.  I think changing this would be
a pretty bad idea, not only on modularity and performance grounds but
because it'd likely break existing applications that expect the current
behavior.

Here's another interesting example, using int8_tbl which is just
a two-column composite type:

regression=# select null::int8_tbl;
 int8_tbl
----------

(1 row)

regression=# select row(null,null)::int8_tbl;
 row
-----
 (,)
(1 row)

regression=# select null::int8_tbl is distinct from row(null,null)::int8_tbl;
 ?column?
----------
 t
(1 row)

It's not clear to me whether the SQL standard rules on what should
happen in this case, or whether we should listen to it if it does say
that these values are not distinct.  They certainly *look* distinct.

(Oh, and dare I mention arrays of nulls?)

            regards, tom lane

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
"Kevin Grittner"
Date:
Tom Lane <tgl@sss.pgh.pa.us> wrote:
> "Kevin Grittner" <Kevin.Grittner@wicourts.gov> writes:

>> So currently a NOT NULL constraint on a column with a composite
>> type is equivalent to:
>> CHECK (NOT c IS NULL)
>
> I don't believe this statement is accurate.  What's really
> happening is that a column-not-null constraint is a
> datatype-independent check for whether the datum per se is null or
> not.

I stand corrected.

> Somebody who really cares about having the SQL-spec definition can
> write a CHECK constraint as suggested above, and then he'll get
> the composite-type-aware behavior, so it's not like there's no way
> to get that.
>
> BTW, the same inconsistency exists for function-argument
> strictness checks: those will consider a heaptuple-of-all-nulls to
> be something you can call a strict function with.  I think
> changing this would be a pretty bad idea, not only on modularity
> and performance grounds but because it'd likely break existing
> applications that expect the current behavior.

Maybe a comment or two in the docs covers it?

> regression=# select null::int8_tbl is distinct from
> row(null,null)::int8_tbl;
>  ?column?
> ----------
>  t
> (1 row)
>
> It's not clear to me whether the SQL standard rules on what should
> happen in this case, or whether we should listen to it if it does
> say that these values are not distinct.  They certainly *look*
> distinct.

I do sympathize with the point of view that a row value about which
absolutely no applicable facts are known is a lot like not knowing
what row you have, but they do seem distinct when you look at the
output.

> (Oh, and dare I mention arrays of nulls?)

Hey, look!  An elephant!

-Kevin

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
Tom Lane
Date:
"Kevin Grittner" <Kevin.Grittner@wicourts.gov> writes:
> Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> It's not clear to me whether the SQL standard rules on what should
>> happen in this case, or whether we should listen to it if it does
>> say that these values are not distinct.  They certainly *look*
>> distinct.

> I do sympathize with the point of view that a row value about which
> absolutely no applicable facts are known is a lot like not knowing
> what row you have, but they do seem distinct when you look at the
> output.

>> (Oh, and dare I mention arrays of nulls?)

> Hey, look!  An elephant!

The reason I mentioned arrays is that it seems clear to me that nobody
sane would consider ARRAY[NULL,NULL]::int[] to be equivalent to
NULL::int[].  The former has got well-defined array dimensions, for one
thing, while the latter does not.  So I think the standard is not being
very bright by conflating a null container with a container full of
nulls in the ROW case.  I'm willing to hold my nose and do what they say
for the specific case of "foo IS NULL" and "foo IS NOT NULL"
expressions, but I'm not eager to let that confusion propagate anyplace
else.

            regards, tom lane

Re: BUG #6701: IS NOT NULL doesn't work on complex composites

From
"Kevin Grittner"
Date:
Tom Lane <tgl@sss.pgh.pa.us> wrote:

> The reason I mentioned arrays is that it seems clear to me that
> nobody sane would consider ARRAY[NULL,NULL]::int[] to be
> equivalent to NULL::int[].

I will defer on that to anyone who has been in a position where the
former has any meaningful semantics in a SQL environment; that is a
set of people which does not include me.

> So I think the standard is not being very bright by conflating a
> null container with a container full of nulls in the ROW case.

I'm pretty sure that Codd argued that any attempt to create such a
row within a relation should be treated as an error; I tend to
agree.  I'm far less clear on the semantics of a row value which is
not a top-level tuple of a relation.  That seems like it's in a gray
area between set theory and practical details of procedural
programming.  There are bound to be a few things in that realm which
don't make sense when viewed from one perspective or the other.
While I might not have chosen to draw the lines where the standard
does, they did make choices that allow reasonable things to be done
in a wide variety of cases, which is, IMO, the most important thing.

> I'm willing to hold my nose and do what they say for the specific
> case of "foo IS NULL" and "foo IS NOT NULL" expressions, but I'm
> not eager to let that confusion propagate anyplace else.

Not having used arrays in SQL, I don't feel able to offer much
opinion on implementation details; just the general opinion that we
should try to allow reasonable things to be done in a wide variety
of cases.  Where it's possible to do that and have behavior which is
easy to explain, all the better.  The vast majority of problematic
SQL code that I've seen is a direct consequence of programmers using
procedural code where they could have used declarative code, so I
would tend to stress clean and complete set operations over
concessions to procedural coding.

I'll stop waving my hands around now.

-Kevin