Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 34c20de

Browse files
committed
Allow to lock views.
Now all tables used in view definitions can be recursively locked by a LOCK command. Author: Yugo Nagata Reviewed by Robert Haas, Thomas Munro and me. Discussion: https://postgr.es/m/20171011183629.eb2817b3.nagata%40sraoss.co.jp
1 parent fb60478 commit 34c20de

File tree

4 files changed

+285
-18
lines changed

4 files changed

+285
-18
lines changed

doc/src/sgml/ref/lock.sgml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ LOCK [ TABLE ] [ ONLY ] <replaceable class="parameter">name</replaceable> [ * ]
4545
end.)
4646
</para>
4747

48+
<para>
49+
When a view is specified to be locked, all relations appearing in the view
50+
definition query are also locked recursively with the same lock mode.
51+
</para>
52+
4853
<para>
4954
When acquiring locks automatically for commands that reference
5055
tables, <productname>PostgreSQL</productname> always uses the least

src/backend/commands/lockcmds.c

Lines changed: 126 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,15 @@
2323
#include "utils/acl.h"
2424
#include "utils/lsyscache.h"
2525
#include "utils/syscache.h"
26+
#include "rewrite/rewriteHandler.h"
27+
#include "access/heapam.h"
28+
#include "nodes/nodeFuncs.h"
2629

27-
static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait);
28-
static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode);
30+
static void LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid);
31+
static AclResult LockTableAclCheck(Oid relid, LOCKMODE lockmode, Oid userid);
2932
static void RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid,
3033
Oid oldrelid, void *arg);
34+
static void LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait);
3135

3236
/*
3337
* LOCK TABLE
@@ -62,8 +66,10 @@ LockTableCommand(LockStmt *lockstmt)
6266
RangeVarCallbackForLockTable,
6367
(void *) &lockstmt->mode);
6468

65-
if (recurse)
66-
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
69+
if (get_rel_relkind(reloid) == RELKIND_VIEW)
70+
LockViewRecurse(reloid, reloid, lockstmt->mode, lockstmt->nowait);
71+
else if (recurse)
72+
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait, GetUserId());
6773
}
6874
}
6975

@@ -86,15 +92,17 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
8692
return; /* woops, concurrently dropped; no permissions
8793
* check */
8894

89-
/* Currently, we only allow plain tables to be locked */
90-
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE)
95+
96+
/* Currently, we only allow plain tables or views to be locked */
97+
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
98+
relkind != RELKIND_VIEW)
9199
ereport(ERROR,
92100
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
93-
errmsg("\"%s\" is not a table",
101+
errmsg("\"%s\" is not a table or a view",
94102
rv->relname)));
95103

96104
/* Check permissions. */
97-
aclresult = LockTableAclCheck(relid, lockmode);
105+
aclresult = LockTableAclCheck(relid, lockmode, GetUserId());
98106
if (aclresult != ACLCHECK_OK)
99107
aclcheck_error(aclresult, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
100108
}
@@ -107,7 +115,7 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
107115
* multiply-inheriting children more than once, but that's no problem.
108116
*/
109117
static void
110-
LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
118+
LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait, Oid userid)
111119
{
112120
List *children;
113121
ListCell *lc;
@@ -120,7 +128,7 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
120128
AclResult aclresult;
121129

122130
/* Check permissions before acquiring the lock. */
123-
aclresult = LockTableAclCheck(childreloid, lockmode);
131+
aclresult = LockTableAclCheck(childreloid, lockmode, userid);
124132
if (aclresult != ACLCHECK_OK)
125133
{
126134
char *relname = get_rel_name(childreloid);
@@ -157,15 +165,120 @@ LockTableRecurse(Oid reloid, LOCKMODE lockmode, bool nowait)
157165
continue;
158166
}
159167

160-
LockTableRecurse(childreloid, lockmode, nowait);
168+
LockTableRecurse(childreloid, lockmode, nowait, userid);
161169
}
162170
}
163171

172+
/*
173+
* Apply LOCK TABLE recursively over a view
174+
*
175+
* All tables and views appearing in the view definition query are locked
176+
* recursively with the same lock mode.
177+
*/
178+
179+
typedef struct
180+
{
181+
Oid root_reloid;
182+
LOCKMODE lockmode;
183+
bool nowait;
184+
Oid viewowner;
185+
Oid viewoid;
186+
} LockViewRecurse_context;
187+
188+
static bool
189+
LockViewRecurse_walker(Node *node, LockViewRecurse_context *context)
190+
{
191+
if (node == NULL)
192+
return false;
193+
194+
if (IsA(node, Query))
195+
{
196+
Query *query = (Query *) node;
197+
ListCell *rtable;
198+
199+
foreach(rtable, query->rtable)
200+
{
201+
RangeTblEntry *rte = lfirst(rtable);
202+
AclResult aclresult;
203+
204+
Oid relid = rte->relid;
205+
char relkind = rte->relkind;
206+
char *relname = get_rel_name(relid);
207+
208+
/* The OLD and NEW placeholder entries in the view's rtable are skipped. */
209+
if (relid == context->viewoid &&
210+
(!strcmp(rte->eref->aliasname, "old") || !strcmp(rte->eref->aliasname, "new")))
211+
continue;
212+
213+
/* Currently, we only allow plain tables or views to be locked. */
214+
if (relkind != RELKIND_RELATION && relkind != RELKIND_PARTITIONED_TABLE &&
215+
relkind != RELKIND_VIEW)
216+
continue;
217+
218+
/* Check infinite recursion in the view definition. */
219+
if (relid == context->root_reloid)
220+
ereport(ERROR,
221+
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
222+
errmsg("infinite recursion detected in rules for relation \"%s\"",
223+
get_rel_name(context->root_reloid))));
224+
225+
/* Check permissions with the view owner's privilege. */
226+
aclresult = LockTableAclCheck(relid, context->lockmode, context->viewowner);
227+
if (aclresult != ACLCHECK_OK)
228+
aclcheck_error(aclresult, get_relkind_objtype(relkind), relname);
229+
230+
/* We have enough rights to lock the relation; do so. */
231+
if (!context->nowait)
232+
LockRelationOid(relid, context->lockmode);
233+
else if (!ConditionalLockRelationOid(relid, context->lockmode))
234+
ereport(ERROR,
235+
(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
236+
errmsg("could not obtain lock on relation \"%s\"",
237+
relname)));
238+
239+
if (relkind == RELKIND_VIEW)
240+
LockViewRecurse(relid, context->root_reloid, context->lockmode, context->nowait);
241+
else if (rte->inh)
242+
LockTableRecurse(relid, context->lockmode, context->nowait, context->viewowner);
243+
}
244+
245+
return query_tree_walker(query,
246+
LockViewRecurse_walker,
247+
context,
248+
QTW_IGNORE_JOINALIASES);
249+
}
250+
251+
return expression_tree_walker(node,
252+
LockViewRecurse_walker,
253+
context);
254+
}
255+
256+
static void
257+
LockViewRecurse(Oid reloid, Oid root_reloid, LOCKMODE lockmode, bool nowait)
258+
{
259+
LockViewRecurse_context context;
260+
261+
Relation view;
262+
Query *viewquery;
263+
264+
view = heap_open(reloid, NoLock);
265+
viewquery = get_view_query(view);
266+
heap_close(view, NoLock);
267+
268+
context.root_reloid = root_reloid;
269+
context.lockmode = lockmode;
270+
context.nowait = nowait;
271+
context.viewowner = view->rd_rel->relowner;
272+
context.viewoid = reloid;
273+
274+
LockViewRecurse_walker((Node *) viewquery, &context);
275+
}
276+
164277
/*
165278
* Check whether the current user is permitted to lock this relation.
166279
*/
167280
static AclResult
168-
LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
281+
LockTableAclCheck(Oid reloid, LOCKMODE lockmode, Oid userid)
169282
{
170283
AclResult aclresult;
171284
AclMode aclmask;
@@ -178,7 +291,7 @@ LockTableAclCheck(Oid reloid, LOCKMODE lockmode)
178291
else
179292
aclmask = ACL_UPDATE | ACL_DELETE | ACL_TRUNCATE;
180293

181-
aclresult = pg_class_aclcheck(reloid, GetUserId(), aclmask);
294+
aclresult = pg_class_aclcheck(reloid, userid, aclmask);
182295

183296
return aclresult;
184297
}

src/test/regress/expected/lock.out

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@
55
CREATE SCHEMA lock_schema1;
66
SET search_path = lock_schema1;
77
CREATE TABLE lock_tbl1 (a BIGINT);
8-
CREATE VIEW lock_view1 AS SELECT 1;
8+
CREATE TABLE lock_tbl1a (a BIGINT);
9+
CREATE VIEW lock_view1 AS SELECT * FROM lock_tbl1;
10+
CREATE VIEW lock_view2(a,b) AS SELECT * FROM lock_tbl1, lock_tbl1a;
11+
CREATE VIEW lock_view3 AS SELECT * from lock_view2;
12+
CREATE VIEW lock_view4 AS SELECT (select a from lock_tbl1a limit 1) from lock_tbl1;
13+
CREATE VIEW lock_view5 AS SELECT * from lock_tbl1 where a in (select * from lock_tbl1a);
14+
CREATE VIEW lock_view6 AS SELECT * from (select * from lock_tbl1) sub;
915
CREATE ROLE regress_rol_lock1;
1016
ALTER ROLE regress_rol_lock1 SET search_path = lock_schema1;
1117
GRANT USAGE ON SCHEMA lock_schema1 TO regress_rol_lock1;
@@ -30,8 +36,90 @@ LOCK TABLE lock_tbl1 IN SHARE MODE NOWAIT;
3036
LOCK TABLE lock_tbl1 IN SHARE ROW EXCLUSIVE MODE NOWAIT;
3137
LOCK TABLE lock_tbl1 IN EXCLUSIVE MODE NOWAIT;
3238
LOCK TABLE lock_tbl1 IN ACCESS EXCLUSIVE MODE NOWAIT;
33-
LOCK TABLE lock_view1 IN EXCLUSIVE MODE; -- Will fail; can't lock a non-table
34-
ERROR: "lock_view1" is not a table
39+
ROLLBACK;
40+
-- Verify that we can lock views.
41+
BEGIN TRANSACTION;
42+
LOCK TABLE lock_view1 IN EXCLUSIVE MODE;
43+
-- lock_view1 and lock_tbl1 are locked.
44+
select relname from pg_locks l, pg_class c
45+
where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
46+
order by relname;
47+
relname
48+
------------
49+
lock_tbl1
50+
lock_view1
51+
(2 rows)
52+
53+
ROLLBACK;
54+
BEGIN TRANSACTION;
55+
LOCK TABLE lock_view2 IN EXCLUSIVE MODE;
56+
-- lock_view1, lock_tbl1, and lock_tbl1a are locked.
57+
select relname from pg_locks l, pg_class c
58+
where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
59+
order by relname;
60+
relname
61+
------------
62+
lock_tbl1
63+
lock_tbl1a
64+
lock_view2
65+
(3 rows)
66+
67+
ROLLBACK;
68+
BEGIN TRANSACTION;
69+
LOCK TABLE lock_view3 IN EXCLUSIVE MODE;
70+
-- lock_view3, lock_view2, lock_tbl1, and lock_tbl1a are locked recursively.
71+
select relname from pg_locks l, pg_class c
72+
where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
73+
order by relname;
74+
relname
75+
------------
76+
lock_tbl1
77+
lock_tbl1a
78+
lock_view2
79+
lock_view3
80+
(4 rows)
81+
82+
ROLLBACK;
83+
BEGIN TRANSACTION;
84+
LOCK TABLE lock_view4 IN EXCLUSIVE MODE;
85+
-- lock_view4, lock_tbl1, and lock_tbl1a are locked.
86+
select relname from pg_locks l, pg_class c
87+
where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
88+
order by relname;
89+
relname
90+
------------
91+
lock_tbl1
92+
lock_tbl1a
93+
lock_view4
94+
(3 rows)
95+
96+
ROLLBACK;
97+
BEGIN TRANSACTION;
98+
LOCK TABLE lock_view5 IN EXCLUSIVE MODE;
99+
-- lock_view5, lock_tbl1, and lock_tbl1a are locked.
100+
select relname from pg_locks l, pg_class c
101+
where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
102+
order by relname;
103+
relname
104+
------------
105+
lock_tbl1
106+
lock_tbl1a
107+
lock_view5
108+
(3 rows)
109+
110+
ROLLBACK;
111+
BEGIN TRANSACTION;
112+
LOCK TABLE lock_view6 IN EXCLUSIVE MODE;
113+
-- lock_view6 an lock_tbl1 are locked.
114+
select relname from pg_locks l, pg_class c
115+
where l.relation = c.oid and relname like '%lock_%' and mode = 'ExclusiveLock'
116+
order by relname;
117+
relname
118+
------------
119+
lock_tbl1
120+
lock_view6
121+
(2 rows)
122+
35123
ROLLBACK;
36124
-- Verify that we can lock a table with inheritance children.
37125
CREATE TABLE lock_tbl2 (b BIGINT) INHERITS (lock_tbl1);
@@ -54,10 +142,16 @@ RESET ROLE;
54142
--
55143
-- Clean up
56144
--
145+
DROP VIEW lock_view6;
146+
DROP VIEW lock_view5;
147+
DROP VIEW lock_view4;
148+
DROP VIEW lock_view3;
149+
DROP VIEW lock_view2;
57150
DROP VIEW lock_view1;
58151
DROP TABLE lock_tbl3;
59152
DROP TABLE lock_tbl2;
60153
DROP TABLE lock_tbl1;
154+
DROP TABLE lock_tbl1a;
61155
DROP SCHEMA lock_schema1 CASCADE;
62156
DROP ROLE regress_rol_lock1;
63157
-- atomic ops tests

0 commit comments

Comments
 (0)