Thread: BUG #2948: default null values for not-null domains

BUG #2948: default null values for not-null domains

From
"Sergiy Vyshnevetskiy"
Date:
The following bug has been logged online:

Bug reference:      2948
Logged by:          Sergiy Vyshnevetskiy
Email address:      serg@vostok.net
PostgreSQL version: 8.2.1
Operating system:   FreeBSD-6 stable
Description:        default null values for not-null domains
Details:

create domain "DInt4" as int not null;
CREATE DOMAIN
#psql  = serg@[local]:5432 test
create or replace function test() returns "DInt4" immutable strict language
plpgsql as $F$declare t "DInt4"; begin return t; end$F$;
CREATE FUNCTION
#psql  = serg@[local]:5432 test
select test() is null;
 ?column?
----------
 t
(1 запись)

#psql  = serg@[local]:5432 test

Last select should have risen an exception:

ERROR:  variable "t" declared NOT NULL cannot default to NULL

Re: BUG #2948: default null values for not-null domains

From
Sergiy Vyshnevetskiy
Date:
This should fix the problem.

Re: BUG #2948: default null values for not-null domains

From
Tom Lane
Date:
Sergiy Vyshnevetskiy <serg@vostok.net> writes:
> This should fix the problem.

No, not at all.  Consider if you'd written a domain CHECK constraint
that rejects nulls, instead of the easy case.  What we'd really have
to do here is see if domain_in() will accept a NULL.

I'm starting to get the feeling that the entire idea of NOT NULL domains
is broken :-(

            regards, tom lane

Re: BUG #2948: default null values for not-null domains

From
Sergiy Vyshnevetskiy
Date:
On Wed, 31 Jan 2007, Tom Lane wrote:

> Sergiy Vyshnevetskiy <serg@vostok.net> writes:
>> This should fix the problem.
>
> No, not at all.  Consider if you'd written a domain CHECK constraint
> that rejects nulls, instead of the easy case.

I've forgotten that.

> What we'd really have to do here is see if domain_in() will accept a
> NULL.

Almost correct. We need to set the default value for every variable to
null "the right way".

First, we need a PLpgSQL_expr variable for "null" expression. A static
variable assigned at program startup would be best, but on-the-fly will
work too, if slightly inefficient.

Second, we use a pointer to it instead of NULL in decl_defval in gram.y.

That's all!

What to call to convert a text string "null" into PLpgSQL_expr?

Where's the best place to call it?


> I'm starting to get the feeling that the entire idea of NOT NULL domains
> is broken :-(

Not at all. What's "broken" is the idea of variable as a simple piece of
memory. It is correct for base types, but not for domains - they may have
non-empty constructors (in C++ terminology).

As such, the so-called "optimized" assignment algorithm for null defaults
("let's flip a bit and pretend we've done it") in exec_stmt_block() may
not work for such domains.

Assign them all and let optimizer sort them out. :)

Re: BUG #2948: default null values for not-null domains

From
Tom Lane
Date:
Sergiy Vyshnevetskiy <serg@vostok.net> writes:
> Not at all. What's "broken" is the idea of variable as a simple piece of
> memory. It is correct for base types, but not for domains - they may have
> non-empty constructors (in C++ terminology).

That may be, but I'm unwilling to pay the overhead for *every* variable
when most of them won't be domains.  I'm inclined to extend PLpgSQL_type
to include a domain indicator and only do it the hard way when we have to.

[ looks at code... ]  Actually, I think we already have the flag we
need: look to see if the typinput function is strict.

            regards, tom lane

Re: BUG #2948: default null values for not-null domains

From
Peter Eisentraut
Date:
Tom Lane wrote:
> I'm starting to get the feeling that the entire idea of NOT NULL
> domains is broken :-(

How is that so very different from having a default value of 5 with a
domain that rejects 5?

--
Peter Eisentraut
http://developer.postgresql.org/~petere/

Re: BUG #2948: default null values for not-null domains

From
Sergiy Vyshnevetskiy
Date:
On Wed, 31 Jan 2007, Tom Lane wrote:

> Sergiy Vyshnevetskiy <serg@vostok.net> writes:
>> Not at all. What's "broken" is the idea of variable as a simple piece of
>> memory. It is correct for base types, but not for domains - they may have
>> non-empty constructors (in C++ terminology).
>
> That may be, but I'm unwilling to pay the overhead for *every* variable
> when most of them won't be domains.  I'm inclined to extend PLpgSQL_type
> to include a domain indicator and only do it the hard way when we have to.

Why not add PLPGSQL_TTYPE_DOMAIN and rename PLPGSQL_TTYPE_SCALAR to
PLPGSQL_TTYPE_BASE? We only use PLPGSQL_TTYPE_SCALAR in _3_ places!

> [ looks at code... ]  Actually, I think we already have the flag we
> need: look to see if the typinput function is strict.

All domains have domain_in as input function - it is NOT strict.


Anyway, as we assign a value to a domain variable we must check
constraints - that's the whole point of domains. Even when the value is
"null".

Hack attached. Any reasons not to call it a bugfix?

Re: BUG #2948: default null values for not-null domains

From
Tom Lane
Date:
Peter Eisentraut <peter_e@gmx.net> writes:
> Tom Lane wrote:
>> I'm starting to get the feeling that the entire idea of NOT NULL
>> domains is broken :-(

> How is that so very different from having a default value of 5 with a
> domain that rejects 5?

Two words for you: outer joins.

            regards, tom lane

Re: BUG #2948: default null values for not-null domains

From
Sergiy Vyshnevetskiy
Date:
On Thu, 1 Feb 2007, Peter Eisentraut wrote:

> Tom Lane wrote:
>> I'm starting to get the feeling that the entire idea of NOT NULL
>> domains is broken :-(
>
> How is that so very different from having a default value of 5 with a
> domain that rejects 5?

Because 5 will be rejected as a value for a variable or field of such
domain. This is correct (and useful) behavior.

On the other hand null can make it there under some circumstances, even if
domain explicitly forbids nulls. Which is the bug I'm fighting against.

Actually there are several of them, and I plan to post them all. And,
hopefully, bugfixes too.

Re: BUG #2948: default null values for not-null domains

From
Tom Lane
Date:
Sergiy Vyshnevetskiy <serg@vostok.net> writes:
> Why not add PLPGSQL_TTYPE_DOMAIN and rename PLPGSQL_TTYPE_SCALAR to
> PLPGSQL_TTYPE_BASE? We only use PLPGSQL_TTYPE_SCALAR in _3_ places!

That was my first thought too, but it's wrong.  The right thing is to
look at the strictness of the input function, because that is the API
we have defined to determine whether a datatype might possibly be
interested in rejecting nulls.  The fact that domain_in() is the only
example in the core system doesn't make it correct to restrict the
functionality to domains.

            regards, tom lane

Re: BUG #2948: default null values for not-null domains

From
Sergiy Vyshnevetskiy
Date:
On Thu, 1 Feb 2007, Tom Lane wrote:

> Sergiy Vyshnevetskiy <serg@vostok.net> writes:
>> Why not add PLPGSQL_TTYPE_DOMAIN and rename PLPGSQL_TTYPE_SCALAR to
>> PLPGSQL_TTYPE_BASE? We only use PLPGSQL_TTYPE_SCALAR in _3_ places!
>
> That was my first thought too, but it's wrong.  The right thing is to
> look at the strictness of the input function, because that is the API
> we have defined to determine whether a datatype might possibly be
> interested in rejecting nulls.  The fact that domain_in() is the only
> example in the core system doesn't make it correct to restrict the
> functionality to domains.

...
I have been slow.
If input function IS strict then nulls are ALWAYS accepted.
If input function IS NOT strict then nulls MIGHT be rejectted.

And the patch is much more simple now (attached).
Is that it?

Re: BUG #2948: default null values for not-null domains

From
Tom Lane
Date:
Sergiy Vyshnevetskiy <serg@vostok.net> writes:
> If input function IS strict then nulls are ALWAYS accepted.
> If input function IS NOT strict then nulls MIGHT be rejectted.
> And the patch is much more simple now (attached).
> Is that it?

Almost right.  exec_assign_value() thinks its isNull argument is the
null flag for the *source* value (not sure why it's pass by reference).
As you set it up, var->isnull would be aliased by *isNull, which might
manage to break things within that function if the code were ever
rearranged.

Also, some comments are usually a good idea (if the purpose were
obvious, it'd have been right the first time, no?), and you always need
to check the regression tests --- it turns out that the wrong behavior
was actually being exposed by the tests.

Patch as-applied is attached.

            regards, tom lane

Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.186
diff -c -r1.186 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c    30 Jan 2007 18:02:22 -0000    1.186
--- src/pl/plpgsql/src/pl_exec.c    1 Feb 2007 19:10:51 -0000
***************
*** 890,897 ****
--- 890,916 ----
                      {
                          if (var->default_val == NULL)
                          {
+                             /* Initially it contains a NULL */
                              var->value = (Datum) 0;
                              var->isnull = true;
+                             /*
+                              * If needed, give the datatype a chance to reject
+                              * NULLs, by assigning a NULL to the variable.
+                              * We claim the value is of type UNKNOWN, not the
+                              * var's datatype, else coercion will be skipped.
+                              * (Do this before the notnull check to be
+                              * consistent with exec_assign_value.)
+                              */
+                             if (!var->datatype->typinput.fn_strict)
+                             {
+                                 bool    valIsNull = true;
+
+                                 exec_assign_value(estate,
+                                                   (PLpgSQL_datum *) var,
+                                                   (Datum) 0,
+                                                   UNKNOWNOID,
+                                                   &valIsNull);
+                             }
                              if (var->notnull)
                                  ereport(ERROR,
                                      (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
Index: src/test/regress/expected/domain.out
===================================================================
RCS file: /cvsroot/pgsql/src/test/regress/expected/domain.out,v
retrieving revision 1.38
diff -c -r1.38 domain.out
*** src/test/regress/expected/domain.out    6 Oct 2006 17:14:01 -0000    1.38
--- src/test/regress/expected/domain.out    1 Feb 2007 19:10:51 -0000
***************
*** 383,388 ****
--- 383,404 ----
  create function doubledecrement(p1 pos_int) returns pos_int as $$
  declare v pos_int;
  begin
+     return p1;
+ end$$ language plpgsql;
+ select doubledecrement(3); -- fail because of implicit null assignment
+ ERROR:  domain pos_int does not allow null values
+ CONTEXT:  PL/pgSQL function "doubledecrement" line 2 during statement block local variable initialization
+ create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
+ declare v pos_int := 0;
+ begin
+     return p1;
+ end$$ language plpgsql;
+ select doubledecrement(3); -- fail at initialization assignment
+ ERROR:  value for domain pos_int violates check constraint "pos_int_check"
+ CONTEXT:  PL/pgSQL function "doubledecrement" line 2 during statement block local variable initialization
+ create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
+ declare v pos_int := 1;
+ begin
      v := p1 - 1;
      return v - 1;
  end$$ language plpgsql;
Index: src/test/regress/sql/domain.sql
===================================================================
RCS file: /cvsroot/pgsql/src/test/regress/sql/domain.sql,v
retrieving revision 1.21
diff -c -r1.21 domain.sql
*** src/test/regress/sql/domain.sql    5 Apr 2006 22:11:58 -0000    1.21
--- src/test/regress/sql/domain.sql    1 Feb 2007 19:10:51 -0000
***************
*** 310,315 ****
--- 310,331 ----
  create function doubledecrement(p1 pos_int) returns pos_int as $$
  declare v pos_int;
  begin
+     return p1;
+ end$$ language plpgsql;
+
+ select doubledecrement(3); -- fail because of implicit null assignment
+
+ create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
+ declare v pos_int := 0;
+ begin
+     return p1;
+ end$$ language plpgsql;
+
+ select doubledecrement(3); -- fail at initialization assignment
+
+ create or replace function doubledecrement(p1 pos_int) returns pos_int as $$
+ declare v pos_int := 1;
+ begin
      v := p1 - 1;
      return v - 1;
  end$$ language plpgsql;

Re: BUG #2948: default null values for not-null domains

From
Sergiy Vyshnevetskiy
Date:
On Thu, 1 Feb 2007, Tom Lane wrote:

> Sergiy Vyshnevetskiy <serg@vostok.net> writes:
>> If input function IS strict then nulls are ALWAYS accepted.
>> If input function IS NOT strict then nulls MIGHT be rejectted.
>> And the patch is much more simple now (attached).
>> Is that it?
>
> Almost right.  exec_assign_value() thinks its isNull argument is the
> null flag for the *source* value (not sure why it's pass by reference).

Because the value may change during type cast. From null to non-null too.
Or vice-versa. I'll try it later.

> As you set it up, var->isnull would be aliased by *isNull, which might
> manage to break things within that function if the code were ever
> rearranged.
>
> Also, some comments are usually a good idea (if the purpose were
> obvious, it'd have been right the first time, no?),

I will, when I'm sure what I'm doing. For now it's mostly "mokey see -
monkey do".

> and you always need to check the regression tests --- it turns out that
> the wrong behavior was actually being exposed by the tests.

Hmm? Oh, yeah, I /heard/ something about them ... I think. :)

> Patch as-applied is attached.

Excellent. Thanks.