Skip to content

Commit 8d2d6ec

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 bed5eac commit 8d2d6ec

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
@@ -3170,13 +3170,22 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
31703170

31713171
if (lockedRels == NIL)
31723172
{
3173-
/* all regular tables used in query */
3173+
/*
3174+
* Lock all regular tables used in query and its subqueries. We
3175+
* examine inFromCl to exclude auto-added RTEs, particularly NEW/OLD
3176+
* in rules. This is a bit of an abuse of a mostly-obsolete flag, but
3177+
* it's convenient. We can't rely on the namespace mechanism that has
3178+
* largely replaced inFromCl, since for example we need to lock
3179+
* base-relation RTEs even if they are masked by upper joins.
3180+
*/
31743181
i = 0;
31753182
foreach(rt, qry->rtable)
31763183
{
31773184
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
31783185

31793186
++i;
3187+
if (!rte->inFromCl)
3188+
continue;
31803189
switch (rte->rtekind)
31813190
{
31823191
case RTE_RELATION:
@@ -3206,7 +3215,11 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
32063215
}
32073216
else
32083217
{
3209-
/* just the named tables */
3218+
/*
3219+
* Lock just the named tables. As above, we allow locking any base
3220+
* relation regardless of alias-visibility rules, so we need to
3221+
* examine inFromCl to exclude OLD/NEW.
3222+
*/
32103223
foreach(l, lockedRels)
32113224
{
32123225
RangeVar *thisrel = (RangeVar *) lfirst(l);
@@ -3227,6 +3240,8 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
32273240
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
32283241

32293242
++i;
3243+
if (!rte->inFromCl)
3244+
continue;
32303245
if (strcmp(rte->eref->aliasname, thisrel->relname) == 0)
32313246
{
32323247
switch (rte->rtekind)

src/include/nodes/parsenodes.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -929,10 +929,10 @@ typedef struct PartitionCmd
929929
* inFromCl marks those range variables that are listed in the FROM clause.
930930
* It's false for RTEs that are added to a query behind the scenes, such
931931
* as the NEW and OLD variables for a rule, or the subqueries of a UNION.
932-
* This flag is not used anymore during parsing, since the parser now uses
933-
* a separate "namespace" data structure to control visibility, but it is
934-
* needed by ruleutils.c to determine whether RTEs should be shown in
935-
* decompiled queries.
932+
* This flag is not used during parsing (except in transformLockingClause,
933+
* q.v.); the parser now uses a separate "namespace" data structure to
934+
* control visibility. But it is needed by ruleutils.c to determine
935+
* whether RTEs should be shown in decompiled queries.
936936
*
937937
* requiredPerms and checkAsUser specify run-time access permissions
938938
* 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
@@ -3026,6 +3026,31 @@ select * from only t1_2;
30263026
(10 rows)
30273027

30283028
reset constraint_exclusion;
3029+
-- test FOR UPDATE in rules
3030+
create table rules_base(f1 int, f2 int);
3031+
insert into rules_base values(1,2), (11,12);
3032+
create rule r1 as on update to rules_base do instead
3033+
select * from rules_base where f1 = 1 for update;
3034+
update rules_base set f2 = f2 + 1;
3035+
f1 | f2
3036+
----+----
3037+
1 | 2
3038+
(1 row)
3039+
3040+
create or replace rule r1 as on update to rules_base do instead
3041+
select * from rules_base where f1 = 11 for update of rules_base;
3042+
update rules_base set f2 = f2 + 1;
3043+
f1 | f2
3044+
----+----
3045+
11 | 12
3046+
(1 row)
3047+
3048+
create or replace rule r1 as on update to rules_base do instead
3049+
select * from rules_base where f1 = 11 for update of old; -- error
3050+
ERROR: relation "old" in FOR UPDATE clause not found in FROM clause
3051+
LINE 2: select * from rules_base where f1 = 11 for update of old;
3052+
^
3053+
drop table rules_base;
30293054
-- test various flavors of pg_get_viewdef()
30303055
select pg_get_viewdef('shoe'::regclass) as unpretty;
30313056
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)