Skip to content

Commit 7fa367d

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 ecd4dd9 commit 7fa367d

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

27542754
if (lockedRels == NIL)
27552755
{
2756-
/* all regular tables used in query */
2756+
/*
2757+
* Lock all regular tables used in query and its subqueries. We
2758+
* examine inFromCl to exclude auto-added RTEs, particularly NEW/OLD
2759+
* in rules. This is a bit of an abuse of a mostly-obsolete flag, but
2760+
* it's convenient. We can't rely on the namespace mechanism that has
2761+
* largely replaced inFromCl, since for example we need to lock
2762+
* base-relation RTEs even if they are masked by upper joins.
2763+
*/
27572764
i = 0;
27582765
foreach(rt, qry->rtable)
27592766
{
27602767
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
27612768

27622769
++i;
2770+
if (!rte->inFromCl)
2771+
continue;
27632772
switch (rte->rtekind)
27642773
{
27652774
case RTE_RELATION:
@@ -2789,7 +2798,11 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
27892798
}
27902799
else
27912800
{
2792-
/* just the named tables */
2801+
/*
2802+
* Lock just the named tables. As above, we allow locking any base
2803+
* relation regardless of alias-visibility rules, so we need to
2804+
* examine inFromCl to exclude OLD/NEW.
2805+
*/
27932806
foreach(l, lockedRels)
27942807
{
27952808
RangeVar *thisrel = (RangeVar *) lfirst(l);
@@ -2810,6 +2823,8 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
28102823
RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
28112824

28122825
++i;
2826+
if (!rte->inFromCl)
2827+
continue;
28132828
if (strcmp(rte->eref->aliasname, thisrel->relname) == 0)
28142829
{
28152830
switch (rte->rtekind)

src/include/nodes/parsenodes.h

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

28892889
reset constraint_exclusion;
2890+
-- test FOR UPDATE in rules
2891+
create table rules_base(f1 int, f2 int);
2892+
insert into rules_base values(1,2), (11,12);
2893+
create rule r1 as on update to rules_base do instead
2894+
select * from rules_base where f1 = 1 for update;
2895+
update rules_base set f2 = f2 + 1;
2896+
f1 | f2
2897+
----+----
2898+
1 | 2
2899+
(1 row)
2900+
2901+
create or replace rule r1 as on update to rules_base do instead
2902+
select * from rules_base where f1 = 11 for update of rules_base;
2903+
update rules_base set f2 = f2 + 1;
2904+
f1 | f2
2905+
----+----
2906+
11 | 12
2907+
(1 row)
2908+
2909+
create or replace rule r1 as on update to rules_base do instead
2910+
select * from rules_base where f1 = 11 for update of old; -- error
2911+
ERROR: relation "old" in FOR UPDATE clause not found in FROM clause
2912+
LINE 2: select * from rules_base where f1 = 11 for update of old;
2913+
^
2914+
drop table rules_base;
28902915
-- test various flavors of pg_get_viewdef()
28912916
select pg_get_viewdef('shoe'::regclass) as unpretty;
28922917
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)