Thread: PostgreSQL: Question about rules

PostgreSQL: Question about rules

From
"Jeremy Smith"
Date:
A question about rules:

I am trying to create a rule which handles inserts on a view, and that
rule should insert rows into multiple tables, each one of which
contains some of the columns that the view contains.

What if one of these tables has, for example, a serial primary key?
If I explicitly deal with that column in one of the insert statements
inside of the rule, then the column will either always either be
explicit, or an error will be thrown if it was not specified in the
insert to the view.  If I don't explicitly deal with that column in
one of the insert statements (or if I use nextval() to deal with it),
then it will be impossible to use explicit values for this column when
inserting (which is a bad idea anyway, but this is just an example).

Example:

------------------------ Begin example SQL
create table parent (
    id serial primary key,
    foo integer,
);

create table child (
    id integer references parent(id) on delete cascade,
    bar integer
)

create view child_with_parent_explicit as
    select parent.id, parent.foo, child.bar from parent join child using(id);

-- this next one is just a copy of the first, to differentiate the two scenarios
create view child_with_parent_implicit as
    select parent.id, parent.foo, child.bar from parent join child using(id);

create rule "child_with_parent_explicit_insert" as
    on insert to child_with_parent_explicit do instead (
        insert into parent(id, foo) values(new.id, new.foo);
        insert into child(id, bar) values(new.id, new.bar);
    );

create rule "child_with_parent_implicit_insert" as
    on insert to child_with_parent_implicit do instead (
        insert into parent(id, foo) values(nextval('parent_id_seq'), new.foo);
        insert into child(id, bar) values(currval('parent_id_seq'), new.bar);
    );

-- OK
insert into child_with_parent_implicit(foo, bar) values(1, 2);
insert into child_with_parent_explicit(id, foo, bar) values(42, 3, 4);

-- Bad - explicit id is ignored
insert into child_with_parent_implicit(id, foo, bar) values(42, 3, 4);

-- Bad - error occurs as explicit null is attempting to be inserted
into not null column
insert into child_with_parent_explicit(foo, bar) values(1, 2);

------------------------------------ End example SQL

Now, I realize I could apply both an implicit and explicit rule to the
same view by qualifying the rules with a ' where id is null ' and a '
where id is not null '.  However, what if I have other not null
columns or columns with default values?  Must I create a qualified
rule for each possible combination of columns?

Is there any way to do an insert inside a rule, and somehow specify
that a column in NEW may or may not be specified, and if it's not then
don't specify it in the insert?

Wow, that was pretty long.  I hope it was clear enough.  Thanks for
any advice you can give me - I am just now starting to get into the
more advanced SQL stuff and I apologize if my question is dumb.  FWIW,
I am trying to use this method to implement a Class-Table Inheritance
scheme.

Thanks,
Jeremy

Re: PostgreSQL: Question about rules

From
Jeff Davis
Date:
On Thu, 2006-11-16 at 11:34 -0800, Jeremy Smith wrote:
> Example:
>
> ------------------------ Begin example SQL
> create table parent (
>     id serial primary key,
>     foo integer,
> );
>
> create table child (
>     id integer references parent(id) on delete cascade,
>     bar integer
> )
>
> create view child_with_parent_explicit as
>     select parent.id, parent.foo, child.bar from parent join child using(id);
>
> -- this next one is just a copy of the first, to differentiate the two scenarios
> create view child_with_parent_implicit as
>     select parent.id, parent.foo, child.bar from parent join child using(id);
>
> create rule "child_with_parent_explicit_insert" as
>     on insert to child_with_parent_explicit do instead (
>         insert into parent(id, foo) values(new.id, new.foo);
>         insert into child(id, bar) values(new.id, new.bar);
>     );
>

create rule "child_with_parent_explicit_insert" as
        on insert to child_with_parent_explicit do instead (
                insert into parent(id, foo) values(COALESCE
(new.id,NEXTVAL('parent_id_seq')), new.foo);
                insert into child(id, bar) values(COALESCE
(new.id,CURRVAL('parent_id_seq')), new.bar);
        );


I'm not sure if this is what you're looking for, but does this help?

Regards,
    Jeff Davis


Re: PostgreSQL: Question about rules

From
"Jeremy Smith"
Date:
On 11/16/06, Jeff Davis <pgsql@j-davis.com> wrote:

create rule "child_with_parent_explicit_insert" as
        on insert to child_with_parent_explicit do instead (
                insert into parent(id, foo) values(COALESCE
(new.id ,NEXTVAL('parent_id_seq')), new.foo);
                insert into child(id, bar) values(COALESCE
(new.id,CURRVAL('parent_id_seq')), new.bar);
        );


I'm not sure if this is what you're looking for, but does this help?

Regards,
        Jeff Davis


COALESCE is certainly something I had never heard of.  I think that will solve my problem quite effectively.  Thanks for your help!

Jeremy

Re: PostgreSQL: Question about rules

From
"Jeremy Smith"
Date:
On 11/16/06, Jeremy Smith <postgres@duckwizard.com> wrote:
>  On 11/16/06, Jeff Davis <pgsql@j-davis.com> wrote:
>
>
> >  create rule "child_with_parent_explicit_insert" as
> >         on insert to child_with_parent_explicit do instead (
> >                 insert into parent(id, foo) values(COALESCE
> > ( new.id ,NEXTVAL('parent_id_seq')), new.foo);
> >                 insert into child(id, bar) values(COALESCE
> > (new.id,CURRVAL('parent_id_seq')),  new.bar);
> >         );
> >
> >
> > I'm not sure if this is what you're looking for, but does this help?
> >
> > Regards,
> >         Jeff Davis
> >
> >
>
>

One more thing that would sweeten the deal even further! Not so much
for sequences, but for other columns with default values:

insert into foo(bar) values(COALESCE(new.bar, DEFAULT))

This doesn't work, because DEFAULT is a language construct that is
only defined within the immediate scope of the values(...) list.  Is
there any way I can use COALESCE to defer to the table for the default
value, rather than explicitly specifying it?

I could probably fake this by writing a function to look up the
default value in pg_attrdef and evaluate it - just want to see if
there is a built-in function for this (I can't find one).  Because it
would be a lot of work :-)

Thanks,
Jeremy

Re: PostgreSQL: Question about rules

From
Jeff Davis
Date:
On Thu, 2006-11-16 at 16:58 -0800, Jeremy Smith wrote:

>
> One more thing that would sweeten the deal even further! Not so much
> for sequences, but for other columns with default values:
>
> insert into foo(bar) values(COALESCE(new.bar, DEFAULT))
>
> This doesn't work, because DEFAULT is a language construct that is
> only defined within the immediate scope of the values(...) list.  Is
> there any way I can use COALESCE to defer to the table for the default
> value, rather than explicitly specifying it?
>
> I could probably fake this by writing a function to look up the
> default value in pg_attrdef and evaluate it - just want to see if
> there is a built-in function for this (I can't find one).  Because it
> would be a lot of work :-)
>

Interesting question. It seems like you're trying to make a default
value for a view that's based on the underlying table's default value.

I think the normal way to do this is to _not_ have a default value on
the underlying table, and instead use the rule to define the default
value. Does that work for you?

Regards,
    Jeff Davis


Re: PostgreSQL: Question about rules

From
Tom Lane
Date:
Jeff Davis <pgsql@j-davis.com> writes:
> Interesting question. It seems like you're trying to make a default
> value for a view that's based on the underlying table's default value.

> I think the normal way to do this is to _not_ have a default value on
> the underlying table, and instead use the rule to define the default
> value. Does that work for you?

Actually, the best way to do that is to attach a default to the view
itself.

    CREATE VIEW v AS SELECT ... ;
    CREATE RULE ... for insert on v ... ;
    ALTER TABLE v ALTER COLUMN c DEFAULT whatever;

In this formulation the rule is not responsible for substituting any
default values, it just does what it's told.  This is better than the
COALESCE approach because the latter does the wrong thing if one is
explicitly inserting NULL.

I'm not sure this answers the OP's problem though, as it sounded like he
wanted a default dependent on other inserted values, which is something
you can't do with either a table or a view.

            regards, tom lane

Re: PostgreSQL: Question about rules

From
"Jeremy Smith"
Date:
On 11/16/06, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Actually, the best way to do that is to attach a default to the view
> itself.
>
>         CREATE VIEW v AS SELECT ... ;
>         CREATE RULE ... for insert on v ... ;
>         ALTER TABLE v ALTER COLUMN c DEFAULT whatever;
>
> In this formulation the rule is not responsible for substituting any
> default values, it just does what it's told.  This is better than the
> COALESCE approach because the latter does the wrong thing if one is
> explicitly inserting NULL.

Hey - this is great information.  I'm now very close to having a
general and robust solution to my problem.  One problem remains with
using this approach: sequences.

alter table my_view alter column set default nextval('my_table_id_seq');

create rule my_view_ins as on insert to my_view do instead(
       insert into my_table(id, foo) values(new.id, new.foo);
       insert into my_child_table(id, foo) values(new.id, new.bar);
);

-- Works
insert into my_view(id, foo, bar) values (42, 'a foo', 'a bar');

-- Error - key (id)=(3) is not present in table "my_table"
insert into my_view(foo, bar) values('another foo', 'another bar');


Now, the default value for new.id gets evaluated *each time* I
reference new.id - meaning the rule's first insert sees N for new.id
while the rule's second insert sees N+1.  That is kind of odd - I
would think that the default value would get evaluated and then
assigned to new.id (since it is a concrete row), but that appears not
to be the case.

My stopgap solution to this is to call a PSQL/PL function for the body
of the rule, which can store new.id in a variable so it is evaluated
only once.  If there is a way to do this inside the rule without
resorting to creating a view, 3 rules and 3 functions for every child
table, I would love to hear about it!

Thanks for all the tips, Jeff and Tom.

Jeremy (Reposted this to the list; I accidentally replied only to Tom
the first time).

Re: PostgreSQL: Question about rules

From
Jeff Davis
Date:
On Fri, 2006-11-17 at 11:49 -0800, Jeremy Smith wrote:

> Now, the default value for new.id gets evaluated *each time* I
> reference new.id - meaning the rule's first insert sees N for new.id
> while the rule's second insert sees N+1.  That is kind of odd - I
> would think that the default value would get evaluated and then
> assigned to new.id (since it is a concrete row), but that appears not
> to be the case.
>
> My stopgap solution to this is to call a PSQL/PL function for the body
> of the rule, which can store new.id in a variable so it is evaluated
> only once.  If there is a way to do this inside the rule without
> resorting to creating a view, 3 rules and 3 functions for every child
> table, I would love to hear about it!
>

I think you're operating on the edge of what rules are supposed to do. A
rule won't copy values or create variables, which is why you needed
COALESCE.

Why do you need so many functions though? Won't one SQL function do the
trick?

Regards,
    Jeff Davis


Re: PostgreSQL: Question about rules

From
"Jeremy Smith"
Date:
On 11/17/06, Jeff Davis <pgsql@j-davis.com> wrote:
>
> Why do you need so many functions though? Won't one SQL function do the
> trick?
>
> Regards,
>         Jeff Davis
>
>

You're right, one will do the trick - I was thinking update and
delete, but of course those will be fine with plain rules because they
don't need to do anything with default values.

Even so, I guess I have to use a trigger+function instead of a real
rule, and have an INSTEAD DO NOTHING rule for insert so that the
trigger gets called without error.  Seems like a bit of a kludge; I
think I may just give up and do all this stuff in application logic
instead of database logic.

I learned a helluva lot about PG though!  Thanks!

Jeremy

Re: PostgreSQL: Question about rules

From
Jeff Davis
Date:
On Fri, 2006-11-17 at 16:42 -0800, Jeremy Smith wrote:
> On 11/17/06, Jeff Davis <pgsql@j-davis.com> wrote:
> >
> > Why do you need so many functions though? Won't one SQL function do the
> > trick?
> >
> > Regards,
> >         Jeff Davis
> >
> >
>
> You're right, one will do the trick - I was thinking update and
> delete, but of course those will be fine with plain rules because they
> don't need to do anything with default values.
>
> Even so, I guess I have to use a trigger+function instead of a real
> rule, and have an INSTEAD DO NOTHING rule for insert so that the
> trigger gets called without error.  Seems like a bit of a kludge; I
> think I may just give up and do all this stuff in application logic
> instead of database logic.
>

For a trigger to work the columns have to match up. You'd have to create
some kind of empty table that you can insert everything you want into,
and have a BEFORE INSERT trigger to take in all the values, pass them
off to child tables, and then return NULL.

That's functionally not much different from having a rule rewrite the
insert into a function call. If you decide to implement it in the
database I think that's the cleanest way. Why do you think a rule that
makes a function call won't work?

Regards,
    Jeff Davis