Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

+5
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

+126-13
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

+97-3
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)