mirror of https://github.com/citusdata/citus.git
1261 lines
36 KiB
C
1261 lines
36 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* extension.c
|
|
* Commands for creating and altering extensions.
|
|
*
|
|
* Copyright (c) Citus Data, Inc.
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/genam.h"
|
|
#include "access/xact.h"
|
|
#include "citus_version.h"
|
|
#include "catalog/dependency.h"
|
|
#include "catalog/pg_depend.h"
|
|
#include "catalog/pg_extension_d.h"
|
|
#include "columnar/columnar.h"
|
|
#include "catalog/pg_foreign_data_wrapper.h"
|
|
#include "commands/defrem.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/listutils.h"
|
|
#include "distributed/coordinator_protocol.h"
|
|
#include "distributed/metadata_sync.h"
|
|
#include "distributed/metadata/dependency.h"
|
|
#include "distributed/metadata/distobject.h"
|
|
#include "distributed/multi_executor.h"
|
|
#include "distributed/relation_access_tracking.h"
|
|
#include "distributed/transaction_management.h"
|
|
#include "foreign/foreign.h"
|
|
#include "nodes/makefuncs.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/syscache.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 MarkExistingObjectDependenciesDistributedIfSupported(void);
|
|
static List * GetAllViews(void);
|
|
static bool ShouldPropagateExtensionCommand(Node *parseTree);
|
|
static bool IsAlterExtensionSetSchemaCitus(Node *parseTree);
|
|
static Node * RecreateExtensionStmt(Oid extensionOid);
|
|
static List * GenerateGrantCommandsOnExtesionDependentFDWs(Oid extensionId);
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
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);
|
|
}
|
|
|
|
DefElem *newVersionValue = GetExtensionOption(optionsList, "new_version");
|
|
|
|
/* return target string safely */
|
|
if (newVersionValue)
|
|
{
|
|
const char *newVersion = defGetString(newVersionValue);
|
|
return pstrdup(newVersion);
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* PostprocessCreateExtensionStmt is called after the creation of an extension.
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
List *
|
|
PostprocessCreateExtensionStmt(Node *node, const char *queryString)
|
|
{
|
|
CreateExtensionStmt *stmt = castNode(CreateExtensionStmt, node);
|
|
|
|
if (!ShouldPropagateExtensionCommand(node))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* check creation against multi-statement transaction policy */
|
|
if (!ShouldPropagateCreateInCoordinatedTransction())
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* extension management can only be done via coordinator node */
|
|
EnsureCoordinator();
|
|
|
|
/*
|
|
* Make sure that the current transaction is already in sequential mode,
|
|
* or can still safely be put in sequential mode
|
|
*/
|
|
EnsureSequentialMode(OBJECT_EXTENSION);
|
|
|
|
/*
|
|
* Here we append "schema" field to the "options" list (if not specified)
|
|
* to satisfy the schema consistency between worker nodes and the coordinator.
|
|
*/
|
|
AddSchemaFieldIfMissing(stmt);
|
|
|
|
/* always send commands with IF NOT EXISTS */
|
|
stmt->if_not_exists = true;
|
|
|
|
const char *createExtensionStmtSql = DeparseTreeNode(node);
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) createExtensionStmtSql,
|
|
ENABLE_DDL_PROPAGATION);
|
|
|
|
List *extensionAddresses = GetObjectAddressListFromParseTree(node, false, true);
|
|
|
|
/* the code-path only supports a single object */
|
|
Assert(list_length(extensionAddresses) == 1);
|
|
|
|
EnsureAllObjectDependenciesExistOnAllNodes(extensionAddresses);
|
|
|
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, 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;
|
|
|
|
DefElem *schemaNameValue = GetExtensionOption(optionsList, "schema");
|
|
|
|
if (!schemaNameValue)
|
|
{
|
|
/*
|
|
* As we already created the extension by standard_ProcessUtility,
|
|
* we actually know the schema it belongs to
|
|
*/
|
|
const 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);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessDropExtensionStmt 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 *
|
|
PreprocessDropExtensionStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext processUtilityContext)
|
|
{
|
|
DropStmt *stmt = castNode(DropStmt, node);
|
|
|
|
if (!ShouldPropagateExtensionCommand(node))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* get distributed extensions to be dropped in worker nodes as well */
|
|
List *allDroppedExtensions = stmt->objects;
|
|
List *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 the current transaction is already in sequential mode,
|
|
* or can still safely be put in sequential mode
|
|
*/
|
|
EnsureSequentialMode(OBJECT_EXTENSION);
|
|
|
|
List *distributedExtensionAddresses = ExtensionNameListToObjectAddressList(
|
|
distributedExtensions);
|
|
|
|
/* unmark each distributed extension */
|
|
ObjectAddress *address = NULL;
|
|
foreach_ptr(address, distributedExtensionAddresses)
|
|
{
|
|
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 PreprocessDropExtensionStmt completes
|
|
* its execution.
|
|
*/
|
|
stmt->objects = distributedExtensions;
|
|
const char *deparsedStmt = DeparseTreeNode((Node *) stmt);
|
|
stmt->objects = allDroppedExtensions;
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) deparsedStmt,
|
|
ENABLE_DDL_PROPAGATION);
|
|
|
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, 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;
|
|
|
|
String *objectName = NULL;
|
|
foreach_ptr(objectName, extensionObjectList)
|
|
{
|
|
const char *extensionName = strVal(objectName);
|
|
const bool missingOk = true;
|
|
|
|
Oid extensionOid = get_extension_oid(extensionName, missingOk);
|
|
|
|
if (!OidIsValid(extensionOid))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
|
ObjectAddressSet(*address, ExtensionRelationId, extensionOid);
|
|
if (!IsAnyObjectDistributed(list_make1(address)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
extensionNameList = lappend(extensionNameList, objectName);
|
|
}
|
|
|
|
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;
|
|
|
|
String *objectName;
|
|
foreach_ptr(objectName, extensionObjectList)
|
|
{
|
|
/*
|
|
* We set missingOk to false as we assume all the objects in
|
|
* extensionObjectList list are valid and distributed.
|
|
*/
|
|
const char *extensionName = strVal(objectName);
|
|
const bool missingOk = false;
|
|
|
|
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
|
|
|
Oid extensionOid = get_extension_oid(extensionName, missingOk);
|
|
|
|
ObjectAddressSet(*address, ExtensionRelationId, extensionOid);
|
|
|
|
extensionObjectAddressList = lappend(extensionObjectAddressList, address);
|
|
}
|
|
|
|
return extensionObjectAddressList;
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessAlterExtensionSchemaStmt is invoked for alter extension set schema statements.
|
|
*/
|
|
List *
|
|
PreprocessAlterExtensionSchemaStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext processUtilityContext)
|
|
{
|
|
if (!ShouldPropagateExtensionCommand(node))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* extension management can only be done via coordinator node */
|
|
EnsureCoordinator();
|
|
|
|
/*
|
|
* Make sure that the current transaction is already in sequential mode,
|
|
* or can still safely be put in sequential mode
|
|
*/
|
|
EnsureSequentialMode(OBJECT_EXTENSION);
|
|
|
|
const char *alterExtensionStmtSql = DeparseTreeNode(node);
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) alterExtensionStmtSql,
|
|
ENABLE_DDL_PROPAGATION);
|
|
|
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
|
}
|
|
|
|
|
|
/*
|
|
* PostprocessAlterExtensionSchemaStmt 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.
|
|
*/
|
|
List *
|
|
PostprocessAlterExtensionSchemaStmt(Node *node, const char *queryString)
|
|
{
|
|
List *extensionAddresses = GetObjectAddressListFromParseTree(node, false, true);
|
|
|
|
/* the code-path only supports a single object */
|
|
Assert(list_length(extensionAddresses) == 1);
|
|
|
|
if (!ShouldPropagateExtensionCommand(node))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* dependencies (schema) have changed let's ensure they exist */
|
|
EnsureAllObjectDependenciesExistOnAllNodes(extensionAddresses);
|
|
|
|
return NIL;
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessAlterExtensionUpdateStmt is invoked for alter extension update statements.
|
|
*/
|
|
List *
|
|
PreprocessAlterExtensionUpdateStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext processUtilityContext)
|
|
{
|
|
AlterExtensionStmt *alterExtensionStmt = castNode(AlterExtensionStmt, node);
|
|
|
|
if (!ShouldPropagateExtensionCommand((Node *) alterExtensionStmt))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* extension management can only be done via coordinator node */
|
|
EnsureCoordinator();
|
|
|
|
/*
|
|
* Make sure that the current transaction is already in sequential mode,
|
|
* or can still safely be put in sequential mode
|
|
*/
|
|
EnsureSequentialMode(OBJECT_EXTENSION);
|
|
|
|
const char *alterExtensionStmtSql = DeparseTreeNode((Node *) alterExtensionStmt);
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) alterExtensionStmtSql,
|
|
ENABLE_DDL_PROPAGATION);
|
|
|
|
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
|
}
|
|
|
|
|
|
/*
|
|
* PostprocessAlterExtensionCitusUpdateStmt is called after ALTER EXTENSION
|
|
* citus UPDATE command is executed by standard utility hook.
|
|
*
|
|
* Actually, we do not need such a post process function for ALTER EXTENSION
|
|
* UPDATE commands unless the extension is Citus itself. This is because we
|
|
* need to mark existing objects that are not included in distributed object
|
|
* infrastructure in older versions of Citus (but now should be) as distributed
|
|
* if we really updated Citus to the available version successfully via standard
|
|
* utility hook.
|
|
*/
|
|
void
|
|
PostprocessAlterExtensionCitusUpdateStmt(Node *node)
|
|
{
|
|
bool citusIsUpdatedToLatestVersion = InstalledAndAvailableVersionsSame();
|
|
|
|
/*
|
|
* Knowing that Citus version was different than the available version before
|
|
* standard process utility runs ALTER EXTENSION command, we perform post
|
|
* process operations if Citus is updated to that available version
|
|
*/
|
|
if (!citusIsUpdatedToLatestVersion)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Finally, mark existing objects that are not included in distributed object
|
|
* infrastructure in older versions of Citus (but now should be) as distributed
|
|
*/
|
|
MarkExistingObjectDependenciesDistributedIfSupported();
|
|
}
|
|
|
|
|
|
/*
|
|
* MarkExistingObjectDependenciesDistributedIfSupported marks all objects that could
|
|
* be distributed by resolving dependencies of "existing distributed tables" and
|
|
* "already distributed objects" to introduce the objects created in older versions
|
|
* of Citus to distributed object infrastructure as well.
|
|
*
|
|
* Note that this function is not responsible for ensuring if dependencies exist on
|
|
* nodes and satisfying these dependendencies if not exists, which is already done by
|
|
* EnsureAllObjectDependenciesExistOnAllNodes on demand. Hence, this function is just designed
|
|
* to be used when "ALTER EXTENSION citus UPDATE" is executed.
|
|
* This is because we want to add existing objects that would have already been in
|
|
* pg_dist_object if we had created them in new version of Citus to pg_dist_object.
|
|
*/
|
|
static void
|
|
MarkExistingObjectDependenciesDistributedIfSupported()
|
|
{
|
|
/* resulting object addresses to be marked as distributed */
|
|
List *resultingObjectAddresses = NIL;
|
|
|
|
/* resolve dependencies of citus tables */
|
|
List *citusTableIdList = CitusTableTypeIdList(ANY_CITUS_TABLE_TYPE);
|
|
|
|
Oid citusTableId = InvalidOid;
|
|
foreach_oid(citusTableId, citusTableIdList)
|
|
{
|
|
if (!ShouldMarkRelationDistributed(citusTableId))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* refrain reading the metadata cache for all tables */
|
|
if (ShouldSyncTableMetadataViaCatalog(citusTableId))
|
|
{
|
|
ObjectAddress tableAddress = { 0 };
|
|
ObjectAddressSet(tableAddress, RelationRelationId, citusTableId);
|
|
|
|
/*
|
|
* We mark tables distributed immediately because we also need to mark
|
|
* views as distributed. We check whether the views that depend on
|
|
* the table has any auto-distirbutable dependencies below. Citus
|
|
* currently cannot "auto" distribute tables as dependencies, so we
|
|
* mark them distributed immediately.
|
|
*/
|
|
MarkObjectDistributedLocally(&tableAddress);
|
|
|
|
/*
|
|
* All the distributable dependencies of a table should be marked as
|
|
* distributed.
|
|
*/
|
|
List *distributableDependencyObjectAddresses =
|
|
GetDistributableDependenciesForObject(&tableAddress);
|
|
|
|
resultingObjectAddresses =
|
|
list_concat(resultingObjectAddresses,
|
|
distributableDependencyObjectAddresses);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* As of Citus 11, views on Citus tables that do not have any unsupported
|
|
* dependency should also be distributed.
|
|
*
|
|
* In general, we mark views distributed as long as it does not have
|
|
* any unsupported dependencies.
|
|
*/
|
|
List *viewList = GetAllViews();
|
|
Oid viewOid = InvalidOid;
|
|
foreach_oid(viewOid, viewList)
|
|
{
|
|
if (!ShouldMarkRelationDistributed(viewOid))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ObjectAddress viewAddress = { 0 };
|
|
ObjectAddressSet(viewAddress, RelationRelationId, viewOid);
|
|
|
|
/*
|
|
* If a view depends on multiple views, that view will be marked
|
|
* as distributed while it is processed for the last view
|
|
* table.
|
|
*/
|
|
MarkObjectDistributedLocally(&viewAddress);
|
|
|
|
/* we need to pass pointer allocated in the heap */
|
|
ObjectAddress *addressPointer = palloc0(sizeof(ObjectAddress));
|
|
*addressPointer = viewAddress;
|
|
|
|
List *distributableDependencyObjectAddresses =
|
|
GetDistributableDependenciesForObject(&viewAddress);
|
|
|
|
resultingObjectAddresses =
|
|
list_concat(resultingObjectAddresses,
|
|
distributableDependencyObjectAddresses);
|
|
}
|
|
|
|
|
|
/* resolve dependencies of the objects in pg_dist_object*/
|
|
List *distributedObjectAddressList = GetDistributedObjectAddressList();
|
|
|
|
ObjectAddress *distributedObjectAddress = NULL;
|
|
foreach_ptr(distributedObjectAddress, distributedObjectAddressList)
|
|
{
|
|
List *distributableDependencyObjectAddresses =
|
|
GetDistributableDependenciesForObject(distributedObjectAddress);
|
|
|
|
resultingObjectAddresses = list_concat(resultingObjectAddresses,
|
|
distributableDependencyObjectAddresses);
|
|
}
|
|
|
|
/* remove duplicates from object addresses list for efficiency */
|
|
List *uniqueObjectAddresses = GetUniqueDependenciesList(resultingObjectAddresses);
|
|
|
|
/*
|
|
* We should sync the new dependencies during ALTER EXTENSION because
|
|
* we cannot know whether the nodes has already been upgraded or not. If
|
|
* the nodes are not upgraded at this point, we cannot sync the object. Also,
|
|
* when the workers upgraded, they'd get the same objects anyway.
|
|
*/
|
|
bool prevMetadataSyncValue = EnableMetadataSync;
|
|
SetLocalEnableMetadataSync(false);
|
|
|
|
ObjectAddress *objectAddress = NULL;
|
|
foreach_ptr(objectAddress, uniqueObjectAddresses)
|
|
{
|
|
MarkObjectDistributed(objectAddress);
|
|
}
|
|
|
|
SetLocalEnableMetadataSync(prevMetadataSyncValue);
|
|
}
|
|
|
|
|
|
/*
|
|
* GetAllViews returns list of view oids that exists on this server.
|
|
*/
|
|
static List *
|
|
GetAllViews(void)
|
|
{
|
|
List *viewOidList = NIL;
|
|
|
|
Relation pgClass = table_open(RelationRelationId, AccessShareLock);
|
|
|
|
SysScanDesc scanDescriptor = systable_beginscan(pgClass, InvalidOid, false, NULL,
|
|
0, NULL);
|
|
|
|
HeapTuple heapTuple = systable_getnext(scanDescriptor);
|
|
while (HeapTupleIsValid(heapTuple))
|
|
{
|
|
Form_pg_class relationForm = (Form_pg_class) GETSTRUCT(heapTuple);
|
|
|
|
/* we're only interested in views */
|
|
if (relationForm->relkind == RELKIND_VIEW)
|
|
{
|
|
viewOidList = lappend_oid(viewOidList, relationForm->oid);
|
|
}
|
|
|
|
heapTuple = systable_getnext(scanDescriptor);
|
|
}
|
|
|
|
systable_endscan(scanDescriptor);
|
|
table_close(pgClass, NoLock);
|
|
|
|
return viewOidList;
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessAlterExtensionContentsStmt issues a notice. It does not propagate.
|
|
*/
|
|
List *
|
|
PreprocessAlterExtensionContentsStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext processUtilityContext)
|
|
{
|
|
ereport(NOTICE, (errmsg(
|
|
"Citus does not propagate adding/dropping member objects"),
|
|
errhint(
|
|
"You can add/drop the member objects on the workers as well.")));
|
|
|
|
return NIL;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 (!EnableMetadataSync)
|
|
{
|
|
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 (IsDropCitusExtensionStmt(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);
|
|
}
|
|
|
|
|
|
/*
|
|
* PreProcessCreateExtensionCitusStmtForColumnar determines whether need to
|
|
* install citus_columnar first or citus_columnar is supported on current
|
|
* citus version, when a given utility is a CREATE statement
|
|
*/
|
|
void
|
|
PreprocessCreateExtensionStmtForCitusColumnar(Node *parsetree)
|
|
{
|
|
/*CREATE EXTENSION CITUS (version Z) */
|
|
CreateExtensionStmt *createExtensionStmt = castNode(CreateExtensionStmt,
|
|
parsetree);
|
|
|
|
if (strcmp(createExtensionStmt->extname, "citus") == 0)
|
|
{
|
|
int versionNumber = (int) (100 * strtod(CITUS_MAJORVERSION, NULL));
|
|
DefElem *newVersionValue = GetExtensionOption(createExtensionStmt->options,
|
|
"new_version");
|
|
|
|
/*create extension citus version xxx*/
|
|
if (newVersionValue)
|
|
{
|
|
char *newVersion = strdup(defGetString(newVersionValue));
|
|
versionNumber = GetExtensionVersionNumber(newVersion);
|
|
}
|
|
|
|
/*citus version >= 11.1 requires install citus_columnar first*/
|
|
if (versionNumber >= 1110)
|
|
{
|
|
if (get_extension_oid("citus_columnar", true) == InvalidOid)
|
|
{
|
|
CreateExtensionWithVersion("citus_columnar", NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*Edge case check: citus_columnar are supported on citus version >= 11.1*/
|
|
if (strcmp(createExtensionStmt->extname, "citus_columnar") == 0)
|
|
{
|
|
Oid citusOid = get_extension_oid("citus", true);
|
|
if (citusOid != InvalidOid)
|
|
{
|
|
char *curCitusVersion = strdup(get_extension_version(citusOid));
|
|
int curCitusVersionNum = GetExtensionVersionNumber(curCitusVersion);
|
|
if (curCitusVersionNum < 1110)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg(
|
|
"must upgrade citus to version 11.1-1 first before install citus_columnar")));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* IsDropCitusExtensionStmt iterates the objects to be dropped in a drop statement
|
|
* and try to find citus extension there.
|
|
*/
|
|
bool
|
|
IsDropCitusExtensionStmt(Node *parseTree)
|
|
{
|
|
/* if it is not a DropStmt, it is needless to search for citus */
|
|
if (!IsA(parseTree, DropStmt))
|
|
{
|
|
return false;
|
|
}
|
|
DropStmt *dropStmt = (DropStmt *) parseTree;
|
|
|
|
/* check if the drop command is a DROP EXTENSION command */
|
|
if (dropStmt->removeType != OBJECT_EXTENSION)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* now that we have a DropStmt, check if citus extension is among the objects to dropped */
|
|
String *objectName;
|
|
foreach_ptr(objectName, dropStmt->objects)
|
|
{
|
|
const char *extensionName = strVal(objectName);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessAlterExtensionCitusStmtForCitusColumnar pre-process the case when upgrade citus
|
|
* to version that support citus_columnar, or downgrade citus to lower version that
|
|
* include columnar inside citus extension
|
|
*/
|
|
void
|
|
PreprocessAlterExtensionCitusStmtForCitusColumnar(Node *parseTree)
|
|
{
|
|
/*upgrade citus: alter extension citus update to 'xxx' */
|
|
DefElem *newVersionValue = GetExtensionOption(
|
|
((AlterExtensionStmt *) parseTree)->options, "new_version");
|
|
Oid citusColumnarOid = get_extension_oid("citus_columnar", true);
|
|
if (newVersionValue)
|
|
{
|
|
char *newVersion = defGetString(newVersionValue);
|
|
double newVersionNumber = GetExtensionVersionNumber(strdup(newVersion));
|
|
|
|
/*alter extension citus update to version >= 11.1-1, and no citus_columnar installed */
|
|
if (newVersionNumber >= 1110 && citusColumnarOid == InvalidOid)
|
|
{
|
|
/*it's upgrade citus to 11.1-1 or further version */
|
|
CreateExtensionWithVersion("citus_columnar", CITUS_COLUMNAR_INTERNAL_VERSION);
|
|
}
|
|
else if (newVersionNumber < 1110 && citusColumnarOid != InvalidOid)
|
|
{
|
|
/*downgrade citus, need downgrade citus_columnar to Y */
|
|
AlterExtensionUpdateStmt("citus_columnar", CITUS_COLUMNAR_INTERNAL_VERSION);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*alter extension citus update without specifying the version*/
|
|
int versionNumber = (int) (100 * strtod(CITUS_MAJORVERSION, NULL));
|
|
if (versionNumber >= 1110)
|
|
{
|
|
if (citusColumnarOid == InvalidOid)
|
|
{
|
|
CreateExtensionWithVersion("citus_columnar",
|
|
CITUS_COLUMNAR_INTERNAL_VERSION);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* PostprocessAlterExtensionCitusStmtForCitusColumnar process the case when upgrade citus
|
|
* to version that support citus_columnar, or downgrade citus to lower version that
|
|
* include columnar inside citus extension
|
|
*/
|
|
void
|
|
PostprocessAlterExtensionCitusStmtForCitusColumnar(Node *parseTree)
|
|
{
|
|
DefElem *newVersionValue = GetExtensionOption(
|
|
((AlterExtensionStmt *) parseTree)->options, "new_version");
|
|
Oid citusColumnarOid = get_extension_oid("citus_columnar", true);
|
|
if (newVersionValue)
|
|
{
|
|
char *newVersion = defGetString(newVersionValue);
|
|
double newVersionNumber = GetExtensionVersionNumber(strdup(newVersion));
|
|
if (newVersionNumber >= 1110 && citusColumnarOid != InvalidOid)
|
|
{
|
|
/*upgrade citus, after "ALTER EXTENSION citus update to xxx" updates citus_columnar Y to version Z. */
|
|
char *curColumnarVersion = get_extension_version(citusColumnarOid);
|
|
if (strcmp(curColumnarVersion, CITUS_COLUMNAR_INTERNAL_VERSION) == 0)
|
|
{
|
|
AlterExtensionUpdateStmt("citus_columnar", "11.1-1");
|
|
}
|
|
}
|
|
else if (newVersionNumber < 1110 && citusColumnarOid != InvalidOid)
|
|
{
|
|
/*downgrade citus, after "ALTER EXTENSION citus update to xxx" drops citus_columnar extension */
|
|
char *curColumnarVersion = get_extension_version(citusColumnarOid);
|
|
if (strcmp(curColumnarVersion, CITUS_COLUMNAR_INTERNAL_VERSION) == 0)
|
|
{
|
|
RemoveExtensionById(citusColumnarOid);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*alter extension citus update, need upgrade citus_columnar from Y to Z*/
|
|
int versionNumber = (int) (100 * strtod(CITUS_MAJORVERSION, NULL));
|
|
if (versionNumber >= 1110)
|
|
{
|
|
char *curColumnarVersion = get_extension_version(citusColumnarOid);
|
|
if (strcmp(curColumnarVersion, CITUS_COLUMNAR_INTERNAL_VERSION) == 0)
|
|
{
|
|
AlterExtensionUpdateStmt("citus_columnar", "11.1-1");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
/* generate a statement for creation of the extension in "if not exists" construct */
|
|
Node *stmt = RecreateExtensionStmt(extensionAddress->objectId);
|
|
|
|
/* capture ddl command for the create statement */
|
|
const char *ddlCommand = DeparseTreeNode(stmt);
|
|
|
|
List *ddlCommands = list_make1((void *) ddlCommand);
|
|
|
|
/* any privilege granted on FDWs that belong to the extension should be included */
|
|
List *FDWGrants =
|
|
GenerateGrantCommandsOnExtesionDependentFDWs(extensionAddress->objectId);
|
|
|
|
ddlCommands = list_concat(ddlCommands, FDWGrants);
|
|
|
|
return ddlCommands;
|
|
}
|
|
|
|
|
|
/*
|
|
* RecreateExtensionStmt 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 */
|
|
|
|
/* set location to -1 as it is unknown */
|
|
int location = -1;
|
|
|
|
/* set extension name and if_not_exists fields */
|
|
createExtensionStmt->extname = extensionName;
|
|
createExtensionStmt->if_not_exists = true;
|
|
|
|
/* get schema name that extension was created on */
|
|
Oid extensionSchemaOid = get_extension_schema(extensionOid);
|
|
char *extensionSchemaName = get_namespace_name(extensionSchemaOid);
|
|
|
|
/* make DefEleme for extensionSchemaName */
|
|
Node *schemaNameArg = (Node *) makeString(extensionSchemaName);
|
|
DefElem *schemaDefElement = makeDefElem("schema", schemaNameArg, location);
|
|
|
|
/* append the schema name DefElem finally */
|
|
createExtensionStmt->options = lappend(createExtensionStmt->options,
|
|
schemaDefElement);
|
|
|
|
char *extensionVersion = get_extension_version(extensionOid);
|
|
if (extensionVersion != NULL)
|
|
{
|
|
Node *extensionVersionArg = (Node *) makeString(extensionVersion);
|
|
DefElem *extensionVersionElement =
|
|
makeDefElem("new_version", extensionVersionArg, location);
|
|
|
|
createExtensionStmt->options = lappend(createExtensionStmt->options,
|
|
extensionVersionElement);
|
|
}
|
|
|
|
return (Node *) createExtensionStmt;
|
|
}
|
|
|
|
|
|
/*
|
|
* GenerateGrantCommandsOnExtesionDependentFDWs returns a list of commands that GRANTs
|
|
* the privileges on FDWs that are depending on the given extension.
|
|
*/
|
|
static List *
|
|
GenerateGrantCommandsOnExtesionDependentFDWs(Oid extensionId)
|
|
{
|
|
List *commands = NIL;
|
|
List *FDWOids = GetDependentFDWsToExtension(extensionId);
|
|
|
|
Oid FDWOid = InvalidOid;
|
|
foreach_oid(FDWOid, FDWOids)
|
|
{
|
|
Acl *aclEntry = GetPrivilegesForFDW(FDWOid);
|
|
|
|
if (aclEntry == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
AclItem *privileges = ACL_DAT(aclEntry);
|
|
int numberOfPrivsGranted = ACL_NUM(aclEntry);
|
|
|
|
for (int i = 0; i < numberOfPrivsGranted; i++)
|
|
{
|
|
commands = list_concat(commands,
|
|
GenerateGrantOnFDWQueriesFromAclItem(FDWOid,
|
|
&privileges[i]));
|
|
}
|
|
}
|
|
|
|
return commands;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetDependentFDWsToExtension gets an extension oid and returns the list of oids of FDWs
|
|
* that are depending on the given extension.
|
|
*/
|
|
List *
|
|
GetDependentFDWsToExtension(Oid extensionId)
|
|
{
|
|
List *extensionFDWs = NIL;
|
|
ScanKeyData key[3];
|
|
int scanKeyCount = 3;
|
|
HeapTuple tup;
|
|
|
|
Relation pgDepend = table_open(DependRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&key[0],
|
|
Anum_pg_depend_refclassid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(ExtensionRelationId));
|
|
ScanKeyInit(&key[1],
|
|
Anum_pg_depend_refobjid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(extensionId));
|
|
ScanKeyInit(&key[2],
|
|
Anum_pg_depend_classid,
|
|
BTEqualStrategyNumber, F_OIDEQ,
|
|
ObjectIdGetDatum(ForeignDataWrapperRelationId));
|
|
|
|
SysScanDesc scan = systable_beginscan(pgDepend, InvalidOid, false,
|
|
NULL, scanKeyCount, key);
|
|
|
|
while (HeapTupleIsValid(tup = systable_getnext(scan)))
|
|
{
|
|
Form_pg_depend pgDependEntry = (Form_pg_depend) GETSTRUCT(tup);
|
|
|
|
if (pgDependEntry->deptype == DEPENDENCY_EXTENSION)
|
|
{
|
|
extensionFDWs = lappend_oid(extensionFDWs, pgDependEntry->objid);
|
|
}
|
|
}
|
|
|
|
systable_endscan(scan);
|
|
table_close(pgDepend, AccessShareLock);
|
|
|
|
return extensionFDWs;
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterExtensionSchemaStmtObjectAddress returns the ObjectAddress of the extension that is
|
|
* the subject of the AlterObjectSchemaStmt. Errors if missing_ok is false.
|
|
*/
|
|
List *
|
|
AlterExtensionSchemaStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess)
|
|
{
|
|
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
|
Assert(stmt->objectType == OBJECT_EXTENSION);
|
|
|
|
const char *extensionName = strVal(stmt->object);
|
|
|
|
Oid extensionOid = get_extension_oid(extensionName, missing_ok);
|
|
|
|
if (extensionOid == InvalidOid)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("extension \"%s\" does not exist",
|
|
extensionName)));
|
|
}
|
|
|
|
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
|
ObjectAddressSet(*address, ExtensionRelationId, extensionOid);
|
|
|
|
return list_make1(address);
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterExtensionUpdateStmtObjectAddress returns the ObjectAddress of the extension that is
|
|
* the subject of the AlterExtensionStmt. Errors if missing_ok is false.
|
|
*/
|
|
List *
|
|
AlterExtensionUpdateStmtObjectAddress(Node *node, bool missing_ok, bool isPostprocess)
|
|
{
|
|
AlterExtensionStmt *stmt = castNode(AlterExtensionStmt, node);
|
|
const char *extensionName = stmt->extname;
|
|
|
|
Oid extensionOid = get_extension_oid(extensionName, missing_ok);
|
|
|
|
if (extensionOid == InvalidOid)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("extension \"%s\" does not exist",
|
|
extensionName)));
|
|
}
|
|
|
|
ObjectAddress *address = palloc0(sizeof(ObjectAddress));
|
|
ObjectAddressSet(*address, ExtensionRelationId, extensionOid);
|
|
|
|
return list_make1(address);
|
|
}
|
|
|
|
|
|
/*
|
|
* CreateExtensionWithVersion builds and execute create extension statements
|
|
* per given extension name and extension verision
|
|
*/
|
|
void
|
|
CreateExtensionWithVersion(char *extname, char *extVersion)
|
|
{
|
|
CreateExtensionStmt *createExtensionStmt = makeNode(CreateExtensionStmt);
|
|
|
|
/* set location to -1 as it is unknown */
|
|
int location = -1;
|
|
|
|
/* set extension name and if_not_exists fields */
|
|
createExtensionStmt->extname = extname;
|
|
createExtensionStmt->if_not_exists = true;
|
|
|
|
if (extVersion == NULL)
|
|
{
|
|
createExtensionStmt->options = NIL;
|
|
}
|
|
else
|
|
{
|
|
Node *extensionVersionArg = (Node *) makeString(extVersion);
|
|
DefElem *extensionVersionElement = makeDefElem("new_version", extensionVersionArg,
|
|
location);
|
|
createExtensionStmt->options = lappend(createExtensionStmt->options,
|
|
extensionVersionElement);
|
|
}
|
|
|
|
CreateExtension(NULL, createExtensionStmt);
|
|
CommandCounterIncrement();
|
|
}
|
|
|
|
|
|
/*
|
|
* GetExtensionVersionNumber convert extension version to real value
|
|
*/
|
|
int
|
|
GetExtensionVersionNumber(char *extVersion)
|
|
{
|
|
char *strtokPosition = NULL;
|
|
char *versionVal = strtok_r(extVersion, "-", &strtokPosition);
|
|
double versionNumber = strtod(versionVal, NULL);
|
|
return (int) (versionNumber * 100);
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterExtensionUpdateStmt builds and execute Alter extension statements
|
|
* per given extension name and updates extension verision
|
|
*/
|
|
void
|
|
AlterExtensionUpdateStmt(char *extname, char *extVersion)
|
|
{
|
|
AlterExtensionStmt *alterExtensionStmt = makeNode(AlterExtensionStmt);
|
|
|
|
/* set location to -1 as it is unknown */
|
|
int location = -1;
|
|
alterExtensionStmt->extname = extname;
|
|
|
|
if (extVersion == NULL)
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT),
|
|
errmsg("alter extension \"%s\" should not be empty",
|
|
extVersion)));
|
|
}
|
|
|
|
Node *extensionVersionArg = (Node *) makeString(extVersion);
|
|
DefElem *extensionVersionElement = makeDefElem("new_version", extensionVersionArg,
|
|
location);
|
|
alterExtensionStmt->options = lappend(alterExtensionStmt->options,
|
|
extensionVersionElement);
|
|
ExecAlterExtensionStmt(NULL, alterExtensionStmt);
|
|
CommandCounterIncrement();
|
|
}
|