Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions regress/expected/drop.out
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'ag_catalog';
-----------
(0 rows)

-- When ag_catalog is missing extension hooks shouldn't fail with the
-- ERROR schema "ag_catalog" does not exist.
-- It might happen when 'age' is loaded but extension isn't created yet.
SET client_min_messages TO WARNING;
DROP SCHEMA IF EXISTS ag_catalog CASCADE;
RESET client_min_messages;
CREATE SCHEMA _regress_drop;
DROP SCHEMA _regress_drop; -- should'n produce the ERROR
-- Recreate the extension and validate we can recreate a graph
CREATE EXTENSION age;
SELECT create_graph('drop');
Expand Down Expand Up @@ -115,7 +123,7 @@ NOTICE: label "issue_1305"."r" has been dropped
(1 row)

SELECT drop_label('issue_1305', 'r');
ERROR: rel_name not found for label "r"
ERROR: label "r" does not exist
SELECT drop_label('issue_1305', 'n', false);
NOTICE: label "issue_1305"."n" has been dropped
drop_label
Expand All @@ -124,7 +132,7 @@ NOTICE: label "issue_1305"."n" has been dropped
(1 row)

SELECT drop_label('issue_1305', 'n');
ERROR: rel_name not found for label "n"
ERROR: label "n" does not exist
SELECT * FROM drop_graph('issue_1305', true);
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to table issue_1305._ag_label_vertex
Expand Down
9 changes: 9 additions & 0 deletions regress/sql/drop.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname = 'drop';

SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname = 'ag_catalog';

-- When ag_catalog is missing extension hooks shouldn't fail with the
-- ERROR schema "ag_catalog" does not exist.
-- It might happen when 'age' is loaded but extension isn't created yet.
SET client_min_messages TO WARNING;
DROP SCHEMA IF EXISTS ag_catalog CASCADE;
RESET client_min_messages;
CREATE SCHEMA _regress_drop;
DROP SCHEMA _regress_drop; -- should'n produce the ERROR

-- Recreate the extension and validate we can recreate a graph
CREATE EXTENSION age;

Expand Down
9 changes: 8 additions & 1 deletion src/backend/age.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
* under the License.
*/

#include "postgres.h"
#include "miscadmin.h"

#include "catalog/ag_catalog.h"
#include "nodes/ag_nodes.h"
#include "optimizer/cypher_paths.h"
Expand All @@ -25,7 +28,6 @@
#include "utils/age_global_graph.h"

#if PG_VERSION_NUM < 170000
#include "miscadmin.h"

/* saved hook pointers for PG < 17 shmem path */
static shmem_request_hook_type prev_shmem_request_hook = NULL;
Expand Down Expand Up @@ -56,6 +58,11 @@ void _PG_init(void);

void _PG_init(void)
{
if (IsBinaryUpgrade)
{
return;
Comment thread
serdarmumcu marked this conversation as resolved.
}

register_ag_nodes();
set_rel_pathlist_init();
object_access_hook_init();
Expand Down
216 changes: 159 additions & 57 deletions src/backend/catalog/ag_catalog.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,27 @@

#include "postgres.h"

#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_class_d.h"
#include "catalog/pg_extension_d.h"
#include "catalog/pg_namespace_d.h"
#include "commands/defrem.h"
#include "commands/extension.h"
#include "nodes/parsenodes.h"
#include "tcop/utility.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"

#include "catalog/ag_graph.h"
#include "catalog/ag_label.h"
#include "utils/ag_cache.h"
#include "utils/age_global_graph.h"

static bool extension_cache_is_valid = false;
static bool age_extension_exists = false;
static object_access_hook_type prev_object_access_hook;
static ProcessUtility_hook_type prev_process_utility_hook;
static bool prev_object_hook_is_set;
Expand All @@ -45,8 +51,46 @@ void ag_ProcessUtility_hook(PlannedStmt *pstmt, const char *queryString, bool re
QueryEnvironment *queryEnv, DestReceiver *dest,
QueryCompletion *qc);

static bool is_age_drop(PlannedStmt *pstmt);
static void drop_age_extension(DropStmt *stmt);
static bool is_age_drop(DropStmt *drop_stmt);

static void
invalidate_extension_cache_callback(Datum argument, Oid relationId)
{
if (!OidIsValid(relationId) || relationId == ExtensionRelationId)
{
extension_cache_is_valid = false;
Comment thread
serdarmumcu marked this conversation as resolved.
}
}

/*
* We don't want most of hooks to do anything if the "age" extension isn't
* created. However, scanning pg_extension is a costly operation, therefore we
* implement a caching mechanism and reset it with the help of the relcache
* callback mechanism.
*
* Please also see ag_ProcessUtility_hook() function for more details.
*/
bool
is_age_extension_exists(void)
{
static bool callback_registered = false;

if (extension_cache_is_valid)
return age_extension_exists;

if (!callback_registered)
{
CacheRegisterRelcacheCallback(invalidate_extension_cache_callback,
(Datum) 0);
callback_registered = true;
}

age_extension_exists = OidIsValid(get_extension_oid("age", true));

extension_cache_is_valid = true;

return age_extension_exists;
}

void object_access_hook_init(void)
{
Expand Down Expand Up @@ -86,50 +130,97 @@ void process_utility_hook_fini(void)
* information in the indexes and tables being dropped. To prevent an error
* from being thrown, we need to disable the object_access_hook before dropping
* the extension.
*
* Besides that, we want to notify other backends about the fact that "age"
* extension was probably created/dropped so that they can enable/disable
* hooks.
*/
void ag_ProcessUtility_hook(PlannedStmt *pstmt, const char *queryString,
bool readOnlyTree, ProcessUtilityContext context,
ParamListInfo params, QueryEnvironment *queryEnv,
DestReceiver *dest, QueryCompletion *qc)
{
if (is_age_drop(pstmt))
{
drop_age_extension((DropStmt *)pstmt->utilityStmt);
}
else
bool creating_age = false;
bool dropping_age = false;

if (!IsAbortedTransactionBlockState())
{
/*
* Check for TRUNCATE on graph label tables. If any truncated
* table is a graph label table, increment the version counter
* for that graph to invalidate VLE caches. We do this before
* the truncate executes so the cache is invalidated regardless.
*/
if (IsA(pstmt->utilityStmt, TruncateStmt))
Node *parsetree = pstmt->utilityStmt;

switch (nodeTag(parsetree))
{
TruncateStmt *tstmt = (TruncateStmt *) pstmt->utilityStmt;
ListCell *lc;
case T_CreateExtensionStmt:
{
CreateExtensionStmt *stmt =
(CreateExtensionStmt *) parsetree;
creating_age = strcmp(stmt->extname, "age") == 0;
}
break;
case T_DropStmt:
{
DropStmt *stmt = (DropStmt *) parsetree;

foreach(lc, tstmt->relations)
{
RangeVar *rv = (RangeVar *) lfirst(lc);
Oid rel_oid = RangeVarGetRelid(rv, AccessShareLock, true);
if (stmt->removeType != OBJECT_EXTENSION)
break;

if (OidIsValid(rel_oid))
{
Oid graph_oid = get_graph_oid_for_table(rel_oid);
if (!is_age_drop(stmt))
break;

if (OidIsValid(graph_oid))
dropping_age = true;
}
break;
case T_TruncateStmt:
{
/*
* Check for TRUNCATE on graph label tables. If any
* truncated table is a graph label table, increment the
* version counter for that graph to invalidate VLE caches.
* We do this before the truncate executes so the cache is
* invalidated regardless.
*/
TruncateStmt *tstmt = (TruncateStmt *) parsetree;
ListCell *lc;

foreach(lc, tstmt->relations)
{
increment_graph_version(graph_oid);
RangeVar *rv = (RangeVar *) lfirst(lc);
Oid rel_oid = RangeVarGetRelid(rv, AccessShareLock,
true);

if (OidIsValid(rel_oid))
{
Oid graph_oid =
get_graph_oid_for_table(rel_oid);

if (OidIsValid(graph_oid))
{
increment_graph_version(graph_oid);
}
}
}
}
}
break;
default:
break;
}
}

if (dropping_age)
{
/* Remove all graphs */
drop_graphs(get_graphnames());

/* Remove the object access hook */
object_access_hook_fini();
}

PG_TRY();
{
if (prev_process_utility_hook)
{
(*prev_process_utility_hook) (pstmt, queryString, readOnlyTree,
context, params, queryEnv, dest, qc);
context, params, queryEnv, dest,
qc);
}
else
{
Expand All @@ -141,38 +232,47 @@ void ag_ProcessUtility_hook(PlannedStmt *pstmt, const char *queryString,
params, queryEnv, dest, qc);
}
}
}

static void drop_age_extension(DropStmt *stmt)
{
/* Remove all graphs */
drop_graphs(get_graphnames());
PG_CATCH();
{
if (dropping_age)
{
/*
* We have to restore the disabled object_access_hook if
* DROP EXTENSION age failed.
*/
object_access_hook_init();
}
PG_RE_THROW();
}
PG_END_TRY();

/* Remove the object access hook */
object_access_hook_fini();
if (dropping_age)
{
/* reset global variables for OIDs */
clear_global_Oids_AGTYPE();
clear_global_Oids_GRAPHID();
clear_global_Oids_VERTEX_EDGE();

/*
* Run Postgres' logic to perform the remaining work to drop the
* extension.
*/
RemoveObjects(stmt);
/* Restore the object access hook */
object_access_hook_init();
}

/* reset global variables for OIDs */
clear_global_Oids_AGTYPE();
clear_global_Oids_GRAPHID();
clear_global_Oids_VERTEX_EDGE();
if (creating_age || dropping_age)
{
/* Notify all backends that pg_extension was modified. */
CacheInvalidateRelcacheByRelid(ExtensionRelationId);
}
}

/* Check to see if the Utility Command is to drop the AGE Extension. */
static bool is_age_drop(PlannedStmt *pstmt)
static bool is_age_drop(DropStmt *drop_stmt)
{
ListCell *lc;
DropStmt *drop_stmt;

if (!IsA(pstmt->utilityStmt, DropStmt))
if (!is_age_extension_exists())
{
return false;
Comment thread
serdarmumcu marked this conversation as resolved.

drop_stmt = (DropStmt *)pstmt->utilityStmt;
}

foreach(lc, drop_stmt->objects)
{
Expand All @@ -183,8 +283,10 @@ static bool is_age_drop(PlannedStmt *pstmt)
String *val = (String *)obj;
char *str = val->sval;

if (!pg_strcasecmp(str, "age"))
if (strcmp(str, "age") == 0)
{
return true;
Comment thread
serdarmumcu marked this conversation as resolved.
}
}
}

Expand All @@ -205,16 +307,16 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id,
if (prev_object_access_hook)
prev_object_access_hook(access, class_id, object_id, sub_id, arg);

/* We are interested in DROP SCHEMA and DROP TABLE commands. */
if (access != OAT_DROP)
if (!is_age_extension_exists())
{
return;
}

/*
* Age might be installed into shared_preload_libraries before extension is
* created. In this case we must bail out from this hook.
*/
if (!OidIsValid(get_namespace_oid("ag_catalog", true)))
/* We are interested in DROP SCHEMA and DROP TABLE commands. */
if (access != OAT_DROP)
{
return;
Comment thread
serdarmumcu marked this conversation as resolved.
}

drop_arg = arg;

Expand Down
Loading