mirror of https://github.com/citusdata/citus.git
610 lines
17 KiB
C
610 lines
17 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* relay_event_utility.c
|
|
*
|
|
* Routines for handling DDL statements that relate to relay files. These
|
|
* routines extend relation, index and constraint names in utility commands.
|
|
*
|
|
* Copyright (c) 2012-2016, Citus Data, Inc.
|
|
*
|
|
* $Id$
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/genam.h"
|
|
#include "access/heapam.h"
|
|
#include "access/htup_details.h"
|
|
#include "access/skey.h"
|
|
#include "access/xact.h"
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/pg_constraint.h"
|
|
#include "commands/defrem.h"
|
|
#include "distributed/relay_utility.h"
|
|
#include "nodes/parsenodes.h"
|
|
#include "parser/parse_utilcmd.h"
|
|
#include "storage/lock.h"
|
|
#include "tcop/utility.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/tqual.h"
|
|
|
|
|
|
/* Local functions forward declarations */
|
|
static bool TypeAddIndexConstraint(const AlterTableCmd *command);
|
|
static bool TypeDropIndexConstraint(const AlterTableCmd *command,
|
|
const RangeVar *relation, uint64 shardId);
|
|
static void AppendShardIdToConstraintName(AlterTableCmd *command, uint64 shardId);
|
|
static void SetSchemaNameIfNotExist(char **schemaName, char *newSchemaName);
|
|
|
|
|
|
/*
|
|
* RelayEventExtendNames extends relation names in the given parse tree for
|
|
* certain utility commands. The function more specifically extends table,
|
|
* sequence, and index names in the parse tree by appending the given shardId;
|
|
* thereby avoiding name collisions in the database among sharded tables. This
|
|
* function has the side effect of extending relation names in the parse tree.
|
|
*/
|
|
void
|
|
RelayEventExtendNames(Node *parseTree, char *schemaName, uint64 shardId)
|
|
{
|
|
/* we don't extend names in extension or schema commands */
|
|
NodeTag nodeType = nodeTag(parseTree);
|
|
if (nodeType == T_CreateExtensionStmt || nodeType == T_CreateSchemaStmt)
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (nodeType)
|
|
{
|
|
case T_AlterSeqStmt:
|
|
{
|
|
AlterSeqStmt *alterSeqStmt = (AlterSeqStmt *) parseTree;
|
|
char **sequenceName = &(alterSeqStmt->sequence->relname);
|
|
char **sequenceSchemaName = &(alterSeqStmt->sequence->schemaname);
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(sequenceSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(sequenceName, shardId);
|
|
break;
|
|
}
|
|
|
|
case T_AlterTableStmt:
|
|
{
|
|
/*
|
|
* We append shardId to the very end of table, sequence and index
|
|
* names to avoid name collisions. We usually do not touch
|
|
* constraint names, except for cases where they refer to index
|
|
* names. In those cases, we also append to constraint names.
|
|
*/
|
|
|
|
AlterTableStmt *alterTableStmt = (AlterTableStmt *) parseTree;
|
|
char **relationName = &(alterTableStmt->relation->relname);
|
|
char **relationSchemaName = &(alterTableStmt->relation->schemaname);
|
|
RangeVar *relation = alterTableStmt->relation; /* for constraints */
|
|
|
|
List *commandList = alterTableStmt->cmds;
|
|
ListCell *commandCell = NULL;
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
|
|
|
|
/* append shardId to base relation name */
|
|
AppendShardIdToName(relationName, shardId);
|
|
|
|
foreach(commandCell, commandList)
|
|
{
|
|
AlterTableCmd *command = (AlterTableCmd *) lfirst(commandCell);
|
|
|
|
if (TypeAddIndexConstraint(command) ||
|
|
TypeDropIndexConstraint(command, relation, shardId))
|
|
{
|
|
AppendShardIdToConstraintName(command, shardId);
|
|
}
|
|
else if (command->subtype == AT_ClusterOn)
|
|
{
|
|
char **indexName = &(command->name);
|
|
AppendShardIdToName(indexName, shardId);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case T_ClusterStmt:
|
|
{
|
|
ClusterStmt *clusterStmt = (ClusterStmt *) parseTree;
|
|
char **relationName = NULL;
|
|
char **relationSchemaName = NULL;
|
|
|
|
/* we do not support clustering the entire database */
|
|
if (clusterStmt->relation == NULL)
|
|
{
|
|
ereport(ERROR, (errmsg("cannot extend name for multi-relation cluster")));
|
|
}
|
|
|
|
relationName = &(clusterStmt->relation->relname);
|
|
relationSchemaName = &(clusterStmt->relation->schemaname);
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(relationName, shardId);
|
|
|
|
if (clusterStmt->indexname != NULL)
|
|
{
|
|
char **indexName = &(clusterStmt->indexname);
|
|
AppendShardIdToName(indexName, shardId);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case T_CreateSeqStmt:
|
|
{
|
|
CreateSeqStmt *createSeqStmt = (CreateSeqStmt *) parseTree;
|
|
char **sequenceName = &(createSeqStmt->sequence->relname);
|
|
char **sequenceSchemaName = &(createSeqStmt->sequence->schemaname);
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(sequenceSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(sequenceName, shardId);
|
|
break;
|
|
}
|
|
|
|
case T_CreateForeignServerStmt:
|
|
{
|
|
CreateForeignServerStmt *serverStmt = (CreateForeignServerStmt *) parseTree;
|
|
char **serverName = &(serverStmt->servername);
|
|
|
|
AppendShardIdToName(serverName, shardId);
|
|
break;
|
|
}
|
|
|
|
case T_CreateForeignTableStmt:
|
|
{
|
|
CreateForeignTableStmt *createStmt = (CreateForeignTableStmt *) parseTree;
|
|
char **serverName = &(createStmt->servername);
|
|
|
|
AppendShardIdToName(serverName, shardId);
|
|
|
|
/*
|
|
* Since CreateForeignTableStmt inherits from CreateStmt and any change
|
|
* performed on CreateStmt should be done here too, we simply *fall
|
|
* through* to avoid code repetition.
|
|
*/
|
|
}
|
|
|
|
case T_CreateStmt:
|
|
{
|
|
CreateStmt *createStmt = (CreateStmt *) parseTree;
|
|
char **relationName = &(createStmt->relation->relname);
|
|
char **relationSchemaName = &(createStmt->relation->schemaname);
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(relationName, shardId);
|
|
break;
|
|
}
|
|
|
|
case T_DropStmt:
|
|
{
|
|
DropStmt *dropStmt = (DropStmt *) parseTree;
|
|
ObjectType objectType = dropStmt->removeType;
|
|
|
|
if (objectType == OBJECT_TABLE || objectType == OBJECT_SEQUENCE ||
|
|
objectType == OBJECT_INDEX || objectType == OBJECT_FOREIGN_TABLE ||
|
|
objectType == OBJECT_FOREIGN_SERVER)
|
|
{
|
|
List *relationNameList = NULL;
|
|
int relationNameListLength = 0;
|
|
Value *relationSchemaNameValue = NULL;
|
|
Value *relationNameValue = NULL;
|
|
char **relationName = NULL;
|
|
|
|
uint32 dropCount = list_length(dropStmt->objects);
|
|
if (dropCount > 1)
|
|
{
|
|
ereport(ERROR,
|
|
(errmsg("cannot extend name for multiple drop objects")));
|
|
}
|
|
|
|
/*
|
|
* We now need to extend a single relation, sequence or index
|
|
* name. To be able to do this extension, we need to extract the
|
|
* names' addresses from the value objects they are stored in.
|
|
* Otherwise, the repalloc called in AppendShardIdToName() will
|
|
* not have the correct memory address for the name.
|
|
*/
|
|
|
|
relationNameList = (List *) linitial(dropStmt->objects);
|
|
relationNameListLength = list_length(relationNameList);
|
|
|
|
switch (relationNameListLength)
|
|
{
|
|
case 1:
|
|
{
|
|
relationNameValue = linitial(relationNameList);
|
|
break;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
relationSchemaNameValue = linitial(relationNameList);
|
|
relationNameValue = lsecond(relationNameList);
|
|
break;
|
|
}
|
|
|
|
case 3:
|
|
{
|
|
relationSchemaNameValue = lsecond(relationNameList);
|
|
relationNameValue = lthird(relationNameList);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("improper relation name: \"%s\"",
|
|
NameListToString(relationNameList))));
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
if (relationSchemaNameValue == NULL)
|
|
{
|
|
Value *schemaNameValue = makeString(pstrdup(schemaName));
|
|
relationNameList = lcons(schemaNameValue, relationNameList);
|
|
}
|
|
|
|
relationName = &(relationNameValue->val.str);
|
|
AppendShardIdToName(relationName, shardId);
|
|
}
|
|
else
|
|
{
|
|
ereport(WARNING, (errmsg("unsafe object type in drop statement"),
|
|
errdetail("Object type: %u", (uint32) objectType)));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
|
|
case T_GrantStmt:
|
|
{
|
|
GrantStmt *grantStmt = (GrantStmt *) parseTree;
|
|
|
|
if (grantStmt->targtype == ACL_TARGET_OBJECT &&
|
|
grantStmt->objtype == ACL_OBJECT_RELATION)
|
|
{
|
|
ListCell *lc;
|
|
|
|
foreach(lc, grantStmt->objects)
|
|
{
|
|
RangeVar *relation = (RangeVar *) lfirst(lc);
|
|
char **relationName = &(relation->relname);
|
|
char **relationSchemaName = &(relation->schemaname);
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(relationName, shardId);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case T_IndexStmt:
|
|
{
|
|
IndexStmt *indexStmt = (IndexStmt *) parseTree;
|
|
char **relationName = &(indexStmt->relation->relname);
|
|
char **indexName = &(indexStmt->idxname);
|
|
char **relationSchemaName = &(indexStmt->relation->schemaname);
|
|
|
|
/*
|
|
* Concurrent index statements cannot run within a transaction block.
|
|
* Therefore, we do not support them.
|
|
*/
|
|
if (indexStmt->concurrent)
|
|
{
|
|
ereport(ERROR, (errmsg("cannot extend name for concurrent index")));
|
|
}
|
|
|
|
/*
|
|
* In the regular DDL execution code path (for non-sharded tables),
|
|
* if the index statement results from a table creation command, the
|
|
* indexName may be null. For sharded tables however, we intercept
|
|
* that code path and explicitly set the index name. Therefore, the
|
|
* index name in here cannot be null.
|
|
*/
|
|
if ((*indexName) == NULL)
|
|
{
|
|
ereport(ERROR, (errmsg("cannot extend name for null index name")));
|
|
}
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(relationSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(relationName, shardId);
|
|
AppendShardIdToName(indexName, shardId);
|
|
break;
|
|
}
|
|
|
|
case T_ReindexStmt:
|
|
{
|
|
ReindexStmt *reindexStmt = (ReindexStmt *) parseTree;
|
|
|
|
ReindexObjectType objectType = reindexStmt->kind;
|
|
if (objectType == REINDEX_OBJECT_TABLE || objectType == REINDEX_OBJECT_INDEX)
|
|
{
|
|
char **objectName = &(reindexStmt->relation->relname);
|
|
char **objectSchemaName = &(reindexStmt->relation->schemaname);
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(objectSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(objectName, shardId);
|
|
}
|
|
else if (objectType == REINDEX_OBJECT_DATABASE)
|
|
{
|
|
ereport(ERROR, (errmsg("cannot extend name for multi-relation reindex")));
|
|
}
|
|
else
|
|
{
|
|
ereport(ERROR, (errmsg("invalid object type in reindex statement"),
|
|
errdetail("Object type: %u", (uint32) objectType)));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case T_RenameStmt:
|
|
{
|
|
RenameStmt *renameStmt = (RenameStmt *) parseTree;
|
|
ObjectType objectType = renameStmt->renameType;
|
|
|
|
if (objectType == OBJECT_TABLE || objectType == OBJECT_SEQUENCE ||
|
|
objectType == OBJECT_INDEX)
|
|
{
|
|
char **oldRelationName = &(renameStmt->relation->relname);
|
|
char **newRelationName = &(renameStmt->newname);
|
|
char **objectSchemaName = &(renameStmt->relation->schemaname);
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(objectSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(oldRelationName, shardId);
|
|
AppendShardIdToName(newRelationName, shardId);
|
|
}
|
|
else if (objectType == OBJECT_COLUMN || objectType == OBJECT_TRIGGER)
|
|
{
|
|
char **relationName = &(renameStmt->relation->relname);
|
|
char **objectSchemaName = &(renameStmt->relation->schemaname);
|
|
|
|
/* prefix with schema name if it is not added already */
|
|
SetSchemaNameIfNotExist(objectSchemaName, schemaName);
|
|
|
|
AppendShardIdToName(relationName, shardId);
|
|
}
|
|
else
|
|
{
|
|
ereport(WARNING, (errmsg("unsafe object type in rename statement"),
|
|
errdetail("Object type: %u", (uint32) objectType)));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case T_TruncateStmt:
|
|
{
|
|
/*
|
|
* We currently do not support truncate statements. This is
|
|
* primarily because truncates allow implicit modifications to
|
|
* sequences through table column dependencies. As we have not
|
|
* determined our dependency model for sequences, we error here.
|
|
*/
|
|
ereport(ERROR, (errmsg("cannot extend name for truncate statement")));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
ereport(WARNING, (errmsg("unsafe statement type in name extension"),
|
|
errdetail("Statement type: %u", (uint32) nodeType)));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* TypeAddIndexConstraint checks if the alter table command adds a constraint
|
|
* and if that constraint also results in an index creation.
|
|
*/
|
|
static bool
|
|
TypeAddIndexConstraint(const AlterTableCmd *command)
|
|
{
|
|
if (command->subtype == AT_AddConstraint)
|
|
{
|
|
if (IsA(command->def, Constraint))
|
|
{
|
|
Constraint *constraint = (Constraint *) command->def;
|
|
if (constraint->contype == CONSTR_PRIMARY ||
|
|
constraint->contype == CONSTR_UNIQUE)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* TypeDropIndexConstraint checks if the alter table command drops a constraint
|
|
* and if that constraint also results in an index drop. Note that drop
|
|
* constraints do not have access to constraint type information; this is in
|
|
* contrast with add constraint commands. This function therefore performs
|
|
* additional system catalog lookups to determine if the drop constraint is
|
|
* associated with an index.
|
|
*/
|
|
static bool
|
|
TypeDropIndexConstraint(const AlterTableCmd *command,
|
|
const RangeVar *relation, uint64 shardId)
|
|
{
|
|
Relation pgConstraint = NULL;
|
|
SysScanDesc scanDescriptor = NULL;
|
|
ScanKeyData scanKey[1];
|
|
int scanKeyCount = 1;
|
|
HeapTuple heapTuple = NULL;
|
|
|
|
char *searchedConstraintName = NULL;
|
|
bool indexConstraint = false;
|
|
Oid relationId = InvalidOid;
|
|
bool failOK = true;
|
|
|
|
if (command->subtype != AT_DropConstraint)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* At this stage, our only option is performing a relationId lookup. We
|
|
* first find the relationId, and then scan the pg_constraints system
|
|
* catalog using this relationId. Finally, we check if the passed in
|
|
* constraint is for a primary key or unique index.
|
|
*/
|
|
relationId = RangeVarGetRelid(relation, NoLock, failOK);
|
|
if (!OidIsValid(relationId))
|
|
{
|
|
/* overlook this error, it should be signaled later in the pipeline */
|
|
return false;
|
|
}
|
|
|
|
searchedConstraintName = pnstrdup(command->name, NAMEDATALEN);
|
|
AppendShardIdToName(&searchedConstraintName, shardId);
|
|
|
|
pgConstraint = heap_open(ConstraintRelationId, AccessShareLock);
|
|
|
|
ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid,
|
|
BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relationId));
|
|
|
|
scanDescriptor = systable_beginscan(pgConstraint,
|
|
ConstraintRelidIndexId, true, /* indexOK */
|
|
NULL, scanKeyCount, scanKey);
|
|
|
|
heapTuple = systable_getnext(scanDescriptor);
|
|
while (HeapTupleIsValid(heapTuple))
|
|
{
|
|
Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple);
|
|
char *constraintName = NameStr(constraintForm->conname);
|
|
|
|
if (strncmp(constraintName, searchedConstraintName, NAMEDATALEN) == 0)
|
|
{
|
|
/* we found the constraint, now check if it is for an index */
|
|
if (constraintForm->contype == CONSTRAINT_PRIMARY ||
|
|
constraintForm->contype == CONSTRAINT_UNIQUE)
|
|
{
|
|
indexConstraint = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
heapTuple = systable_getnext(scanDescriptor);
|
|
}
|
|
|
|
systable_endscan(scanDescriptor);
|
|
heap_close(pgConstraint, AccessShareLock);
|
|
|
|
pfree(searchedConstraintName);
|
|
|
|
return indexConstraint;
|
|
}
|
|
|
|
|
|
/*
|
|
* AppendShardIdToConstraintName extends given constraint name with given
|
|
* shardId. Note that we only extend constraint names if they correspond to
|
|
* indexes, and the caller should verify that index correspondence before
|
|
* calling this function.
|
|
*/
|
|
static void
|
|
AppendShardIdToConstraintName(AlterTableCmd *command, uint64 shardId)
|
|
{
|
|
if (command->subtype == AT_AddConstraint)
|
|
{
|
|
Constraint *constraint = (Constraint *) command->def;
|
|
char **constraintName = &(constraint->conname);
|
|
AppendShardIdToName(constraintName, shardId);
|
|
}
|
|
else if (command->subtype == AT_DropConstraint)
|
|
{
|
|
char **constraintName = &(command->name);
|
|
AppendShardIdToName(constraintName, shardId);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* SetSchemaNameIfNotExist function checks whether schemaName is set and if it is not set
|
|
* it sets its value to given newSchemaName.
|
|
*/
|
|
static void
|
|
SetSchemaNameIfNotExist(char **schemaName, char *newSchemaName)
|
|
{
|
|
if ((*schemaName) == NULL)
|
|
{
|
|
*schemaName = pstrdup(newSchemaName);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* AppendShardIdToName appends shardId to the given name. The function takes in
|
|
* the name's address in order to reallocate memory for the name in the same
|
|
* memory context the name was originally created in.
|
|
*/
|
|
void
|
|
AppendShardIdToName(char **name, uint64 shardId)
|
|
{
|
|
char extendedName[NAMEDATALEN];
|
|
uint32 extendedNameLength = 0;
|
|
|
|
snprintf(extendedName, NAMEDATALEN, "%s%c" UINT64_FORMAT,
|
|
(*name), SHARD_NAME_SEPARATOR, shardId);
|
|
|
|
/*
|
|
* Parser should have already checked that the table name has enough space
|
|
* reserved for appending shardIds. Nonetheless, we perform an additional
|
|
* check here to verify that the appended name does not overflow.
|
|
*/
|
|
extendedNameLength = strlen(extendedName) + 1;
|
|
if (extendedNameLength >= NAMEDATALEN)
|
|
{
|
|
ereport(ERROR, (errmsg("shard name too long to extend: \"%s\"", (*name))));
|
|
}
|
|
|
|
(*name) = (char *) repalloc((*name), extendedNameLength);
|
|
snprintf((*name), extendedNameLength, "%s", extendedName);
|
|
}
|
|
|
|
|
|
/*
|
|
* AppendShardIdToStringInfo appends shardId to the given name, represented
|
|
* by a StringInfo.
|
|
*/
|
|
void
|
|
AppendShardIdToStringInfo(StringInfo name, uint64 shardId)
|
|
{
|
|
appendStringInfo(name, "%c" UINT64_FORMAT, SHARD_NAME_SEPARATOR, shardId);
|
|
}
|