Thread: NOT NULL with CREATE TYPE

NOT NULL with CREATE TYPE

From
Jean Hoderd
Date:
Dear list,

Here's the situation: I want to create a functional API to a Postgresql
database, in such a way that instead of issuing raw SQL commands (SELECT,
INSERT, etc), the client will only invoke functions from this API.

For example, to get all people in the database, the client will invoke
function "SELECT * FROM get_people()" instead of manually doing a SELECT
over the 'people' table:

create table people (id serial primary key, name text not null, age int4 not null);
create type result as (name text, age int4);
create function get_people() returns setof result as $$ select name, age from people $$ language sql;

But here's the problem: I want the client side library to check the
attnotnull field from pg_attribute to determine if it may be null.
However, in a user defined type like 'result' above, all the fields
are marked as possibly being null.

I have tried the following, but it's not accepted:
create type result as (name text not null, age int4 not null);

I also tried manually setting the attnotnull value in result to TRUE,
but it doesn't work as expected.

I also thought of creating a dummy table for the return results, but
I'm afraid the performance penalty might be too great.  Could you give
me some help in solving this problem?

Thank you!
Jean





Re: NOT NULL with CREATE TYPE

From
Tom Lane
Date:
Jean Hoderd <jhoderd@yahoo.com> writes:
> I have tried the following, but it's not accepted:
> create type result as (name text not null, age int4 not null);

Frankly, the notion that a "not null" condition might be associated with
a SQL data type is simply a bad idea.  The SQL committee let this happen
for domains in an episode of monumentally fuzzy thinking, but you don't
want to do it.  Consider

    create table t1 (id1 int, f1 int);
    create table t2 (id2 int, f2 mynotnulltype);
    select * from t1 left join t2 on id1=id2;

What is the datatype of the f2 column of the output?  mynotnulltype,
presumably.  Now what do you do about t1 rows that have no match in
t2?  You can either emit null-extended rows, thus producing null
values in a mynotnulltype column, or throw an error, which isn't too
appetizing either --- you just rendered outer joins useless.

With a not null domain, it is at least possible to finesse this by
deciding that the join output column should be considered to be
of the domain's base type.  If "not null" is hardwired into the
type definition, there's no way out.

So I don't recommend you try to do this.  What is the actual problem you
are trying to solve?  Why do you want the client library to be concerned
with attnotnull at all?

> For example, to get all people in the database, the client will invoke
> function "SELECT * FROM get_people()" instead of manually doing a SELECT
> over the 'people' table:

... or even more to the point, why do you think the above is a good idea
to begin with?  It looks more like the kind of bad design that is
frequently committed by people who basically don't like SQL, and try to
ensure that no one else will like it either.

            regards, tom lane

Re: NOT NULL with CREATE TYPE

From
Jean Hoderd
Date:
Hi,

> So I don't recommend you try to do this.  What is the actual problem
> you are trying to solve?  Why do you want the client library to be
> concerned with attnotnull at all?

In general the client library needs to check attnotnull to make sure
that the client is not making a type mistake.  If for example a field
is "int null", but the client program treats it as "int not null" (ie,
forgetting to check for the null case), then the client program is
wrong and the compiler should issue a warning.  This is basic, essential,
functionality.  But I'm curious: how else could this be done?...

> ... or even more to the point, why do you think the above is a good
> idea to begin with?  It looks more like the kind of bad design that
> is frequently committed by people who basically don't like SQL, and
> try to ensure that no one else will like it either.

The above example was absurdly simple, but in the real world the query
is complex enough that instead of being just a SELECT is actually the
return of a PL/PGSQL function.  I just want a way to tell the client
which fields from the return type are actually, really, nullable...

Any other ideas?

Cheers,
Jean





Re: NOT NULL with CREATE TYPE

From
Jeff Davis
Date:
On Fri, 2009-06-05 at 10:58 -0700, Jean Hoderd wrote:
> The above example was absurdly simple, but in the real world the query
> is complex enough that instead of being just a SELECT is actually the
> return of a PL/PGSQL function.  I just want a way to tell the client
> which fields from the return type are actually, really, nullable...

The way you are approaching this problem is understandable. You're
thinking of NULL as just an extra value in the domain of the type (and
therefore can be restricted by a type constraint), but that is not true
in SQL. You may think this approach is good or you may think it's bad,
but NULL permeates SQL at many levels, and can't merely be ignored.

In general, in SQL, NULLs can be produced in several ways even if every
column in your database is declared NOT NULL and you never specify a
NULL in any query. For instance, aggregates produce NULLs when there are
no input rows (COUNT is an exception), and (as Tom pointed out) OUTER
JOIN produces NULLs when there is no matching row on the other side.

I believe this fact foils the kind of checks you intend to do in the
general case, although you may be able to work around it creatively for
your specific situation. If you are interested in such a workaround,
provide a few more details and someone will probably have some ideas for
you.

The best I can say right now is that the client always needs to check
for NULL unless it somehow knows that NULL can't be produced.

Regards,
    Jeff Davis




Re: NOT NULL with CREATE TYPE

From
Merlin Moncure
Date:
On Fri, Jun 5, 2009 at 1:58 PM, Jean Hoderd<jhoderd@yahoo.com> wrote:
>
> Hi,
>
>> So I don't recommend you try to do this.  What is the actual problem
>> you are trying to solve?  Why do you want the client library to be
>> concerned with attnotnull at all?
>
> In general the client library needs to check attnotnull to make sure
> that the client is not making a type mistake.  If for example a field
> is "int null", but the client program treats it as "int not null" (ie,
> forgetting to check for the null case), then the client program is
> wrong and the compiler should issue a warning.  This is basic, essential,
> functionality.  But I'm curious: how else could this be done?...
>
>> ... or even more to the point, why do you think the above is a good
>> idea to begin with?  It looks more like the kind of bad design that
>> is frequently committed by people who basically don't like SQL, and
>> try to ensure that no one else will like it either.
>
> The above example was absurdly simple, but in the real world the query
> is complex enough that instead of being just a SELECT is actually the
> return of a PL/PGSQL function.  I just want a way to tell the client
> which fields from the return type are actually, really, nullable...

I agree with Tom...here is what I see as good reasons to get the app
to call functions:

*) chain together sequence of statements with dependant inputs and outputs
*) interface to a library hooked to the database that you dont want to
expose to the app for some reason, like dblink, or advisory locks
*) it's difficult or problematic to change/recompile the client side code

OTOH, if you are simply hiding one-liner select statements, you should
strongly consider using views instead of functions where possible.
The internal workings of views are more exposed to the planner at
query time and you are not forcing a particular method of invocation
to the app.  If you are wrapping updates and inserts (especially
updates), you are likely just creating work for yourself...consider
moving data dependent code into triggers.

sql functions are pretty inflexible...even with recent even with
recent advancements like varargs and default parameters they are
designed to do a very particular thing...and insert/update tend to be
fairly generic in how they operate.

merlin

Re: NOT NULL with CREATE TYPE

From
Jeff Davis
Date:
On Sat, 2009-06-06 at 15:03 -0400, Merlin Moncure wrote:
> sql functions are pretty inflexible...even with recent even with
> recent advancements like varargs and default parameters they are
> designed to do a very particular thing...and insert/update tend to be
> fairly generic in how they operate.
>

I think Jean was using that as an example to show how attnotnull is
sometimes invisible to the application, and the same would be true for a
view.

For instance, let's say you have:

create table foo(i int not null);
create view foo_v1 as select i from foo where i > 5;
create view foo_v2 as select sum(i) as i from foo;

Logically speaking, foo.i is not nullable, foo_v1.i is not nullable, but
foo_v2.i _is_ nullable. The application has no good way to know that.

Regards,
    Jeff Davis



Re: NOT NULL with CREATE TYPE

From
Merlin Moncure
Date:
On Mon, Jun 8, 2009 at 2:18 PM, Jeff Davis<pgsql@j-davis.com> wrote:
> On Sat, 2009-06-06 at 15:03 -0400, Merlin Moncure wrote:
>> sql functions are pretty inflexible...even with recent even with
>> recent advancements like varargs and default parameters they are
>> designed to do a very particular thing...and insert/update tend to be
>> fairly generic in how they operate.
>>
>
> I think Jean was using that as an example to show how attnotnull is
> sometimes invisible to the application, and the same would be true for a
> view.
>
> For instance, let's say you have:
>
> create table foo(i int not null);
> create view foo_v1 as select i from foo where i > 5;
> create view foo_v2 as select sum(i) as i from foo;
>
> Logically speaking, foo.i is not nullable, foo_v1.i is not nullable, but
> foo_v2.i _is_ nullable. The application has no good way to know that.

hm.  maybe, try defining the return type from your function using
'create table' not 'create type':

create table foo (a int, b text not null);

create function get_foo() returns setof foo as ...


not sure if this works.  if it does, it is yet another example of why
'create type as' is redundant and inferior to 'create table' for
purposes of creating composite types.

merlin