diff --git a/doc/src/sgml/ref/create_rule.sgml b/doc/src/sgml/ref/create_rule.sgml index 430026a786..a2388b09d1 100644 --- a/doc/src/sgml/ref/create_rule.sgml +++ b/doc/src/sgml/ref/create_rule.sgml @@ -1,5 +1,5 @@ @@ -167,7 +167,8 @@ CREATE action part of the rule is executed. The action is done instead of the original query if INSTEAD is specified; otherwise - it is done before the original query is performed. + it is done after the original query in the case of ON INSERT, or before + the original query in the case of ON UPDATE or ON DELETE. Within both the condition and action, values from fields in the old instance and/or the new instance are substituted for diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml index 508345a442..512f5f266b 100644 --- a/doc/src/sgml/rules.sgml +++ b/doc/src/sgml/rules.sgml @@ -1,3 +1,5 @@ + + The <ProductName>Postgres</ProductName> Rule System @@ -1014,6 +1016,16 @@ for a rule with one action. + + For ON INSERT rules, the original query (if not suppressed by INSTEAD) + is done before any actions added by rules. This allows the actions to + see the inserted row(s). But for ON UPDATE and ON + DELETE rules, the original query is done after the actions added by rules. + This ensures that the actions can see the to-be-updated or to-be-deleted + rows; otherwise, the actions might do nothing because they find no rows + matching their qualifications. + + The parsetrees generated from rule actions are thrown into the rewrite system again and maybe more rules get applied resulting @@ -1022,8 +1034,8 @@ or another resultrelation. Otherwise this recursive process will end up in a loop. There is a compiled in recursion limit of currently 10 iterations. If after 10 iterations there are still update rules to apply the - rule system assumes a loop over multiple rule definitions and aborts the - transaction. + rule system assumes a loop over multiple rule definitions and reports + an error. @@ -1262,10 +1274,8 @@ - It is important, that the original parsetree is executed last. - The Postgres "traffic cop" does - a command counter increment between the execution of the two - parsetrees so the second one can see changes made by the first. + Here we can see why it is important that the original parsetree is + executed last. If the UPDATE would have been executed first, all the rows are already set to zero, so the logging INSERT would not find any row where 0 != shoelace_data.sl_avail. diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 3011e54fd7..3b3d209d46 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.96 2001/07/06 13:40:47 wieck Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.97 2001/07/09 23:50:32 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -831,7 +831,7 @@ deepRewriteQuery(Query *parsetree) numQueryRewriteInvoked - 1); } - instead = FALSE; + instead = false; result = RewriteQuery(parsetree, &instead, &qual_products); foreach(n, result) @@ -845,25 +845,41 @@ deepRewriteQuery(Query *parsetree) } /* - * qual_products are the original query with the negated rule - * qualification of an instead rule + * For INSERTs, the original query is done first; for UPDATE/DELETE, it is + * done last. This is needed because update and delete rule actions might + * not do anything if they are invoked after the update or delete is + * performed. The command counter increment between the query execution + * makes the deleted (and maybe the updated) tuples disappear so the scans + * for them in the rule actions cannot find them. */ - if (qual_products != NIL) - rewritten = nconc(rewritten, qual_products); - - /* - * The original query is appended last (if no "instead" rule) because - * update and delete rule actions might not do anything if they are - * invoked after the update or delete is performed. The command - * counter increment between the query execution makes the deleted - * (and maybe the updated) tuples disappear so the scans for them in - * the rule actions cannot find them. - */ - if (!instead) - if (parsetree->commandType == CMD_INSERT) + if (parsetree->commandType == CMD_INSERT) + { + /* + * qual_products are the original query with the negated rule + * qualification of an INSTEAD rule + */ + if (qual_products != NIL) + rewritten = nconc(qual_products, rewritten); + /* + * Add the unmodified original query, if no INSTEAD rule was seen. + */ + if (!instead) rewritten = lcons(parsetree, rewritten); - else + } + else + { + /* + * qual_products are the original query with the negated rule + * qualification of an INSTEAD rule + */ + if (qual_products != NIL) + rewritten = nconc(rewritten, qual_products); + /* + * Add the unmodified original query, if no INSTEAD rule was seen. + */ + if (!instead) rewritten = lappend(rewritten, parsetree); + } return rewritten; } diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 1c4fabd28b..acf6aa47f0 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -107,7 +107,7 @@ create table rtest_nothn2 (a int4, b text); create table rtest_nothn3 (a int4, b text); create table rtest_nothn4 (a int4, b text); create rule rtest_nothn_r1 as on insert to rtest_nothn1 - where new.a >= 10 and new.a < 20 do instead (select 1); + where new.a >= 10 and new.a < 20 do instead nothing; create rule rtest_nothn_r2 as on insert to rtest_nothn1 where new.a >= 30 and new.a < 40 do instead nothing; create rule rtest_nothn_r3 as on insert to rtest_nothn2 @@ -1313,7 +1313,7 @@ SELECT tablename, rulename, definition FROM pg_rules rtest_emp | rtest_emp_del | CREATE RULE rtest_emp_del AS ON DELETE TO rtest_emp DO INSERT INTO rtest_emplog (ename, who, "action", newsal, oldsal) VALUES (old.ename, "current_user"(), 'fired '::bpchar, '$0.00'::money, old.salary); rtest_emp | rtest_emp_ins | CREATE RULE rtest_emp_ins AS ON INSERT TO rtest_emp DO INSERT INTO rtest_emplog (ename, who, "action", newsal, oldsal) VALUES (new.ename, "current_user"(), 'hired '::bpchar, new.salary, '$0.00'::money); rtest_emp | rtest_emp_upd | CREATE RULE rtest_emp_upd AS ON UPDATE TO rtest_emp WHERE (new.salary <> old.salary) DO INSERT INTO rtest_emplog (ename, who, "action", newsal, oldsal) VALUES (new.ename, "current_user"(), 'honored '::bpchar, new.salary, old.salary); - rtest_nothn1 | rtest_nothn_r1 | CREATE RULE rtest_nothn_r1 AS ON INSERT TO rtest_nothn1 WHERE ((new.a >= 10) AND (new.a < 20)) DO INSTEAD SELECT 1; + rtest_nothn1 | rtest_nothn_r1 | CREATE RULE rtest_nothn_r1 AS ON INSERT TO rtest_nothn1 WHERE ((new.a >= 10) AND (new.a < 20)) DO INSTEAD NOTHING; rtest_nothn1 | rtest_nothn_r2 | CREATE RULE rtest_nothn_r2 AS ON INSERT TO rtest_nothn1 WHERE ((new.a >= 30) AND (new.a < 40)) DO INSTEAD NOTHING; rtest_nothn2 | rtest_nothn_r3 | CREATE RULE rtest_nothn_r3 AS ON INSERT TO rtest_nothn2 WHERE (new.a >= 100) DO INSTEAD INSERT INTO rtest_nothn3 (a, b) VALUES (new.a, new.b); rtest_nothn2 | rtest_nothn_r4 | CREATE RULE rtest_nothn_r4 AS ON INSERT TO rtest_nothn2 DO INSTEAD NOTHING; diff --git a/src/test/regress/sql/rules.sql b/src/test/regress/sql/rules.sql index 9a04ed3a3d..5068face88 100644 --- a/src/test/regress/sql/rules.sql +++ b/src/test/regress/sql/rules.sql @@ -131,7 +131,7 @@ create table rtest_nothn3 (a int4, b text); create table rtest_nothn4 (a int4, b text); create rule rtest_nothn_r1 as on insert to rtest_nothn1 - where new.a >= 10 and new.a < 20 do instead (select 1); + where new.a >= 10 and new.a < 20 do instead nothing; create rule rtest_nothn_r2 as on insert to rtest_nothn1 where new.a >= 30 and new.a < 40 do instead nothing;