Skip to content

Commit 4649003

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 18914f2 commit 4649003

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy