mirror of https://github.com/citusdata/citus.git
839 lines
24 KiB
C
839 lines
24 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* extension.c
|
|
* Commands for creating and altering extensions.
|
|
*
|
|
* Copyright (c) Citus Data, Inc.
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "citus_version.h"
|
|
#include "catalog/pg_extension_d.h"
|
|
#include "commands/extension.h"
|
|
#include "distributed/citus_ruleutils.h"
|
|
#include "distributed/commands.h"
|
|
#include "distributed/commands/utility_hook.h"
|
|
#include "distributed/deparser.h"
|
|
#include "distributed/metadata_sync.h"
|
|
#include "distributed/metadata/distobject.h"
|
|
#include "distributed/multi_executor.h"
|
|
#include "distributed/relation_access_tracking.h"
|
|
#include "distributed/transaction_management.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/builtins.h"
|
|
|
|
|
|
/* Local functions forward declarations for helper functions */
|
|
static char * ExtractNewExtensionVersion(Node *parseTree);
|
|
static void AddSchemaFieldIfMissing(CreateExtensionStmt *stmt);
|
|
static List * FilterDistributedExtensions(List *extensionObjectList);
|
|
static List * ExtensionNameListToObjectAddressList(List *extensionObjectList);
|
|
static void EnsureSequentialModeForExtensionDDL(void);
|
|
static bool ShouldPropagateExtensionCommand(Node *parseTree);
|
|
static bool IsDropCitusStmt(Node *parseTree);
|
|
static bool IsAlterExtensionSetSchemaCitus(Node *parseTree);
|
|
static Node * RecreateExtensionStmt(Oid extensionOid);
|
|
|
|
|
|
/*
|
|
* ErrorIfUnstableCreateOrAlterExtensionStmt compares CITUS_EXTENSIONVERSION
|
|
* and version given CREATE/ALTER EXTENSION statement will create/update to. If
|
|
* they are not same in major or minor version numbers, this function errors
|
|
* out. It ignores the schema version.
|
|
*/
|
|
void
|
|
ErrorIfUnstableCreateOrAlterExtensionStmt(Node *parseTree)
|
|
{
|
|
char *newExtensionVersion = ExtractNewExtensionVersion(parseTree);
|
|
|
|
if (newExtensionVersion != NULL)
|
|
{
|
|
/* explicit version provided in CREATE or ALTER EXTENSION UPDATE; verify */
|
|
if (!MajorVersionsCompatible(newExtensionVersion, CITUS_EXTENSIONVERSION))
|
|
{
|
|
ereport(ERROR, (errmsg("specified version incompatible with loaded "
|
|
"Citus library"),
|
|
errdetail("Loaded library requires %s, but %s was specified.",
|
|
CITUS_MAJORVERSION, newExtensionVersion),
|
|
errhint("If a newer library is present, restart the database "
|
|
"and try the command again.")));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* No version was specified, so PostgreSQL will use the default_version
|
|
* from the citus.control file.
|
|
*/
|
|
CheckAvailableVersion(ERROR);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ExtractNewExtensionVersion returns the palloc'd new extension version specified
|
|
* by a CREATE or ALTER EXTENSION statement. Other inputs are not permitted.
|
|
* This function returns NULL for statements with no explicit version specified.
|
|
*/
|
|
static char *
|
|
ExtractNewExtensionVersion(Node *parseTree)
|
|
{
|
|
Value *newVersionValue = NULL;
|
|
|
|
List *optionsList = NIL;
|
|
|
|
if (IsA(parseTree, CreateExtensionStmt))
|
|
{
|
|
optionsList = ((CreateExtensionStmt *) parseTree)->options;
|
|
}
|
|
else if (IsA(parseTree, AlterExtensionStmt))
|
|
{
|
|
optionsList = ((AlterExtensionStmt *) parseTree)->options;
|
|
}
|
|
else
|
|
{
|
|
/* input must be one of the two above types */
|
|
Assert(false);
|
|
}
|
|
|
|
newVersionValue = GetExtensionOption(optionsList, "new_version");
|
|
|
|
/* return target string safely */
|
|
if (newVersionValue)
|
|
{
|
|
const char *newVersion = strVal(newVersionValue);
|
|
|
|
return pstrdup(newVersion);
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* PlanCreateExtensionStmt is called during the creation of an extension.
|
|
* It is executed before the statement is applied locally.
|
|
* We decide if the extension needs to be replicated to the worker, and
|
|
* if that is the case return a list of DDLJob's that describe how and
|
|
* where the extension needs to be created.
|
|
*/
|
|
List *
|
|
PlanCreateExtensionStmt(CreateExtensionStmt *createExtensionStmt, const char *queryString)
|
|
{
|
|
List *commands = NIL;
|
|
const char *createExtensionStmtSql = NULL;
|
|
|
|
if (!ShouldPropagateExtensionCommand((Node *) createExtensionStmt))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/*
|
|
* If the extension command is a part of a bigger multi-statement transaction,
|
|
* do not propagate it
|
|
*/
|
|
if (IsMultiStatementTransaction())
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* extension management can only be done via coordinator node */
|
|
EnsureCoordinator();
|
|
|
|
/*
|
|
* Make sure that no new nodes are added after this point until the end of the
|
|
* transaction by taking a RowShareLock on pg_dist_node, which conflicts with the
|
|
* ExclusiveLock taken by master_add_node.
|
|
* This guarantees that all active nodes will have the extension, because they will
|
|
* either get it now, or get it in master_add_node after this transaction finishes and
|
|
* the pg_dist_object record becomes visible.
|
|
*/
|
|
LockRelationOid(DistNodeRelationId(), RowShareLock);
|
|
|
|
/*
|
|
* Make sure that the current transaction is already in sequential mode,
|
|
* or can still safely be put in sequential mode
|
|
*/
|
|
EnsureSequentialModeForExtensionDDL();
|
|
|
|
/*
|
|
* Here we append "schema" field to the "options" list (if not specified)
|
|
* to satisfy the schema consistency between worker nodes and the coordinator.
|
|
*/
|
|
AddSchemaFieldIfMissing(createExtensionStmt);
|
|
|
|
createExtensionStmtSql = DeparseTreeNode((Node *) createExtensionStmt);
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) createExtensionStmtSql,
|
|
ENABLE_DDL_PROPAGATION);
|
|
|
|
return NodeDDLTaskList(ALL_WORKERS, commands);
|
|
}
|
|
|
|
|
|
/*
|
|
* AddSchemaFieldIfMissing adds DefElem item for "schema" (if not specified
|
|
* in statement) to "options" list before deparsing the statement to satisfy
|
|
* the schema consistency between worker nodes and the coordinator.
|
|
*/
|
|
static void
|
|
AddSchemaFieldIfMissing(CreateExtensionStmt *createExtensionStmt)
|
|
{
|
|
List *optionsList = createExtensionStmt->options;
|
|
|
|
Value *schemaNameValue = GetExtensionOption(optionsList, "schema");
|
|
|
|
if (!schemaNameValue)
|
|
{
|
|
/*
|
|
* As we already created the extension by standard_ProcessUtility,
|
|
* we actually know the schema it belongs to
|
|
*/
|
|
bool missingOk = false;
|
|
Oid extensionOid = get_extension_oid(createExtensionStmt->extname, missingOk);
|
|
Oid extensionSchemaOid = get_extension_schema(extensionOid);
|
|
char *extensionSchemaName = get_namespace_name(extensionSchemaOid);
|
|
|
|
Node *schemaNameArg = (Node *) makeString(extensionSchemaName);
|
|
|
|
/* set location to -1 as it is unknown */
|
|
int location = -1;
|
|
|
|
DefElem *newDefElement = makeDefElem("schema", schemaNameArg, location);
|
|
|
|
createExtensionStmt->options = lappend(createExtensionStmt->options,
|
|
newDefElement);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* ProcessCreateExtensionStmt is executed after the extension has been
|
|
* created locally and before we create it on the worker nodes.
|
|
* As we now have access to ObjectAddress of the extension that is just
|
|
* created, we can mark it as distributed to make sure that its
|
|
* dependencies exist on all nodes.
|
|
*/
|
|
void
|
|
ProcessCreateExtensionStmt(CreateExtensionStmt *createExtensionStmt, const
|
|
char *queryString)
|
|
{
|
|
const ObjectAddress *extensionAddress = NULL;
|
|
|
|
if (!ShouldPropagateExtensionCommand((Node *) createExtensionStmt))
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
/*
|
|
* If the extension command is a part of a bigger multi-statement transaction,
|
|
* do not propagate it
|
|
*/
|
|
if (IsMultiStatementTransaction())
|
|
{
|
|
return;
|
|
}
|
|
|
|
extensionAddress = GetObjectAddressFromParseTree((Node *) createExtensionStmt, false);
|
|
|
|
EnsureDependenciesExistsOnAllNodes(extensionAddress);
|
|
|
|
MarkObjectDistributed(extensionAddress);
|
|
}
|
|
|
|
|
|
/*
|
|
* PlanDropExtensionStmt is called to drop extension(s) in coordinator and
|
|
* in worker nodes if distributed before.
|
|
* We first ensure that we keep only the distributed ones before propagating
|
|
* the statement to worker nodes.
|
|
* If no extensions in the drop list are distributed, then no calls will
|
|
* be made to the workers.
|
|
*/
|
|
List *
|
|
PlanDropExtensionStmt(DropStmt *dropStmt, const char *queryString)
|
|
{
|
|
List *allDroppedExtensions = dropStmt->objects;
|
|
|
|
List *distributedExtensions = NIL;
|
|
List *distributedExtensionAddresses = NIL;
|
|
|
|
List *commands = NIL;
|
|
const char *deparsedStmt = NULL;
|
|
|
|
ListCell *addressCell = NULL;
|
|
|
|
if (!ShouldPropagateExtensionCommand((Node *) dropStmt))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* get distributed extensions to be dropped in worker nodes as well */
|
|
distributedExtensions = FilterDistributedExtensions(allDroppedExtensions);
|
|
|
|
if (list_length(distributedExtensions) <= 0)
|
|
{
|
|
/* no distributed extensions to drop */
|
|
return NIL;
|
|
}
|
|
|
|
/* extension management can only be done via coordinator node */
|
|
EnsureCoordinator();
|
|
|
|
/*
|
|
* Make sure that no new nodes are added after this point until the end of the
|
|
* transaction by taking a RowShareLock on pg_dist_node, which conflicts with the
|
|
* ExclusiveLock taken by master_add_node.
|
|
* This guarantees that all active nodes will drop the extension, because they will
|
|
* either get it now, or get it in master_add_node after this transaction finishes and
|
|
* the pg_dist_object record becomes visible.
|
|
*/
|
|
LockRelationOid(DistNodeRelationId(), RowShareLock);
|
|
|
|
/*
|
|
* Make sure that the current transaction is already in sequential mode,
|
|
* or can still safely be put in sequential mode
|
|
*/
|
|
EnsureSequentialModeForExtensionDDL();
|
|
|
|
distributedExtensionAddresses = ExtensionNameListToObjectAddressList(
|
|
distributedExtensions);
|
|
|
|
/* unmark each distributed extension */
|
|
foreach(addressCell, distributedExtensionAddresses)
|
|
{
|
|
ObjectAddress *address = (ObjectAddress *) lfirst(addressCell);
|
|
UnmarkObjectDistributed(address);
|
|
}
|
|
|
|
/*
|
|
* Temporary swap the lists of objects to delete with the distributed
|
|
* objects and deparse to an sql statement for the workers.
|
|
* Then switch back to allDroppedExtensions to drop all specified
|
|
* extensions in coordinator after PlanDropExtensionStmt completes
|
|
* its execution.
|
|
*/
|
|
dropStmt->objects = distributedExtensions;
|
|
deparsedStmt = DeparseTreeNode((Node *) dropStmt);
|
|
|
|
dropStmt->objects = allDroppedExtensions;
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) deparsedStmt,
|
|
ENABLE_DDL_PROPAGATION);
|
|
|
|
return NodeDDLTaskList(ALL_WORKERS, commands);
|
|
}
|
|
|
|
|
|
/*
|
|
* FilterDistributedExtensions returns the distributed objects in an "objects"
|
|
* list of a DropStmt, a list having the format of a "DropStmt.objects" list.
|
|
* That is, in turn, a list of string "Value"s.
|
|
*/
|
|
static List *
|
|
FilterDistributedExtensions(List *extensionObjectList)
|
|
{
|
|
List *extensionNameList = NIL;
|
|
|
|
bool missingOk = true;
|
|
ListCell *objectCell = NULL;
|
|
|
|
foreach(objectCell, extensionObjectList)
|
|
{
|
|
char *extensionName = strVal(lfirst(objectCell));
|
|
|
|
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
|
|
|
Oid extensionOid = get_extension_oid(extensionName, missingOk);
|
|
|
|
if (!OidIsValid(extensionOid))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ObjectAddressSet(*address, ExtensionRelationId, extensionOid);
|
|
|
|
if (!IsObjectDistributed(address))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
extensionNameList = lappend(extensionNameList, makeString(extensionName));
|
|
}
|
|
|
|
return extensionNameList;
|
|
}
|
|
|
|
|
|
/*
|
|
* ExtensionNameListToObjectAddressList returns the object addresses in
|
|
* an ObjectAddress list for an "objects" list of a DropStmt.
|
|
* Callers of this function should ensure that all the objects in the list
|
|
* are valid and distributed.
|
|
*/
|
|
static List *
|
|
ExtensionNameListToObjectAddressList(List *extensionObjectList)
|
|
{
|
|
List *extensionObjectAddressList = NIL;
|
|
|
|
ListCell *objectCell = NULL;
|
|
|
|
foreach(objectCell, extensionObjectList)
|
|
{
|
|
/*
|
|
* We set missingOk to false as we assume all the objects in
|
|
* extensionObjectList list are valid and distributed.
|
|
*/
|
|
bool missingOk = false;
|
|
|
|
const char *extensionName = strVal(lfirst(objectCell));
|
|
|
|
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
|
|
|
Oid extensionOid = get_extension_oid(extensionName, missingOk);
|
|
|
|
ObjectAddressSet(*address, ExtensionRelationId, extensionOid);
|
|
|
|
extensionObjectAddressList = lappend(extensionObjectAddressList, address);
|
|
}
|
|
|
|
return extensionObjectAddressList;
|
|
}
|
|
|
|
|
|
/*
|
|
* PlanAlterExtensionSchemaStmt is invoked for alter extension set schema statements.
|
|
*/
|
|
List *
|
|
PlanAlterExtensionSchemaStmt(AlterObjectSchemaStmt *alterExtensionStmt, const
|
|
char *queryString)
|
|
{
|
|
const char *alterExtensionStmtSql = NULL;
|
|
List *commands = NIL;
|
|
|
|
if (!ShouldPropagateExtensionCommand((Node *) alterExtensionStmt))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* extension management can only be done via coordinator node */
|
|
EnsureCoordinator();
|
|
|
|
/*
|
|
* Make sure that no new nodes are added after this point until the end of the
|
|
* transaction by taking a RowShareLock on pg_dist_node, which conflicts with the
|
|
* ExclusiveLock taken by master_add_node.
|
|
* This guarantees that all active nodes will update the extension schema after
|
|
* this transaction finishes and the pg_dist_object record becomes visible.
|
|
*/
|
|
LockRelationOid(DistNodeRelationId(), RowShareLock);
|
|
|
|
/*
|
|
* Make sure that the current transaction is already in sequential mode,
|
|
* or can still safely be put in sequential mode
|
|
*/
|
|
EnsureSequentialModeForExtensionDDL();
|
|
|
|
alterExtensionStmtSql = DeparseTreeNode((Node *) alterExtensionStmt);
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) alterExtensionStmtSql,
|
|
ENABLE_DDL_PROPAGATION);
|
|
|
|
return NodeDDLTaskList(ALL_WORKERS, commands);
|
|
}
|
|
|
|
|
|
/*
|
|
* ProcessAlterExtensionSchemaStmt is executed after the change has been applied
|
|
* locally, we can now use the new dependencies (schema) of the extension to ensure
|
|
* all its dependencies exist on the workers before we apply the commands remotely.
|
|
*/
|
|
void
|
|
ProcessAlterExtensionSchemaStmt(AlterObjectSchemaStmt *alterExtensionStmt, const
|
|
char *queryString)
|
|
{
|
|
const ObjectAddress *extensionAddress = NULL;
|
|
|
|
extensionAddress = GetObjectAddressFromParseTree((Node *) alterExtensionStmt, false);
|
|
|
|
if (!ShouldPropagateExtensionCommand((Node *) alterExtensionStmt))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* dependencies (schema) have changed let's ensure they exist */
|
|
EnsureDependenciesExistsOnAllNodes(extensionAddress);
|
|
}
|
|
|
|
|
|
/*
|
|
* PlanAlterExtensionUpdateStmt is invoked for alter extension update statements.
|
|
*/
|
|
List *
|
|
PlanAlterExtensionUpdateStmt(AlterExtensionStmt *alterExtensionStmt, const
|
|
char *queryString)
|
|
{
|
|
const char *alterExtensionStmtSql = NULL;
|
|
List *commands = NIL;
|
|
|
|
if (!ShouldPropagateExtensionCommand((Node *) alterExtensionStmt))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* extension management can only be done via coordinator node */
|
|
EnsureCoordinator();
|
|
|
|
/*
|
|
* Make sure that no new nodes are added after this point until the end of the
|
|
* transaction by taking a RowShareLock on pg_dist_node, which conflicts with the
|
|
* ExclusiveLock taken by master_add_node.
|
|
* This guarantees that all active nodes will update the extension version, because
|
|
* they will either get it now, or get it in master_add_node after this transaction
|
|
* finishes and the pg_dist_object record becomes visible.
|
|
*/
|
|
LockRelationOid(DistNodeRelationId(), RowShareLock);
|
|
|
|
/*
|
|
* Make sure that the current transaction is already in sequential mode,
|
|
* or can still safely be put in sequential mode
|
|
*/
|
|
EnsureSequentialModeForExtensionDDL();
|
|
|
|
alterExtensionStmtSql = DeparseTreeNode((Node *) alterExtensionStmt);
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) alterExtensionStmtSql,
|
|
ENABLE_DDL_PROPAGATION);
|
|
|
|
return NodeDDLTaskList(ALL_WORKERS, commands);
|
|
}
|
|
|
|
|
|
/*
|
|
* EnsureSequentialModeForExtensionDDL makes sure that the current transaction is already in
|
|
* sequential mode, or can still safely be put in sequential mode, it errors if that is
|
|
* not possible. The error contains information for the user to retry the transaction with
|
|
* sequential mode set from the beginnig.
|
|
*
|
|
* As extensions are node scoped objects there exists only 1 instance of the
|
|
* extension used by potentially multiple shards. To make sure all shards in
|
|
* the transaction can interact with the extension the extension needs to be
|
|
* visible on all connections used by the transaction, meaning we can only use
|
|
* 1 connection per node.
|
|
*/
|
|
static void
|
|
EnsureSequentialModeForExtensionDDL(void)
|
|
{
|
|
if (ParallelQueryExecutedInTransaction())
|
|
{
|
|
ereport(ERROR, (errmsg("cannot run extension command because there was a "
|
|
"parallel operation on a distributed table in the "
|
|
"transaction"),
|
|
errdetail(
|
|
"When running command on/for a distributed extension, Citus needs to "
|
|
"perform all operations over a single connection per "
|
|
"node to ensure consistency."),
|
|
errhint("Try re-running the transaction with "
|
|
"\"SET LOCAL citus.multi_shard_modify_mode TO "
|
|
"\'sequential\';\"")));
|
|
}
|
|
|
|
ereport(DEBUG1, (errmsg("switching to sequential query execution mode"),
|
|
errdetail(
|
|
"A command for a distributed extension is run. To make sure subsequent "
|
|
"commands see the type correctly we need to make sure to "
|
|
"use only one connection for all future commands")));
|
|
SetLocalMultiShardModifyModeToSequential();
|
|
}
|
|
|
|
|
|
/*
|
|
* ShouldPropagateExtensionCommand determines whether to propagate an extension
|
|
* command to the worker nodes.
|
|
*/
|
|
static bool
|
|
ShouldPropagateExtensionCommand(Node *parseTree)
|
|
{
|
|
/* if we disabled object propagation, then we should not propagate anything. */
|
|
if (!EnableDependencyCreation)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* If extension command is run for/on citus, leave the rest to standard utility hook
|
|
* by returning false.
|
|
*/
|
|
if (IsCreateAlterExtensionUpdateCitusStmt(parseTree))
|
|
{
|
|
return false;
|
|
}
|
|
else if (IsDropCitusStmt(parseTree))
|
|
{
|
|
return false;
|
|
}
|
|
else if (IsAlterExtensionSetSchemaCitus(parseTree))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* IsCreateAlterExtensionUpdateCitusStmt returns whether a given utility is a
|
|
* CREATE or ALTER EXTENSION UPDATE statement which references the citus extension.
|
|
* This function returns false for all other inputs.
|
|
*/
|
|
bool
|
|
IsCreateAlterExtensionUpdateCitusStmt(Node *parseTree)
|
|
{
|
|
const char *extensionName = NULL;
|
|
|
|
if (IsA(parseTree, CreateExtensionStmt))
|
|
{
|
|
extensionName = ((CreateExtensionStmt *) parseTree)->extname;
|
|
}
|
|
else if (IsA(parseTree, AlterExtensionStmt))
|
|
{
|
|
extensionName = ((AlterExtensionStmt *) parseTree)->extname;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If it is not a Create Extension or a Alter Extension stmt,
|
|
* it does not matter if the it is about citus
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Now that we have CreateExtensionStmt or AlterExtensionStmt,
|
|
* check if it is run for/on citus
|
|
*/
|
|
return (strncasecmp(extensionName, "citus", NAMEDATALEN) == 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* IsDropCitusStmt iterates the objects to be dropped in a drop statement
|
|
* and try to find citus there.
|
|
*/
|
|
static bool
|
|
IsDropCitusStmt(Node *parseTree)
|
|
{
|
|
ListCell *objectCell = NULL;
|
|
|
|
/* if it is not a DropStmt, it is needless to search for citus */
|
|
if (!IsA(parseTree, DropStmt))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* now that we have a DropStmt, check if citus is among the objects to dropped */
|
|
foreach(objectCell, ((DropStmt *) parseTree)->objects)
|
|
{
|
|
const char *extensionName = strVal(lfirst(objectCell));
|
|
|
|
if (strncasecmp(extensionName, "citus", NAMEDATALEN) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* IsAlterExtensionSetSchemaCitus returns whether a given utility is an
|
|
* ALTER EXTENSION SET SCHEMA statement which references the citus extension.
|
|
* This function returns false for all other inputs.
|
|
*/
|
|
static bool
|
|
IsAlterExtensionSetSchemaCitus(Node *parseTree)
|
|
{
|
|
const char *extensionName = NULL;
|
|
|
|
if (IsA(parseTree, AlterObjectSchemaStmt))
|
|
{
|
|
AlterObjectSchemaStmt *alterExtensionSetSchemaStmt =
|
|
(AlterObjectSchemaStmt *) parseTree;
|
|
|
|
if (alterExtensionSetSchemaStmt->objectType == OBJECT_EXTENSION)
|
|
{
|
|
extensionName = strVal(((AlterObjectSchemaStmt *) parseTree)->object);
|
|
|
|
/*
|
|
* Now that we have AlterObjectSchemaStmt for an extension,
|
|
* check if it is run for/on citus
|
|
*/
|
|
return (strncasecmp(extensionName, "citus", NAMEDATALEN) == 0);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* CreateExtensionDDLCommand returns a list of DDL statements (const char *) to be
|
|
* executed on a node to recreate the extension addressed by the extensionAddress.
|
|
*/
|
|
List *
|
|
CreateExtensionDDLCommand(const ObjectAddress *extensionAddress)
|
|
{
|
|
List *ddlCommands = NIL;
|
|
const char *ddlCommand = NULL;
|
|
|
|
Node *stmt = NULL;
|
|
|
|
/* generate a statement for creation of the extension in "if not exists" construct */
|
|
stmt = RecreateExtensionStmt(extensionAddress->objectId);
|
|
|
|
/* capture ddl command for the create statement */
|
|
ddlCommand = DeparseTreeNode(stmt);
|
|
|
|
ddlCommands = list_make1((void *) ddlCommand);
|
|
|
|
return ddlCommands;
|
|
}
|
|
|
|
|
|
/*
|
|
* RecreateEnumStmt returns a parsetree for a CREATE EXTENSION statement that would
|
|
* recreate the given extension on a new node.
|
|
*/
|
|
static Node *
|
|
RecreateExtensionStmt(Oid extensionOid)
|
|
{
|
|
CreateExtensionStmt *createExtensionStmt = makeNode(CreateExtensionStmt);
|
|
|
|
char *extensionName = get_extension_name(extensionOid);
|
|
|
|
if (!extensionName)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("extension with oid %u does not exist",
|
|
extensionOid)));
|
|
}
|
|
|
|
/* schema DefElement related variables */
|
|
Oid extensionSchemaOid = InvalidOid;
|
|
char *extensionSchemaName = NULL;
|
|
Node *schemaNameArg = NULL;
|
|
|
|
/* set location to -1 as it is unknown */
|
|
int location = -1;
|
|
DefElem *schemaDefElement = NULL;
|
|
|
|
/* set extension name and if_not_exists fields */
|
|
createExtensionStmt->extname = extensionName;
|
|
createExtensionStmt->if_not_exists = true;
|
|
|
|
/* get schema name that extension was created on */
|
|
extensionSchemaOid = get_extension_schema(extensionOid);
|
|
extensionSchemaName = get_namespace_name(extensionSchemaOid);
|
|
|
|
/* make DefEleme for extensionSchemaName */
|
|
schemaNameArg = (Node *) makeString(extensionSchemaName);
|
|
|
|
schemaDefElement = makeDefElem("schema", schemaNameArg, location);
|
|
|
|
/* append the schema name DefElem finally */
|
|
createExtensionStmt->options = lappend(createExtensionStmt->options,
|
|
schemaDefElement);
|
|
|
|
return (Node *) createExtensionStmt;
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterExtensionSchemaStmtObjectAddress returns the ObjectAddress of the extension that is
|
|
* the subject of the AlterObjectSchemaStmt. Errors if missing_ok is false.
|
|
*/
|
|
ObjectAddress *
|
|
AlterExtensionSchemaStmtObjectAddress(AlterObjectSchemaStmt *alterExtensionSchemaStmt,
|
|
bool missing_ok)
|
|
{
|
|
ObjectAddress *extensionAddress = NULL;
|
|
Oid extensionOid = InvalidOid;
|
|
const char *extensionName = NULL;
|
|
|
|
Assert(alterExtensionSchemaStmt->objectType == OBJECT_EXTENSION);
|
|
|
|
extensionName = strVal(alterExtensionSchemaStmt->object);
|
|
|
|
extensionOid = get_extension_oid(extensionName, missing_ok);
|
|
|
|
if (extensionOid == InvalidOid)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("extension \"%s\" does not exist",
|
|
extensionName)));
|
|
}
|
|
|
|
extensionAddress = palloc0(sizeof(ObjectAddress));
|
|
ObjectAddressSet(*extensionAddress, ExtensionRelationId, extensionOid);
|
|
|
|
return extensionAddress;
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterExtensionUpdateStmtObjectAddress returns the ObjectAddress of the extension that is
|
|
* the subject of the AlterExtensionStmt. Errors if missing_ok is false.
|
|
*/
|
|
ObjectAddress *
|
|
AlterExtensionUpdateStmtObjectAddress(AlterExtensionStmt *alterExtensionStmt,
|
|
bool missing_ok)
|
|
{
|
|
ObjectAddress *extensionAddress = NULL;
|
|
Oid extensionOid = InvalidOid;
|
|
const char *extensionName = NULL;
|
|
|
|
extensionName = alterExtensionStmt->extname;
|
|
|
|
extensionOid = get_extension_oid(extensionName, missing_ok);
|
|
|
|
if (extensionOid == InvalidOid)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("extension \"%s\" does not exist",
|
|
extensionName)));
|
|
}
|
|
|
|
extensionAddress = palloc0(sizeof(ObjectAddress));
|
|
ObjectAddressSet(*extensionAddress, ExtensionRelationId, extensionOid);
|
|
|
|
return extensionAddress;
|
|
}
|