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 );
2932static 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 */
109117static 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 */
167280static 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}
0 commit comments