Skip to content

Commit 0c13ee1

Browse files
committed
Avoid trying to lock OLD/NEW in a rule with FOR UPDATE.
transformLockingClause neglected to exclude the pseudo-RTEs for OLD/NEW when processing a rule's query. This led to odd errors or even crashes later on. This bug is very ancient, but it's not terribly surprising that nobody noticed, since the use-case for SELECT FOR UPDATE in a non-view rule is somewhere between thin and non-existent. Still, crashing is not OK. Per bug #17151 from Zhiyong Wu. Thanks to Masahiko Sawada for analysis of the problem. Discussion: https://postgr.es/m/17151-c03a3e6e4ec9aadb@postgresql.org
1 parent eb2f59b commit 0c13ee1

File tree

4 files changed

+60
-6
lines changed

4 files changed

+60
-6
lines changed

src/backend/parser/analyze.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,13 +2744,22 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
27442744

27452745
if (lockedRels == NIL)
27462746
{
2747-
/* all regular tables used in query */
2747+
/*
2748+
* Lock all regular tables used in query and its subqueries. We
2749+
* examine inFromCl to exclude auto-added RTEs, particularly NEW/OLD
2750+
* in rules. This is a bit of an abuse of a mostly-obsolete flag, but
2751+
* it's convenient. We can't rely on the namespace mechanism that has
2752+
* largely replaced inFromCl, since for example we need to lock
2753+
* base-relation RTEs even if they are masked by upper joins.
2754+
*/
27482755
i = 0;
27492756
foreach(rt, qry->rtable)
27502757
{
27512758
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
27522759

27532760
++i;
2761+
if (!rte->inFromCl)
2762+
continue;
27542763
switch (rte->rtekind)
27552764
{
27562765
case RTE_RELATION:
@@ -2780,7 +2789,11 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
27802789
}
27812790
else
27822791
{
2783-
/* just the named tables */
2792+
/*
2793+
* Lock just the named tables. As above, we allow locking any base
2794+
* relation regardless of alias-visibility rules, so we need to
2795+
* examine inFromCl to exclude OLD/NEW.
2796+
*/
27842797
foreach(l, lockedRels)
27852798
{
27862799
RangeVar *thisrel = (RangeVar *) lfirst(l);
@@ -2801,6 +2814,8 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
28012814
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
28022815

28032816
++i;
2817+
if (!rte->inFromCl)
2818+
continue;
28042819
if (strcmp(rte->eref->aliasname, thisrel->relname) == 0)
28052820
{
28062821
switch (rte->rtekind)

src/include/nodes/parsenodes.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -913,10 +913,10 @@ typedef struct PartitionCmd
913913
* inFromCl marks those range variables that are listed in the FROM clause.
914914
* It's false for RTEs that are added to a query behind the scenes, such
915915
* as the NEW and OLD variables for a rule, or the subqueries of a UNION.
916-
* This flag is not used anymore during parsing, since the parser now uses
917-
* a separate "namespace" data structure to control visibility, but it is
918-
* needed by ruleutils.c to determine whether RTEs should be shown in
919-
* decompiled queries.
916+
* This flag is not used during parsing (except in transformLockingClause,
917+
* q.v.); the parser now uses a separate "namespace" data structure to
918+
* control visibility. But it is needed by ruleutils.c to determine
919+
* whether RTEs should be shown in decompiled queries.
920920
*
921921
* requiredPerms and checkAsUser specify run-time access permissions
922922
* checks to be performed at query startup. The user must have *all*

src/test/regress/expected/rules.out

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2822,6 +2822,31 @@ select * from only t1_2;
28222822
(10 rows)
28232823

28242824
reset constraint_exclusion;
2825+
-- test FOR UPDATE in rules
2826+
create table rules_base(f1 int, f2 int);
2827+
insert into rules_base values(1,2), (11,12);
2828+
create rule r1 as on update to rules_base do instead
2829+
select * from rules_base where f1 = 1 for update;
2830+
update rules_base set f2 = f2 + 1;
2831+
f1 | f2
2832+
----+----
2833+
1 | 2
2834+
(1 row)
2835+
2836+
create or replace rule r1 as on update to rules_base do instead
2837+
select * from rules_base where f1 = 11 for update of rules_base;
2838+
update rules_base set f2 = f2 + 1;
2839+
f1 | f2
2840+
----+----
2841+
11 | 12
2842+
(1 row)
2843+
2844+
create or replace rule r1 as on update to rules_base do instead
2845+
select * from rules_base where f1 = 11 for update of old; -- error
2846+
ERROR: relation "old" in FOR UPDATE clause not found in FROM clause
2847+
LINE 2: select * from rules_base where f1 = 11 for update of old;
2848+
^
2849+
drop table rules_base;
28252850
-- test various flavors of pg_get_viewdef()
28262851
select pg_get_viewdef('shoe'::regclass) as unpretty;
28272852
unpretty

src/test/regress/sql/rules.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,20 @@ select * from only t1_2;
992992

993993
reset constraint_exclusion;
994994

995+
-- test FOR UPDATE in rules
996+
997+
create table rules_base(f1 int, f2 int);
998+
insert into rules_base values(1,2), (11,12);
999+
create rule r1 as on update to rules_base do instead
1000+
select * from rules_base where f1 = 1 for update;
1001+
update rules_base set f2 = f2 + 1;
1002+
create or replace rule r1 as on update to rules_base do instead
1003+
select * from rules_base where f1 = 11 for update of rules_base;
1004+
update rules_base set f2 = f2 + 1;
1005+
create or replace rule r1 as on update to rules_base do instead
1006+
select * from rules_base where f1 = 11 for update of old; -- error
1007+
drop table rules_base;
1008+
9951009
-- test various flavors of pg_get_viewdef()
9961010

9971011
select pg_get_viewdef('shoe'::regclass) as unpretty;

0 commit comments

Comments
 (0)