1919
2020#include "postgres.h"
2121
22+ #include "access/xact.h"
2223#include "catalog/dependency.h"
2324#include "catalog/namespace.h"
2425#include "catalog/objectaccess.h"
2526#include "catalog/pg_class_d.h"
27+ #include "catalog/pg_extension_d.h"
2628#include "catalog/pg_namespace_d.h"
2729#include "commands/defrem.h"
30+ #include "commands/extension.h"
2831#include "nodes/parsenodes.h"
2932#include "tcop/utility.h"
33+ #include "utils/inval.h"
3034#include "utils/lsyscache.h"
3135
3236#include "catalog/ag_graph.h"
3337#include "catalog/ag_label.h"
3438#include "utils/ag_cache.h"
3539#include "utils/age_global_graph.h"
3640
41+ static bool extension_cache_is_valid = false;
42+ static bool age_extension_exists = false;
3743static object_access_hook_type prev_object_access_hook ;
3844static ProcessUtility_hook_type prev_process_utility_hook ;
3945static bool prev_object_hook_is_set ;
@@ -45,8 +51,44 @@ void ag_ProcessUtility_hook(PlannedStmt *pstmt, const char *queryString, bool re
4551 QueryEnvironment * queryEnv , DestReceiver * dest ,
4652 QueryCompletion * qc );
4753
48- static bool is_age_drop (PlannedStmt * pstmt );
49- static void drop_age_extension (DropStmt * stmt );
54+ static bool is_age_drop (DropStmt * drop_stmt );
55+
56+ static void
57+ invalidate_extension_cache_callback (Datum argument , Oid relationId )
58+ {
59+ if (relationId == ExtensionRelationId )
60+ extension_cache_is_valid = false;
61+ }
62+
63+ /*
64+ * We don't want most of hooks to do anything if the "age" extension isn't
65+ * created. However, scanning pg_extension is a costly operation, therefore we
66+ * implement a caching mechanism and reset it with the help of the relcache
67+ * callback mechanism.
68+ *
69+ * Please also see ag_ProcessUtility_hook() function for more details.
70+ */
71+ bool
72+ is_age_extension_exist (void )
73+ {
74+ static bool callback_registered = false;
75+
76+ if (extension_cache_is_valid )
77+ return age_extension_exists ;
78+
79+ if (!callback_registered )
80+ {
81+ CacheRegisterRelcacheCallback (invalidate_extension_cache_callback ,
82+ (Datum ) 0 );
83+ callback_registered = true;
84+ }
85+
86+ age_extension_exists = OidIsValid (get_extension_oid ("age" , true));
87+
88+ extension_cache_is_valid = true;
89+
90+ return age_extension_exists ;
91+ }
5092
5193void object_access_hook_init (void )
5294{
@@ -86,50 +128,97 @@ void process_utility_hook_fini(void)
86128 * information in the indexes and tables being dropped. To prevent an error
87129 * from being thrown, we need to disable the object_access_hook before dropping
88130 * the extension.
131+ *
132+ * Besides that, we want to notify other backends about the fact that "age"
133+ * extension was probably created/dropped so that they can enable/disable
134+ * hooks.
89135 */
90136void ag_ProcessUtility_hook (PlannedStmt * pstmt , const char * queryString ,
91137 bool readOnlyTree , ProcessUtilityContext context ,
92138 ParamListInfo params , QueryEnvironment * queryEnv ,
93139 DestReceiver * dest , QueryCompletion * qc )
94140{
95- if (is_age_drop (pstmt ))
96- {
97- drop_age_extension ((DropStmt * )pstmt -> utilityStmt );
98- }
99- else
141+ bool creating_age = false;
142+ bool dropping_age = false;
143+
144+ if (!IsAbortedTransactionBlockState ())
100145 {
101- /*
102- * Check for TRUNCATE on graph label tables. If any truncated
103- * table is a graph label table, increment the version counter
104- * for that graph to invalidate VLE caches. We do this before
105- * the truncate executes so the cache is invalidated regardless.
106- */
107- if (IsA (pstmt -> utilityStmt , TruncateStmt ))
146+ Node * parsetree = pstmt -> utilityStmt ;
147+
148+ switch (nodeTag (parsetree ))
108149 {
109- TruncateStmt * tstmt = (TruncateStmt * ) pstmt -> utilityStmt ;
110- ListCell * lc ;
150+ case T_CreateExtensionStmt :
151+ {
152+ CreateExtensionStmt * stmt =
153+ (CreateExtensionStmt * ) parsetree ;
154+ creating_age = strcmp (stmt -> extname , "age" ) == 0 ;
155+ }
156+ break ;
157+ case T_DropStmt :
158+ {
159+ DropStmt * stmt = (DropStmt * ) parsetree ;
111160
112- foreach (lc , tstmt -> relations )
113- {
114- RangeVar * rv = (RangeVar * ) lfirst (lc );
115- Oid rel_oid = RangeVarGetRelid (rv , AccessShareLock , true);
161+ if (stmt -> removeType != OBJECT_EXTENSION )
162+ break ;
116163
117- if (OidIsValid (rel_oid ))
118- {
119- Oid graph_oid = get_graph_oid_for_table (rel_oid );
164+ if (!is_age_drop (stmt ))
165+ break ;
120166
121- if (OidIsValid (graph_oid ))
167+ dropping_age = true;
168+ }
169+ break ;
170+ case T_TruncateStmt :
171+ {
172+ /*
173+ * Check for TRUNCATE on graph label tables. If any
174+ * truncated table is a graph label table, increment the
175+ * version counter for that graph to invalidate VLE caches.
176+ * We do this before the truncate executes so the cache is
177+ * invalidated regardless.
178+ */
179+ TruncateStmt * tstmt = (TruncateStmt * ) parsetree ;
180+ ListCell * lc ;
181+
182+ foreach (lc , tstmt -> relations )
122183 {
123- increment_graph_version (graph_oid );
184+ RangeVar * rv = (RangeVar * ) lfirst (lc );
185+ Oid rel_oid = RangeVarGetRelid (rv , AccessShareLock ,
186+ true);
187+
188+ if (OidIsValid (rel_oid ))
189+ {
190+ Oid graph_oid =
191+ get_graph_oid_for_table (rel_oid );
192+
193+ if (OidIsValid (graph_oid ))
194+ {
195+ increment_graph_version (graph_oid );
196+ }
197+ }
124198 }
125199 }
126- }
200+ break ;
201+ default :
202+ break ;
127203 }
204+ }
128205
206+ if (dropping_age )
207+ {
208+ /* Remove all graphs */
209+ drop_graphs (get_graphnames ());
210+
211+ /* Remove the object access hook */
212+ object_access_hook_fini ();
213+ }
214+
215+ PG_TRY ();
216+ {
129217 if (prev_process_utility_hook )
130218 {
131219 (* prev_process_utility_hook ) (pstmt , queryString , readOnlyTree ,
132- context , params , queryEnv , dest , qc );
220+ context , params , queryEnv , dest ,
221+ qc );
133222 }
134223 else
135224 {
@@ -141,39 +230,46 @@ void ag_ProcessUtility_hook(PlannedStmt *pstmt, const char *queryString,
141230 params , queryEnv , dest , qc );
142231 }
143232 }
144- }
145-
146- static void drop_age_extension (DropStmt * stmt )
147- {
148- /* Remove all graphs */
149- drop_graphs (get_graphnames ());
233+ PG_CATCH ();
234+ {
235+ if (dropping_age )
236+ {
237+ /*
238+ * We have to restore the disabled object_access_hook if
239+ * DROP EXTENSION age failed.
240+ */
241+ object_access_hook_init ();
242+ }
243+ PG_RE_THROW ();
244+ }
245+ PG_END_TRY ();
150246
151- /* Remove the object access hook */
152- object_access_hook_fini ();
247+ if (dropping_age )
248+ {
249+ /* reset global variables for OIDs */
250+ clear_global_Oids_AGTYPE ();
251+ clear_global_Oids_GRAPHID ();
252+ clear_global_Oids_VERTEX_EDGE ();
153253
154- /*
155- * Run Postgres' logic to perform the remaining work to drop the
156- * extension.
157- */
158- RemoveObjects (stmt );
254+ /* Restore the object access hook */
255+ object_access_hook_init ();
256+ }
159257
160- /* reset global variables for OIDs */
161- clear_global_Oids_AGTYPE ();
162- clear_global_Oids_GRAPHID ();
163- clear_global_Oids_VERTEX_EDGE ();
258+ if (creating_age || dropping_age )
259+ {
260+ /* Notify all backends that pg_extension was modified. */
261+ CacheInvalidateRelcacheByRelid (ExtensionRelationId );
262+ }
164263}
165264
166265/* Check to see if the Utility Command is to drop the AGE Extension. */
167- static bool is_age_drop (PlannedStmt * pstmt )
266+ static bool is_age_drop (DropStmt * drop_stmt )
168267{
169268 ListCell * lc ;
170- DropStmt * drop_stmt ;
171269
172- if (!IsA ( pstmt -> utilityStmt , DropStmt ))
270+ if (!is_age_extension_exist ( ))
173271 return false;
174272
175- drop_stmt = (DropStmt * )pstmt -> utilityStmt ;
176-
177273 foreach (lc , drop_stmt -> objects )
178274 {
179275 Node * obj = lfirst (lc );
@@ -183,7 +279,7 @@ static bool is_age_drop(PlannedStmt *pstmt)
183279 String * val = (String * )obj ;
184280 char * str = val -> sval ;
185281
186- if (! pg_strcasecmp (str , "age" ))
282+ if (strcmp (str , "age" ) == 0 )
187283 return true;
188284 }
189285 }
@@ -205,15 +301,11 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id,
205301 if (prev_object_access_hook )
206302 prev_object_access_hook (access , class_id , object_id , sub_id , arg );
207303
208- /* We are interested in DROP SCHEMA and DROP TABLE commands. */
209- if (access != OAT_DROP )
304+ if (!is_age_extension_exist ())
210305 return ;
211306
212- /*
213- * Age might be installed into shared_preload_libraries before extension is
214- * created. In this case we must bail out from this hook.
215- */
216- if (!OidIsValid (get_namespace_oid ("ag_catalog" , true)))
307+ /* We are interested in DROP SCHEMA and DROP TABLE commands. */
308+ if (access != OAT_DROP )
217309 return ;
218310
219311 drop_arg = arg ;
0 commit comments