Thread: RULE with conditional behaviour?

RULE with conditional behaviour?

From
Rob Hoopman
Date:
Hello all,
I've got two versions of the same question;
- The short version:
    As I understand it a rule cannot fire a trigger?

- And the long version:

What I am trying to do is this:

I have three tables and  a view on those tables ( see end of message if
the view below alone isn't enough info and you're in a helpful mood )

  CREATE VIEW field_label_locales AS
  -- [REFNAME: flabel_loc]
    SELECT
      field_labels.id AS field_label_id,
      field_labels.label,
      fields.id AS field_id,
      fields.identifier,
      locales.id AS locale_id,
      locales.iso639,
      locales.iso3166
    FROM
      field_labels
    RIGHT OUTER JOIN
      fields ON field_labels.field_id = fields.id
    RIGHT OUTER JOIN
      locales ON field_labels.locale_id = locales.id;

a select on this view looks like:

 field_label_id |     label      | field_id |   identifier   | locale_id
| iso639 | iso3166
----------------+----------------+----------+----------------+-----------+--------+---------
              1 | Naam Ontvanger |        2 | sender_address |         1
| nl     | NL
                |                |          |                |         2
| nl     | BE
              2 | Recipient Name |        2 | sender_address |         3
| en     | GB
                |                |          |                |         4
| en     | US
                |                |          |                |         5
| de     | DE


What I would like to be able to do is:

UPDATE field_label_locales SET label = 'Sender Name' WHERE locale_id = 1;

So; if field_label_id = NULL, I need to insert a record into
field_labels, else I need to update the record referenced in field_labels.

Can I do this with a rule on the view?
I've created a trigger which works just fine, but I can seem to fire a
trigger with a rule?

I could implement it some other way, but lazy as I am I thought I'd fire
off a mail to the list to see if I am missing something obvious.

Regards,
Rob


====
the relevant bits of the thre tables:

  CREATE TABLE locales (
    iso639         varchar(2) NOT NULL,   -- two character iso639
language code
    iso3166        varchar(2),            -- two character iso3166
country code
    lang_native    varchar(80) NOT NULL,  -- language name in native
language
    country_native varchar(80),           -- country name in native language
    fallback       boolean DEFAULT false, -- If set to true, this locale is
    id            bigserial,
    PRIMARY KEY(id),
    UNIQUE(iso639, iso3166)
  );

  CREATE TABLE fields (
    identifier    varchar(30),
    id            bigserial,
    PRIMARY KEY(id)
  );

  CREATE TABLE field_labels (
    label         varchar(100) NOT NULL,
    locale_id       bigint NOT NULL,
    field_id        bigint NOT NULL,
    FOREIGN KEY(locale_id) REFERENCES locales ON DELETE RESTRICT ON
UPDATE CASCADE,
    FOREIGN KEY(field_id) REFERENCES fields ON DELETE RESTRICT ON UPDATE
CASCADE
  );


Re: RULE with conditional behaviour?

From
Tom Lane
Date:
Rob Hoopman <rob@tuna.nl> writes:
> So; if field_label_id = NULL, I need to insert a record into
> field_labels, else I need to update the record referenced in field_labels.

> Can I do this with a rule on the view?

There is no concept of conditional execution in rules, but sometimes you
can fake it with suitably conditionalized individual actions.  In this
case it might work to do a two-action rule.  Very schematically:

ON UPDATE TO view DO INSTEAD
(
  UPDATE base-table WHERE key = whatever;
  INSERT INTO base-table SELECT list-of-values WHERE
    NOT EXISTS (SELECT 1 FROM base-table WHERE key = whatever);
)

The first action gets the job done if there are existing rows, and does
nothing if not.  The second action, vice versa.

> I've created a trigger which works just fine, but I can seem to fire a
> trigger with a rule?

Sure, triggers are fired by rule actions.  However, triggers only fire
on insertions/deletions of real tuples --- and there are none in a view.
So you can't usefully attach a trigger to a view, only to a base table.

If the above approach seems too complex, another possibility is to write
the rule attached to the view as something simple that invokes an action
you know will fire a trigger.  For example,

ON UPDATE TO view DO INSTEAD INSERT INTO base-table VALUES(needed-values)

and then the insert trigger on base-table is set up to first look for a
conflicting extant row; if found, UPDATE it and suppress the insert;
else allow the insert to proceed.  A difficulty with this approach is
that the insert-trigger will also apply to direct inserts into the base
table, but you may find that that's not a problem for you.

            regards, tom lane

Re: RULE with conditional behaviour?

From
Tom Lane
Date:
I wrote:
> If the above approach seems too complex, another possibility is to write
> the rule attached to the view as something simple that invokes an action
> you know will fire a trigger.  For example,
> ON UPDATE TO view DO INSTEAD INSERT INTO base-table VALUES(needed-values)

This approach may not work well if your view is a join and so you might
need to update multiple base tables.  There is a sneaky way to get
around that, which is to use a dummy table that has no other purpose
than to be a place where you can fire a trigger.  For example,

ON UPDATE TO view DO INSTEAD INSERT INTO dummy-table SELECT NEW.*

Here, dummy-table is a real table (not a view), but it will never have
any rows in it, because you'll give it an on-insert trigger that always
suppresses the insert.  Now, that trigger has access to a full row of
the "updated view" and so it can go off and do whatever is needed to
the underlying base tables.

If you did it just like this then you'd actually need three dummy
tables, one each for insert, update, and delete actions on the view.
If that seems like overkill, consider

ON INSERT TO view DO INSTEAD INSERT INTO dummy-table SELECT 1, NEW.*
ON UPDATE TO view DO INSTEAD INSERT INTO dummy-table SELECT 2, NEW.*
ON DELETE TO view DO INSTEAD INSERT INTO dummy-table SELECT 3, OLD.*

Now the insert trigger can look at the first column to decide what to
do.  (Note you can't do anything useful with update or delete triggers
on the dummy table; it'll never have any rows so they'd never fire.)
In general, the columns of the dummy table are exactly the parameter
list you need to pass to your trigger, and so you can make 'em whatever
you like.  For example, to correctly update a view you might need both
the old and new states of the (virtual) view row.  No problem:

ON UPDATE TO view DO INSTEAD INSERT INTO dummy-table SELECT OLD.*, NEW.*

where dummy-table is declared with the necessary column set.

I have not actually had occasion to use this approach myself, but
here is a simple example showing that it would work:

regression=# select * from b1;
     f1      |     f2
-------------+-------------
           0 |           0
      123456 |       61728
     -123456 |      -61728
  2147483647 |  1073741823
 -2147483647 | -1073741823
(5 rows)

regression=# select * from b2;
     f1      |     f2
-------------+-------------
           0 |           2
      123456 |      123458
     -123456 |     -123454
  2147483647 | -2147483647
 -2147483647 | -2147483645
(5 rows)

regression=# create view vvv as
regression-# select b1.f1, b1.f2, b2.f2 as f22 from b1 join b2 using (f1);
CREATE
regression=# select * from vvv;
     f1      |     f2      |     f22
-------------+-------------+-------------
 -2147483647 | -1073741823 | -2147483645
     -123456 |      -61728 |     -123454
           0 |           0 |           2
      123456 |       61728 |      123458
  2147483647 |  1073741823 | -2147483647
(5 rows)

-- Note that this update makes no sense in terms of either base table alone:
regression=# update vvv set f22 = 43 where f2 =  61728 ;
ERROR:  Cannot update a view without an appropriate rule

regression=# create table vvv_dummy (f1 int, f2 int, f22 int,
regression(# new_f1 int, new_f2 int, new_f22 int);
CREATE
regression=# create rule vvv_update as on update to vvv do instead
regression-# insert into vvv_dummy select old.*, new.*;
CREATE

regression=# update vvv set f22 = 43 where f2 =  61728 ;
UPDATE 0
regression=# select * from vvv_dummy;
   f1   |  f2   |  f22   | new_f1 | new_f2 | new_f22
--------+-------+--------+--------+--------+---------
 123456 | 61728 | 123458 | 123456 |  61728 |      43
(1 row)

I didn't bother to make an ON INSERT trigger for vvv_dummy, but if I had
one, it would have received the data shown here as inserted into vvv_dummy.

In short: by using a dummy table you can get the effect of a trigger
applied to a view.

            regards, tom lane