Add citus_schema_distribute/undistribute udfs to convert a schema into a tenant schema / back to a regular schema (#6933)

* Currently we do not allow any Citus tables other than Citus local
tables inside a regular schema before executing
`citus_schema_distribute`.
* `citus_schema_undistribute` expects only single shard distributed
tables inside a tenant schema.

DESCRIPTION: Adds the udf `citus_schema_distribute` to convert a regular
schema into a tenant schema.
DESCRIPTION: Adds the udf `citus_schema_undistribute` to convert a
tenant schema back to a regular schema.

---------

Co-authored-by: Onur Tirtir <onurcantirtir@gmail.com>
pull/6997/head^2
aykut-bozkurt 2023-06-12 18:41:31 +03:00 committed by GitHub
parent e37ee16d59
commit 213d363bc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 3375 additions and 76 deletions

View File

@ -361,6 +361,37 @@ worker_change_sequence_dependency(PG_FUNCTION_ARGS)
}
/*
* UndistributeTables undistributes given relations. It first collects all foreign keys
* to recreate them after the undistribution. Then, drops the foreign keys and
* undistributes the relations. Finally, it recreates foreign keys.
*/
void
UndistributeTables(List *relationIdList)
{
/*
* Collect foreign keys for recreation and then drop fkeys and undistribute
* tables.
*/
List *originalForeignKeyRecreationCommands = NIL;
Oid relationId = InvalidOid;
foreach_oid(relationId, relationIdList)
{
List *fkeyCommandsForRelation =
GetFKeyCreationCommandsRelationInvolvedWithTableType(relationId,
INCLUDE_ALL_TABLE_TYPES);
originalForeignKeyRecreationCommands = list_concat(
originalForeignKeyRecreationCommands, fkeyCommandsForRelation);
DropFKeysAndUndistributeTable(relationId);
}
/* We can skip foreign key validations as we are sure about them at start */
bool skip_validation = true;
ExecuteForeignKeyCreateCommandList(originalForeignKeyRecreationCommands,
skip_validation);
}
/*
* UndistributeTable undistributes the given table. It uses ConvertTable function to
* create a new local table and move everything to that table.

View File

@ -159,10 +159,6 @@ static void EnsureCitusTableCanBeCreated(Oid relationOid);
static void PropagatePrerequisiteObjectsForDistributedTable(Oid relationId);
static void EnsureDistributedSequencesHaveOneType(Oid relationId,
List *seqInfoList);
static List * GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId,
int tableTypeFlag);
static Oid DropFKeysAndUndistributeTable(Oid relationId);
static void DropFKeysRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag);
static void CopyLocalDataIntoShards(Oid relationId);
static List * TupleDescColumnNameList(TupleDesc tupleDescriptor);
@ -1580,7 +1576,7 @@ EnsureDistributedSequencesHaveOneType(Oid relationId, List *seqInfoList)
* commands to recreate the foreign keys that relation with relationId is involved
* with given table type.
*/
static List *
List *
GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag)
{
int referencingFKeysFlag = INCLUDE_REFERENCING_CONSTRAINTS |
@ -1606,7 +1602,7 @@ GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId, int tableTy
* Also note that callers are responsible for storing & recreating foreign
* keys to be dropped if needed.
*/
static Oid
Oid
DropFKeysAndUndistributeTable(Oid relationId)
{
DropFKeysRelationInvolvedWithTableType(relationId, INCLUDE_ALL_TABLE_TYPES);
@ -1639,7 +1635,7 @@ DropFKeysAndUndistributeTable(Oid relationId)
* DropFKeysRelationInvolvedWithTableType drops foreign keys that relation
* with relationId is involved with given table type.
*/
static void
void
DropFKeysRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag)
{
int referencingFKeysFlag = INCLUDE_REFERENCING_CONSTRAINTS |

View File

@ -8,28 +8,43 @@
#include "postgres.h"
#include "miscadmin.h"
#include "access/genam.h"
#include "catalog/catalog.h"
#include "catalog/pg_namespace_d.h"
#include "commands/extension.h"
#include "distributed/argutils.h"
#include "distributed/backend_data.h"
#include "distributed/colocation_utils.h"
#include "distributed/commands.h"
#include "distributed/listutils.h"
#include "distributed/metadata_sync.h"
#include "distributed/metadata/distobject.h"
#include "distributed/multi_partitioning_utils.h"
#include "distributed/tenant_schema_metadata.h"
#include "distributed/metadata/distobject.h"
#include "distributed/worker_shard_visibility.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
PG_FUNCTION_INFO_V1(citus_internal_unregister_tenant_schema_globally);
static void UnregisterTenantSchemaGlobally(Oid schemaId, char *schemaName);
static List * SchemaGetNonShardTableIdList(Oid schemaId);
static void EnsureSchemaCanBeDistributed(Oid schemaId, List *schemaTableIdList);
static void EnsureTenantSchemaNameAllowed(Oid schemaId);
static void EnsureTableKindSupportedForTenantSchema(Oid relationId);
static void EnsureFKeysForTenantTable(Oid relationId);
static void EnsureSchemaExist(Oid schemaId);
/* controlled via citus.enable_schema_based_sharding GUC */
bool EnableSchemaBasedSharding = false;
PG_FUNCTION_INFO_V1(citus_internal_unregister_tenant_schema_globally);
PG_FUNCTION_INFO_V1(citus_schema_distribute);
PG_FUNCTION_INFO_V1(citus_schema_undistribute);
/*
* ShouldUseSchemaBasedSharding returns true if schema given name should be
* used as a tenant schema.
@ -108,6 +123,112 @@ ShouldCreateTenantSchemaTable(Oid relationId)
}
/*
* EnsureTableKindSupportedForTenantSchema ensures that given table's kind is
* supported by a tenant schema.
*/
static void
EnsureTableKindSupportedForTenantSchema(Oid relationId)
{
if (IsForeignTable(relationId))
{
ereport(ERROR, (errmsg("cannot create a foreign table in a distributed "
"schema")));
}
if (PartitionTable(relationId))
{
ErrorIfIllegalPartitioningInTenantSchema(PartitionParentOid(relationId),
relationId);
}
if (PartitionedTable(relationId))
{
List *partitionList = PartitionList(relationId);
Oid partitionRelationId = InvalidOid;
foreach_oid(partitionRelationId, partitionList)
{
ErrorIfIllegalPartitioningInTenantSchema(relationId, partitionRelationId);
}
}
if (IsChildTable(relationId) || IsParentTable(relationId))
{
ereport(ERROR, (errmsg("tables in a distributed schema cannot inherit or "
"be inherited")));
}
}
/*
* EnsureFKeysForTenantTable ensures that all referencing and referenced foreign
* keys are allowed for given table.
*/
static void
EnsureFKeysForTenantTable(Oid relationId)
{
Oid tenantSchemaId = get_rel_namespace(relationId);
int fKeyReferencingFlags = INCLUDE_REFERENCING_CONSTRAINTS | INCLUDE_ALL_TABLE_TYPES;
List *referencingForeignKeys = GetForeignKeyOids(relationId, fKeyReferencingFlags);
Oid foreignKeyId = InvalidOid;
foreach_oid(foreignKeyId, referencingForeignKeys)
{
Oid referencingTableId = GetReferencingTableId(foreignKeyId);
Oid referencedTableId = GetReferencedTableId(foreignKeyId);
Oid referencedTableSchemaId = get_rel_namespace(referencedTableId);
/* We allow foreign keys to a table in the same schema */
if (tenantSchemaId == referencedTableSchemaId)
{
continue;
}
/*
* Allow foreign keys to the other schema only if the referenced table is
* a reference table.
*/
if (!IsCitusTable(referencedTableId) ||
!IsCitusTableType(referencedTableId, REFERENCE_TABLE))
{
ereport(ERROR, (errmsg("foreign keys from distributed schemas can only "
"point to the same distributed schema or reference "
"tables in regular schemas"),
errdetail("\"%s\" references \"%s\" via foreign key "
"constraint \"%s\"",
generate_qualified_relation_name(
referencingTableId),
generate_qualified_relation_name(referencedTableId),
get_constraint_name(foreignKeyId))));
}
}
int fKeyReferencedFlags = INCLUDE_REFERENCED_CONSTRAINTS | INCLUDE_ALL_TABLE_TYPES;
List *referencedForeignKeys = GetForeignKeyOids(relationId, fKeyReferencedFlags);
foreach_oid(foreignKeyId, referencedForeignKeys)
{
Oid referencingTableId = GetReferencingTableId(foreignKeyId);
Oid referencedTableId = GetReferencedTableId(foreignKeyId);
Oid referencingTableSchemaId = get_rel_namespace(referencingTableId);
/* We allow foreign keys from a table in the same schema */
if (tenantSchemaId == referencingTableSchemaId)
{
continue;
}
/* Not allow any foreign keys from the other schema */
ereport(ERROR, (errmsg("cannot create foreign keys to tables in a distributed "
"schema from another schema"),
errdetail("\"%s\" references \"%s\" via foreign key "
"constraint \"%s\"",
generate_qualified_relation_name(referencingTableId),
generate_qualified_relation_name(referencedTableId),
get_constraint_name(foreignKeyId))));
}
}
/*
* CreateTenantSchemaTable creates a tenant table with given relationId.
*
@ -130,20 +251,12 @@ CreateTenantSchemaTable(Oid relationId)
* prefer to throw an error with a more meaningful message, rather
* than saying "operation is not allowed on this node".
*/
ereport(ERROR, (errmsg("cannot create a tenant table from a worker node"),
ereport(ERROR, (errmsg("cannot create tables in a distributed schema from "
"a worker node"),
errhint("Connect to the coordinator node and try again.")));
}
if (IsForeignTable(relationId))
{
/* throw an error that is nicer than the one CreateSingleShardTable() would throw */
ereport(ERROR, (errmsg("cannot create a tenant table from a foreign table")));
}
else if (PartitionTable(relationId))
{
ErrorIfIllegalPartitioningInTenantSchema(PartitionParentOid(relationId),
relationId);
}
EnsureTableKindSupportedForTenantSchema(relationId);
/*
* We don't expect this to happen because ShouldCreateTenantSchemaTable()
@ -153,7 +266,7 @@ CreateTenantSchemaTable(Oid relationId)
uint32 colocationId = SchemaIdGetTenantColocationId(schemaId);
if (colocationId == INVALID_COLOCATION_ID)
{
ereport(ERROR, (errmsg("schema \"%s\" is not a tenant schema",
ereport(ERROR, (errmsg("schema \"%s\" is not distributed",
get_namespace_name(schemaId))));
}
@ -169,29 +282,16 @@ CreateTenantSchemaTable(Oid relationId)
* ErrorIfIllegalPartitioningInTenantSchema throws an error if the
* partitioning relationship between the parent and the child is illegal
* because they are in different schemas while one of them is a tenant table.
*
* This function assumes that either the parent or the child are in a tenant
* schema.
*/
void
ErrorIfIllegalPartitioningInTenantSchema(Oid parentRelationId, Oid partitionRelationId)
{
Oid partitionSchemaId = get_rel_namespace(partitionRelationId);
Oid parentSchemaId = get_rel_namespace(parentRelationId);
bool partitionIsTenantTable = IsTenantSchema(partitionSchemaId);
bool parentIsTenantTable = IsTenantSchema(parentSchemaId);
bool illegalPartitioning = false;
if (partitionIsTenantTable != parentIsTenantTable)
if (get_rel_namespace(partitionRelationId) != get_rel_namespace(parentRelationId))
{
illegalPartitioning = true;
}
else if (partitionIsTenantTable && parentIsTenantTable)
{
illegalPartitioning = (parentSchemaId != partitionSchemaId);
}
if (illegalPartitioning)
{
ereport(ERROR, (errmsg("partitioning with tenant tables is not "
ereport(ERROR, (errmsg("partitioning within a distributed schema is not "
"supported when the parent and the child "
"are in different schemas")));
}
@ -199,9 +299,236 @@ ErrorIfIllegalPartitioningInTenantSchema(Oid parentRelationId, Oid partitionRela
/*
* citus_internal_unregister_tenant_schema_globally removes given schema from
* the tenant schema metadata table, deletes the colocation group of the schema
* and sends the command to do the same on the workers.
* CreateTenantSchemaColocationId returns new colocation id for a tenant schema.
*/
uint32
CreateTenantSchemaColocationId(void)
{
int shardCount = 1;
int replicationFactor = 1;
Oid distributionColumnType = InvalidOid;
Oid distributionColumnCollation = InvalidOid;
uint32 schemaColocationId = CreateColocationGroup(
shardCount, replicationFactor, distributionColumnType,
distributionColumnCollation);
return schemaColocationId;
}
/*
* SchemaGetNonShardTableIdList returns all nonshard relation ids
* inside given schema.
*/
static List *
SchemaGetNonShardTableIdList(Oid schemaId)
{
List *relationIdList = NIL;
/* scan all relations in pg_class and return all tables inside given schema */
Relation relationRelation = relation_open(RelationRelationId, AccessShareLock);
ScanKeyData scanKey[1] = { 0 };
ScanKeyInit(&scanKey[0], Anum_pg_class_relnamespace, BTEqualStrategyNumber,
F_OIDEQ, ObjectIdGetDatum(schemaId));
SysScanDesc scanDescriptor = systable_beginscan(relationRelation, ClassNameNspIndexId,
true, NULL, 1, scanKey);
HeapTuple heapTuple = NULL;
while (HeapTupleIsValid(heapTuple = systable_getnext(scanDescriptor)))
{
Form_pg_class relationForm = (Form_pg_class) GETSTRUCT(heapTuple);
char *relationName = NameStr(relationForm->relname);
Oid relationId = get_relname_relid(relationName, schemaId);
if (!OidIsValid(relationId))
{
ereport(ERROR, errmsg("table %s is dropped by a concurrent operation",
relationName));
}
/* skip shards */
if (RelationIsAKnownShard(relationId))
{
continue;
}
if (RegularTable(relationId) || PartitionTable(relationId) ||
IsForeignTable(relationId))
{
relationIdList = lappend_oid(relationIdList, relationId);
}
}
systable_endscan(scanDescriptor);
relation_close(relationRelation, AccessShareLock);
return relationIdList;
}
/*
* EnsureSchemaCanBeDistributed ensures the schema can be distributed.
* Caller should take required the lock on relations and the schema.
*
* It checks:
* - Schema name is in the allowed-list,
* - Schema does not depend on an extension (created by extension),
* - No extension depends on the schema (CREATE EXTENSION <ext> SCHEMA <schema>),
* - Current user should be the owner of tables under the schema,
* - Table kinds are supported,
* - Referencing and referenced foreign keys for the tables under the schema are
* supported,
* - Tables under the schema are not owned by an extension,
* - Only Citus local and Postgres local tables exist under the schema.
*/
static void
EnsureSchemaCanBeDistributed(Oid schemaId, List *schemaTableIdList)
{
/* Ensure schema name is allowed */
EnsureTenantSchemaNameAllowed(schemaId);
/* Any schema owned by extension is not allowed */
char *schemaName = get_namespace_name(schemaId);
ObjectAddress *schemaAddress = palloc0(sizeof(ObjectAddress));
ObjectAddressSet(*schemaAddress, NamespaceRelationId, schemaId);
if (IsAnyObjectAddressOwnedByExtension(list_make1(schemaAddress), NULL))
{
ereport(ERROR, (errmsg("schema %s, which is owned by an extension, cannot "
"be distributed", schemaName)));
}
/* Extension schemas are not allowed */
ObjectAddress *extensionAddress = FirstExtensionWithSchema(schemaId);
if (extensionAddress)
{
char *extensionName = get_extension_name(extensionAddress->objectId);
ereport(ERROR, (errmsg("schema %s cannot be distributed since it is the schema "
"of extension %s", schemaName, extensionName)));
}
Oid relationId = InvalidOid;
foreach_oid(relationId, schemaTableIdList)
{
/* Ensure table owner */
EnsureTableOwner(relationId);
/* Check relation kind */
EnsureTableKindSupportedForTenantSchema(relationId);
/* Check foreign keys */
EnsureFKeysForTenantTable(relationId);
/* Check table not owned by an extension */
ObjectAddress *tableAddress = palloc0(sizeof(ObjectAddress));
ObjectAddressSet(*tableAddress, RelationRelationId, relationId);
if (IsAnyObjectAddressOwnedByExtension(list_make1(tableAddress), NULL))
{
char *tableName = get_namespace_name(schemaId);
ereport(ERROR, (errmsg("schema cannot be distributed since it has "
"table %s which is owned by an extension",
tableName)));
}
/* Postgres local tables are allowed */
if (!IsCitusTable(relationId))
{
continue;
}
/* Only Citus local tables, amongst Citus table types, are allowed */
if (!IsCitusTableType(relationId, CITUS_LOCAL_TABLE))
{
ereport(ERROR, (errmsg("schema already has distributed tables"),
errhint("Undistribute distributed tables under "
"the schema before distributing the schema.")));
}
}
}
/*
* EnsureTenantSchemaNameAllowed ensures if given schema is applicable for registering
* as a tenant schema.
*/
static void
EnsureTenantSchemaNameAllowed(Oid schemaId)
{
char *schemaName = get_namespace_name(schemaId);
/* public schema is not allowed */
if (strcmp(schemaName, "public") == 0)
{
ereport(ERROR, (errmsg("public schema cannot be distributed")));
}
/* information_schema schema is not allowed */
if (strcmp(schemaName, "information_schema") == 0)
{
ereport(ERROR, (errmsg("information_schema schema cannot be distributed")));
}
/* pg_temp_xx and pg_toast_temp_xx schemas are not allowed */
if (isAnyTempNamespace(schemaId))
{
ereport(ERROR, (errmsg("temporary schema cannot be distributed")));
}
/* pg_catalog schema is not allowed */
if (IsCatalogNamespace(schemaId))
{
ereport(ERROR, (errmsg("pg_catalog schema cannot be distributed")));
}
/* pg_toast schema is not allowed */
if (IsToastNamespace(schemaId))
{
ereport(ERROR, (errmsg("pg_toast schema cannot be distributed")));
}
}
/*
* EnsureSchemaExist ensures that schema exists. Caller is responsible to take
* the required lock on the schema.
*/
static void
EnsureSchemaExist(Oid schemaId)
{
if (!SearchSysCacheExists1(NAMESPACEOID, ObjectIdGetDatum(schemaId)))
{
ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA),
errmsg("schema with OID %u does not exist", schemaId)));
}
}
/*
* UnregisterTenantSchemaGlobally removes given schema from the tenant schema
* metadata table, deletes the colocation group of the schema and sends the
* command to do the same on the workers.
*/
static void
UnregisterTenantSchemaGlobally(Oid schemaId, char *schemaName)
{
uint32 tenantSchemaColocationId = SchemaIdGetTenantColocationId(schemaId);
DeleteTenantSchemaLocally(schemaId);
if (EnableMetadataSync)
{
SendCommandToWorkersWithMetadata(TenantSchemaDeleteCommand(schemaName));
}
DeleteColocationGroup(tenantSchemaColocationId);
}
/*
* citus_internal_unregister_tenant_schema_globally, called by Citus drop hook,
* unregisters the schema when a tenant schema is dropped.
*
* NOTE: We need to pass schema_name as an argument. We cannot use schema id
* to obtain schema name since the schema would have already been dropped when this
* udf is called by the drop hook.
*/
Datum
citus_internal_unregister_tenant_schema_globally(PG_FUNCTION_ARGS)
@ -227,18 +554,185 @@ citus_internal_unregister_tenant_schema_globally(PG_FUNCTION_ARGS)
if (HeapTupleIsValid(namespaceTuple))
{
ReleaseSysCache(namespaceTuple);
ereport(ERROR, (errmsg("schema is expected to be already dropped "
"because this function is only expected to "
"be called from Citus drop hook")));
}
UnregisterTenantSchemaGlobally(schemaId, schemaNameStr);
PG_RETURN_VOID();
}
uint32 tenantSchemaColocationId = SchemaIdGetTenantColocationId(schemaId);
DeleteTenantSchemaLocally(schemaId);
SendCommandToWorkersWithMetadata(TenantSchemaDeleteCommand(schemaNameStr));
/*
* citus_schema_distribute gets a regular schema name, then converts it to a tenant
* schema.
*/
Datum
citus_schema_distribute(PG_FUNCTION_ARGS)
{
CheckCitusVersion(ERROR);
EnsureCoordinator();
DeleteColocationGroup(tenantSchemaColocationId);
Oid schemaId = PG_GETARG_OID(0);
EnsureSchemaExist(schemaId);
EnsureSchemaOwner(schemaId);
/* Prevent concurrent table creation under the schema */
LockDatabaseObject(NamespaceRelationId, schemaId, 0, AccessExclusiveLock);
/*
* We should ensure the existence of the schema after taking the lock since
* the schema could have been dropped before we acquired the lock.
*/
EnsureSchemaExist(schemaId);
EnsureSchemaOwner(schemaId);
/* Return if the schema is already a tenant schema */
char *schemaName = get_namespace_name(schemaId);
if (IsTenantSchema(schemaId))
{
ereport(NOTICE, (errmsg("schema %s is already distributed", schemaName)));
PG_RETURN_VOID();
}
/* Take lock on the relations and filter out partition tables */
List *tableIdListInSchema = SchemaGetNonShardTableIdList(schemaId);
List *tableIdListToConvert = NIL;
Oid relationId = InvalidOid;
foreach_oid(relationId, tableIdListInSchema)
{
/* prevent concurrent drop of the relation */
LockRelationOid(relationId, AccessShareLock);
EnsureRelationExists(relationId);
/*
* Skip partitions as they would be distributed by the parent table.
*
* We should filter out partitions here before distributing the schema.
* Otherwise, converted partitioned table would change oid of partitions and its
* partition tables would fail with oid not exist.
*/
if (PartitionTable(relationId))
{
continue;
}
tableIdListToConvert = lappend_oid(tableIdListToConvert, relationId);
}
/* Makes sure the schema can be distributed. */
EnsureSchemaCanBeDistributed(schemaId, tableIdListInSchema);
ereport(NOTICE, (errmsg("distributing the schema %s", schemaName)));
/* Create colocation id and then single shard tables with the colocation id */
uint32 colocationId = CreateTenantSchemaColocationId();
ColocationParam colocationParam = {
.colocationParamType = COLOCATE_WITH_COLOCATION_ID,
.colocationId = colocationId,
};
/*
* Collect foreign keys for recreation and then drop fkeys and create single shard
* tables.
*/
List *originalForeignKeyRecreationCommands = NIL;
foreach_oid(relationId, tableIdListToConvert)
{
List *fkeyCommandsForRelation =
GetFKeyCreationCommandsRelationInvolvedWithTableType(relationId,
INCLUDE_ALL_TABLE_TYPES);
originalForeignKeyRecreationCommands = list_concat(
originalForeignKeyRecreationCommands, fkeyCommandsForRelation);
DropFKeysRelationInvolvedWithTableType(relationId, INCLUDE_ALL_TABLE_TYPES);
CreateSingleShardTable(relationId, colocationParam);
}
/* We can skip foreign key validations as we are sure about them at start */
bool skip_validation = true;
ExecuteForeignKeyCreateCommandList(originalForeignKeyRecreationCommands,
skip_validation);
/* Register the schema locally and sync it to workers */
InsertTenantSchemaLocally(schemaId, colocationId);
char *registerSchemaCommand = TenantSchemaInsertCommand(schemaId, colocationId);
if (EnableMetadataSync)
{
SendCommandToWorkersWithMetadata(registerSchemaCommand);
}
PG_RETURN_VOID();
}
/*
* citus_schema_undistribute gets a tenant schema name, then converts it to a regular
* schema by undistributing all tables under it.
*/
Datum
citus_schema_undistribute(PG_FUNCTION_ARGS)
{
CheckCitusVersion(ERROR);
EnsureCoordinator();
Oid schemaId = PG_GETARG_OID(0);
EnsureSchemaExist(schemaId);
EnsureSchemaOwner(schemaId);
/* Prevent concurrent table creation under the schema */
LockDatabaseObject(NamespaceRelationId, schemaId, 0, AccessExclusiveLock);
/*
* We should ensure the existence of the schema after taking the lock since
* the schema could have been dropped before we acquired the lock.
*/
EnsureSchemaExist(schemaId);
EnsureSchemaOwner(schemaId);
/* The schema should be a tenant schema */
char *schemaName = get_namespace_name(schemaId);
if (!IsTenantSchema(schemaId))
{
ereport(ERROR, (errmsg("schema %s is not distributed", schemaName)));
}
ereport(NOTICE, (errmsg("undistributing schema %s", schemaName)));
/* Take lock on the relations and filter out partition tables */
List *tableIdListInSchema = SchemaGetNonShardTableIdList(schemaId);
List *tableIdListToConvert = NIL;
Oid relationId = InvalidOid;
foreach_oid(relationId, tableIdListInSchema)
{
/* prevent concurrent drop of the relation */
LockRelationOid(relationId, AccessShareLock);
EnsureRelationExists(relationId);
/*
* Skip partitions as they would be undistributed by the parent table.
*
* We should filter out partitions here before undistributing the schema.
* Otherwise, converted partitioned table would change oid of partitions and its
* partition tables would fail with oid not exist.
*/
if (PartitionTable(relationId))
{
continue;
}
tableIdListToConvert = lappend_oid(tableIdListToConvert, relationId);
/* Only single shard tables are expected during the undistribution of the schema */
Assert(IsCitusTableType(relationId, SINGLE_SHARD_DISTRIBUTED));
}
/*
* First, we need to delete schema metadata and sync it to workers. Otherwise,
* we would get error from `ErrorIfTenantTable` while undistributing the tables.
*/
UnregisterTenantSchemaGlobally(schemaId, schemaName);
UndistributeTables(tableIdListToConvert);
PG_RETURN_VOID();
}
@ -253,7 +747,8 @@ ErrorIfTenantTable(Oid relationId, char *operationName)
{
if (IsTenantSchema(get_rel_namespace(relationId)))
{
ereport(ERROR, (errmsg("%s is not allowed for %s because it is a tenant table",
ereport(ERROR, (errmsg("%s is not allowed for %s because it belongs to "
"a distributed schema",
generate_qualified_relation_name(relationId),
operationName)));
}

View File

@ -414,7 +414,11 @@ PostprocessCreateTableStmtPartitionOf(CreateStmt *createStatement, const
}
}
ErrorIfIllegalPartitioningInTenantSchema(PartitionParentOid(relationId), relationId);
if (IsTenantSchema(get_rel_namespace(parentRelationId)) ||
IsTenantSchema(get_rel_namespace(relationId)))
{
ErrorIfIllegalPartitioningInTenantSchema(parentRelationId, relationId);
}
/*
* If a partition is being created and if its parent is a distributed
@ -496,8 +500,12 @@ PreprocessAlterTableStmtAttachPartition(AlterTableStmt *alterTableStatement,
return NIL;
}
ErrorIfIllegalPartitioningInTenantSchema(parentRelationId,
partitionRelationId);
if (IsTenantSchema(get_rel_namespace(parentRelationId)) ||
IsTenantSchema(get_rel_namespace(partitionRelationId)))
{
ErrorIfIllegalPartitioningInTenantSchema(parentRelationId,
partitionRelationId);
}
if (!IsCitusTable(parentRelationId))
{

View File

@ -1192,6 +1192,47 @@ IsAnyObjectAddressOwnedByExtension(const List *targets,
}
/*
* FirstExtensionWithSchema returns the first extension address whose schema is the same
* as given schema. If no extension depends on the schema, it returns NULL.
* i.e. decide if given schema is an extension schema as in
* `CREATE EXTENSION <ext> [WITH] SCHEMA <schema>;`
*/
ObjectAddress *
FirstExtensionWithSchema(Oid schemaId)
{
ObjectAddress *extensionAddress = NULL;
Relation relation = table_open(ExtensionRelationId, AccessShareLock);
ScanKeyData entry[1];
ScanKeyInit(&entry[0], Anum_pg_extension_extnamespace, BTEqualStrategyNumber,
F_INT4EQ, schemaId);
SysScanDesc scan = systable_beginscan(relation, InvalidOid, false, NULL, 1, entry);
HeapTuple extensionTuple = systable_getnext(scan);
if (HeapTupleIsValid(extensionTuple))
{
int extensionIdIndex = Anum_pg_extension_oid;
TupleDesc tupleDescriptor = RelationGetDescr(relation);
bool isNull = false;
Datum extensionIdDatum = heap_getattr(extensionTuple, extensionIdIndex,
tupleDescriptor, &isNull);
Oid extensionId = DatumGetObjectId(extensionIdDatum);
extensionAddress = palloc0(sizeof(ObjectAddress));
extensionAddress->objectId = extensionId;
extensionAddress->classId = ExtensionRelationId;
extensionAddress->objectSubId = 0;
}
systable_endscan(scan);
table_close(relation, AccessShareLock);
return extensionAddress;
}
/*
* IsObjectAddressOwnedByCitus returns true if the given object address
* is owned by the citus or citus_columnar extensions.

View File

@ -2256,6 +2256,21 @@ EnsureTableOwner(Oid relationId)
}
/*
* Check that the current user has owner rights to schemaId, error out if
* not. Superusers are regarded as owners.
*/
void
EnsureSchemaOwner(Oid schemaId)
{
if (!pg_namespace_ownercheck(schemaId, GetUserId()))
{
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SCHEMA,
get_namespace_name(schemaId));
}
}
/*
* Check that the current user has owner rights to functionId, error out if
* not. Superusers are regarded as owners. Functions and procedures are

View File

@ -26,3 +26,7 @@ GRANT SELECT ON pg_catalog.pg_dist_tenant_schema TO public;
#include "udfs/citus_tables/12.0-1.sql"
#include "udfs/citus_shards/12.0-1.sql"
-- udfs to convert a regular/tenant schema to a tenant/regular schema
#include "udfs/citus_schema_distribute/12.0-1.sql"
#include "udfs/citus_schema_undistribute/12.0-1.sql"

View File

@ -30,6 +30,9 @@ BEGIN
END;
$$ LANGUAGE plpgsql;
DROP FUNCTION pg_catalog.citus_schema_distribute(regnamespace);
DROP FUNCTION pg_catalog.citus_schema_undistribute(regnamespace);
DROP FUNCTION pg_catalog.citus_internal_add_tenant_schema(Oid, int);
#include "../udfs/citus_prepare_pg_upgrade/11.2-1.sql"

View File

@ -0,0 +1,6 @@
CREATE OR REPLACE FUNCTION pg_catalog.citus_schema_distribute(schemaname regnamespace)
RETURNS void
LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_schema_distribute$$;
COMMENT ON FUNCTION pg_catalog.citus_schema_distribute(schemaname regnamespace)
IS 'distributes a schema, allowing it to move between nodes';

View File

@ -0,0 +1,6 @@
CREATE OR REPLACE FUNCTION pg_catalog.citus_schema_distribute(schemaname regnamespace)
RETURNS void
LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_schema_distribute$$;
COMMENT ON FUNCTION pg_catalog.citus_schema_distribute(schemaname regnamespace)
IS 'distributes a schema, allowing it to move between nodes';

View File

@ -0,0 +1,6 @@
CREATE OR REPLACE FUNCTION pg_catalog.citus_schema_undistribute(schemaname regnamespace)
RETURNS void
LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_schema_undistribute$$;
COMMENT ON FUNCTION pg_catalog.citus_schema_undistribute(schemaname regnamespace)
IS 'reverts schema distribution, moving it back to the coordinator';

View File

@ -0,0 +1,6 @@
CREATE OR REPLACE FUNCTION pg_catalog.citus_schema_undistribute(schemaname regnamespace)
RETURNS void
LANGUAGE C STRICT
AS 'MODULE_PATHNAME', $$citus_schema_undistribute$$;
COMMENT ON FUNCTION pg_catalog.citus_schema_undistribute(schemaname regnamespace)
IS 'reverts schema distribution, moving it back to the coordinator';

View File

@ -15,6 +15,7 @@
#include "postgres.h"
#include "distributed/metadata_utility.h"
#include "utils/rel.h"
#include "nodes/parsenodes.h"
#include "tcop/dest.h"
@ -273,6 +274,10 @@ extern List * GetForeignConstraintToReferenceTablesCommands(Oid relationId);
extern List * GetForeignConstraintToDistributedTablesCommands(Oid relationId);
extern List * GetForeignConstraintFromDistributedTablesCommands(Oid relationId);
extern List * GetForeignConstraintCommandsInternal(Oid relationId, int flags);
extern Oid DropFKeysAndUndistributeTable(Oid relationId);
extern void DropFKeysRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag);
extern List * GetFKeyCreationCommandsRelationInvolvedWithTableType(Oid relationId,
int tableTypeFlag);
extern bool AnyForeignKeyDependsOnIndex(Oid indexId);
extern bool HasForeignKeyWithLocalTable(Oid relationId);
extern bool HasForeignKeyToReferenceTable(Oid relationOid);
@ -793,5 +798,6 @@ extern void ErrorIfIllegalPartitioningInTenantSchema(Oid parentRelationId,
extern void CreateTenantSchemaTable(Oid relationId);
extern void ErrorIfTenantTable(Oid relationId, char *operationName);
extern void ErrorIfTenantSchema(Oid nspOid, char *operationName);
extern uint32 CreateTenantSchemaColocationId(void);
#endif /*CITUS_COMMANDS_H */

View File

@ -30,6 +30,7 @@ extern bool IsTableOwnedByExtension(Oid relationId);
extern bool ObjectAddressDependsOnExtension(const ObjectAddress *target);
extern bool IsAnyObjectAddressOwnedByExtension(const List *targets,
ObjectAddress *extensionAddress);
extern ObjectAddress * FirstExtensionWithSchema(Oid schemaId);
extern bool IsObjectAddressOwnedByCitus(const ObjectAddress *objectAddress);
extern ObjectAddress PgGetObjectAddress(char *ttype, ArrayType *namearr,
ArrayType *argsarr);

View File

@ -364,6 +364,7 @@ extern void CreateDistributedTable(Oid relationId, char *distributionColumnName,
extern void CreateReferenceTable(Oid relationId);
extern void CreateTruncateTrigger(Oid relationId);
extern TableConversionReturn * UndistributeTable(TableConversionParameters *params);
extern void UndistributeTables(List *relationIdList);
extern void EnsureAllObjectDependenciesExistOnAllNodes(const List *targets);
extern DeferredErrorMessage * DeferErrorIfCircularDependencyExists(const
@ -383,6 +384,7 @@ extern void EnsureTableOwner(Oid relationId);
extern void EnsureHashDistributedTable(Oid relationId);
extern void EnsureHashOrSingleShardDistributedTable(Oid relationId);
extern void EnsureFunctionOwner(Oid functionId);
extern void EnsureSchemaOwner(Oid schemaId);
extern void EnsureSuperUser(void);
extern void ErrorIfTableIsACatalogTable(Relation relation);
extern void EnsureTableNotDistributed(Oid relationId);

View File

@ -0,0 +1,923 @@
SET citus.next_shard_id TO 1730000;
SET citus.shard_replication_factor TO 1;
SET client_min_messages TO WARNING;
SET citus.enable_schema_based_sharding TO off;
SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0);
?column?
---------------------------------------------------------------------
1
(1 row)
CREATE USER tenantuser superuser;
SET role tenantuser;
-- check invalid input
SELECT citus_schema_distribute(1);
ERROR: schema with OID 1 does not exist
SELECT citus_schema_undistribute(1);
ERROR: schema with OID 1 does not exist
-- noop
SELECT citus_schema_distribute(null);
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute(null);
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- public and some others cannot be distributed as a tenant schema, but check what happens
-- if we try to call citus_schema_undistribute() for such a schema.
SELECT citus_schema_undistribute('public');
ERROR: schema public is not distributed
-- create non-tenant schema
CREATE SCHEMA citus_schema_distribute_undistribute;
-- create tenant schema
CREATE SCHEMA tenant1;
CREATE TABLE tenant1.table1(id int PRIMARY KEY, name text);
INSERT INTO tenant1.table1 SELECT i, 'asd'::text FROM generate_series(1,20) i;
CREATE TABLE tenant1.table2(id int REFERENCES tenant1.table1(id), num bigint UNIQUE);
INSERT INTO tenant1.table2 SELECT i, i FROM generate_series(1,20) i;
CREATE TABLE citus_schema_distribute_undistribute.ref(id int PRIMARY KEY);
SELECT create_reference_table('citus_schema_distribute_undistribute.ref');
create_reference_table
---------------------------------------------------------------------
(1 row)
INSERT INTO citus_schema_distribute_undistribute.ref SELECT i FROM generate_series(1,100) i;
-- autoconverted to Citus local table due to foreign key to reference table
CREATE TABLE tenant1.table3(id int REFERENCES citus_schema_distribute_undistribute.ref(id));
INSERT INTO tenant1.table3 SELECT i FROM generate_series(1,100) i;
-- Citus local table with autoconverted=false
CREATE TABLE tenant1.table3x(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.ref(id));
SELECT citus_add_local_table_to_metadata('tenant1.table3x');
citus_add_local_table_to_metadata
---------------------------------------------------------------------
(1 row)
INSERT INTO tenant1.table3x SELECT i FROM generate_series(1,100) i;
-- foreign key to another local table in the same schema
CREATE TABLE tenant1.table3y(id int PRIMARY KEY REFERENCES tenant1.table3x(id));
SELECT citus_add_local_table_to_metadata('tenant1.table3y');
citus_add_local_table_to_metadata
---------------------------------------------------------------------
(1 row)
INSERT INTO tenant1.table3y SELECT i FROM generate_series(1,100) i;
-- table with composite type
CREATE TYPE tenant1.catname AS ENUM ('baby', 'teen', 'mid');
CREATE TYPE tenant1.agecat AS (below_age int, name tenant1.catname);
CREATE TABLE tenant1.table4(id int, age tenant1.agecat);
-- create autoconverted partitioned table
CREATE TABLE tenant1.partitioned_table(id int REFERENCES citus_schema_distribute_undistribute.ref(id)) PARTITION BY RANGE(id);
CREATE TABLE tenant1.partition1 PARTITION OF tenant1.partitioned_table FOR VALUES FROM (1) TO (11);
CREATE TABLE tenant1.partition2 PARTITION OF tenant1.partitioned_table FOR VALUES FROM (11) TO (21);
INSERT INTO tenant1.partitioned_table SELECT i FROM generate_series(1,20) i;
-- create view
CREATE VIEW tenant1.view1 AS SELECT * FROM tenant1.table1 JOIN tenant1.table2 USING(id);
WARNING: "view tenant1.view1" has dependency to "table tenant1.table2" that is not in Citus' metadata
DETAIL: "view tenant1.view1" will be created only locally
HINT: Distribute "table tenant1.table2" first to distribute "view tenant1.view1"
-- create view in regular schema
CREATE VIEW citus_schema_distribute_undistribute.view2 AS SELECT * FROM tenant1.view1;
WARNING: "view citus_schema_distribute_undistribute.view2" has dependency to "table tenant1.table2" that is not in Citus' metadata
DETAIL: "view citus_schema_distribute_undistribute.view2" will be created only locally
HINT: Distribute "table tenant1.table2" first to distribute "view citus_schema_distribute_undistribute.view2"
-- create materialized view
CREATE MATERIALIZED VIEW tenant1.view2 AS SELECT * FROM tenant1.table1;
-- create collation
CREATE COLLATION citus_schema_distribute_undistribute.german_phonebook (provider = icu, locale = 'de-u-co-phonebk');
-- create type
CREATE TYPE citus_schema_distribute_undistribute.pair_type AS (a int, b int);
-- Create function
CREATE FUNCTION citus_schema_distribute_undistribute.one_as_result() RETURNS INT LANGUAGE SQL AS
$$
SELECT 1;
$$;
-- create text search dictionary
CREATE TEXT SEARCH DICTIONARY citus_schema_distribute_undistribute.my_german_dict (
template = snowball,
language = german,
stopwords = german
);
-- create text search config
CREATE TEXT SEARCH CONFIGURATION citus_schema_distribute_undistribute.my_ts_config ( parser = default );
ALTER TEXT SEARCH CONFIGURATION citus_schema_distribute_undistribute.my_ts_config ALTER MAPPING FOR asciiword WITH citus_schema_distribute_undistribute.my_german_dict;
-- create sequence
CREATE SEQUENCE citus_schema_distribute_undistribute.seq;
-- create complex table
CREATE TABLE tenant1.complextable (id int PRIMARY KEY default nextval('citus_schema_distribute_undistribute.seq'), col int default (citus_schema_distribute_undistribute.one_as_result()), myserial serial, phone text COLLATE citus_schema_distribute_undistribute.german_phonebook, initials citus_schema_distribute_undistribute.pair_type);
CREATE SEQUENCE tenant1.seq_owned OWNED BY tenant1.complextable.id;
-- not allowed from workers
SELECT run_command_on_workers($$SELECT citus_schema_distribute('tenant1');$$);
run_command_on_workers
---------------------------------------------------------------------
(localhost,57637,f,"ERROR: operation is not allowed on this node")
(localhost,57638,f,"ERROR: operation is not allowed on this node")
(2 rows)
SELECT run_command_on_workers($$SELECT citus_schema_undistribute('tenant1');$$);
run_command_on_workers
---------------------------------------------------------------------
(localhost,57637,f,"ERROR: operation is not allowed on this node")
(localhost,57638,f,"ERROR: operation is not allowed on this node")
(2 rows)
-- inherited table not allowed
CREATE TABLE citus_schema_distribute_undistribute.cities (
name text,
population real,
elevation int
);
CREATE TABLE citus_schema_distribute_undistribute.capitals (
state char(2) UNIQUE NOT NULL
) INHERITS (citus_schema_distribute_undistribute.cities);
-- temporarily move "cities" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a table that is inherited
ALTER TABLE citus_schema_distribute_undistribute.cities SET SCHEMA tenant1;
SELECT citus_schema_distribute('tenant1');
ERROR: tables in a distributed schema cannot inherit or be inherited
ALTER TABLE tenant1.cities SET SCHEMA citus_schema_distribute_undistribute;
-- temporarily move "capitals" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a table that inherits
ALTER TABLE citus_schema_distribute_undistribute.capitals SET SCHEMA tenant1;
SELECT citus_schema_distribute('tenant1');
ERROR: tables in a distributed schema cannot inherit or be inherited
ALTER TABLE tenant1.capitals SET SCHEMA citus_schema_distribute_undistribute;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
CREATE TABLE citus_schema_distribute_undistribute.illegal_partitioned_table(id int) PARTITION BY RANGE(id);
CREATE TABLE citus_schema_distribute_undistribute.illegal_partition1 PARTITION OF citus_schema_distribute_undistribute.illegal_partitioned_table FOR VALUES FROM (1) TO (11);
-- temporarily move "illegal_partitioned_table" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a partition table whose parent is created in another schema
ALTER TABLE citus_schema_distribute_undistribute.illegal_partitioned_table SET SCHEMA tenant1;
SELECT citus_schema_distribute('tenant1');
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
ALTER TABLE tenant1.illegal_partitioned_table SET SCHEMA citus_schema_distribute_undistribute;
-- temporarily move "illegal_partition1" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a parent table whose partition is created in another schema
ALTER TABLE citus_schema_distribute_undistribute.illegal_partition1 SET SCHEMA tenant1;
SELECT citus_schema_distribute('tenant1');
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
ALTER TABLE tenant1.illegal_partition1 SET SCHEMA citus_schema_distribute_undistribute;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- foreign key to a local table in another schema is not allowed
CREATE TABLE citus_schema_distribute_undistribute.tbl1(id int PRIMARY KEY);
CREATE TABLE tenant1.table3z(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.tbl1(id));
SELECT citus_schema_distribute('tenant1');
ERROR: foreign keys from distributed schemas can only point to the same distributed schema or reference tables in regular schemas
DETAIL: "tenant1.table3z" references "citus_schema_distribute_undistribute.tbl1" via foreign key constraint "table3z_id_fkey"
SELECT create_reference_table('citus_schema_distribute_undistribute.tbl1');
create_reference_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- foreign key to a distributed table in another schema is not allowed
CREATE TABLE citus_schema_distribute_undistribute.tbl2(id int PRIMARY KEY);
SELECT create_distributed_table('citus_schema_distribute_undistribute.tbl2','id');
create_distributed_table
---------------------------------------------------------------------
(1 row)
CREATE TABLE tenant1.table3w(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.tbl2(id));
SELECT citus_schema_distribute('tenant1');
ERROR: foreign keys from distributed schemas can only point to the same distributed schema or reference tables in regular schemas
DETAIL: "tenant1.table3w" references "citus_schema_distribute_undistribute.tbl2" via foreign key constraint "table3w_id_fkey"
DROP TABLE citus_schema_distribute_undistribute.tbl2 CASCADE;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- foreign key from a local table in another schema is not allowed
CREATE TABLE tenant1.table3q(id int PRIMARY KEY);
CREATE TABLE citus_schema_distribute_undistribute.tbl3(id int PRIMARY KEY REFERENCES tenant1.table3q(id));
SELECT citus_schema_distribute('tenant1');
ERROR: cannot create foreign keys to tables in a distributed schema from another schema
DETAIL: "citus_schema_distribute_undistribute.tbl3" references "tenant1.table3q" via foreign key constraint "tbl3_id_fkey"
DROP TABLE citus_schema_distribute_undistribute.tbl3 CASCADE;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- foreign key from a reference table in another schema is not allowed
CREATE TABLE tenant1.table3t(id int PRIMARY KEY);
CREATE TABLE citus_schema_distribute_undistribute.tbl4(id int PRIMARY KEY REFERENCES tenant1.table3t(id));
SELECT create_reference_table('citus_schema_distribute_undistribute.tbl4');
create_reference_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
ERROR: cannot create foreign keys to tables in a distributed schema from another schema
DETAIL: "citus_schema_distribute_undistribute.tbl4" references "tenant1.table3t" via foreign key constraint "tbl4_id_fkey"
DROP TABLE citus_schema_distribute_undistribute.tbl4 CASCADE;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- only allowed for schema owner or superuser
CREATE USER dummyregular;
SET role dummyregular;
SELECT citus_schema_distribute('tenant1');
ERROR: must be owner of schema tenant1
SELECT citus_schema_undistribute('tenant1');
ERROR: must be owner of schema tenant1
-- assign all tables to dummyregular except table5
SET role tenantuser;
SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY tenantuser TO dummyregular; $$);
result
---------------------------------------------------------------------
REASSIGN OWNED
REASSIGN OWNED
REASSIGN OWNED
(3 rows)
CREATE TABLE tenant1.table5(id int);
-- table owner check fails the distribution
SET role dummyregular;
SELECT citus_schema_distribute('tenant1');
ERROR: must be owner of table table5
-- alter table owner, then redistribute
SET role tenantuser;
ALTER TABLE tenant1.table5 OWNER TO dummyregular;
SET role dummyregular;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
-- below query verifies the same colocationid in pg_dist_tenant_schema, pg_dist_colocation and all entries in pg_dist_partition at the same time
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$);
result
---------------------------------------------------------------------
15
15
15
(3 rows)
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a regular schema
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
result
---------------------------------------------------------------------
(3 rows)
-- below query verifies the tenant colocationid is removed from both pg_dist_colocation and all entries in pg_dist_partition at the same time
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
result
---------------------------------------------------------------------
(3 rows)
RESET role;
SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY dummyregular TO tenantuser; $$);
result
---------------------------------------------------------------------
REASSIGN OWNED
REASSIGN OWNED
REASSIGN OWNED
(3 rows)
DROP USER dummyregular;
CREATE USER dummysuper superuser;
SET role dummysuper;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$);
result
---------------------------------------------------------------------
15
15
15
(3 rows)
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a regular schema
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
result
---------------------------------------------------------------------
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
result
---------------------------------------------------------------------
(3 rows)
RESET role;
DROP USER dummysuper;
-- foreign table
CREATE TABLE tenant1.foreign_table_test (id integer NOT NULL, data text, a bigserial);
INSERT INTO tenant1.foreign_table_test SELECT i FROM generate_series(1,100) i;
CREATE EXTENSION postgres_fdw;
CREATE SERVER foreign_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port :'master_port', dbname 'regression');
CREATE USER MAPPING FOR CURRENT_USER
SERVER foreign_server
OPTIONS (user 'postgres');
CREATE FOREIGN TABLE tenant1.foreign_table (
id integer NOT NULL,
data text,
a bigserial
)
SERVER foreign_server
OPTIONS (schema_name 'tenant1', table_name 'foreign_table_test');
-- foreign table not allowed
SELECT citus_schema_distribute('tenant1');
ERROR: cannot create a foreign table in a distributed schema
ALTER FOREIGN TABLE tenant1.foreign_table SET SCHEMA citus_schema_distribute_undistribute;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- already have distributed table error
CREATE TABLE tenant1.dist(id int);
SELECT create_distributed_table('tenant1.dist', 'id');
create_distributed_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
ERROR: schema already has distributed tables
HINT: Undistribute distributed tables under the schema before distributing the schema.
SELECT undistribute_table('tenant1.dist');
undistribute_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
CREATE TABLE tenant1.ref2(id int);
SELECT create_reference_table('tenant1.ref2');
create_reference_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
ERROR: schema already has distributed tables
HINT: Undistribute distributed tables under the schema before distributing the schema.
SELECT undistribute_table('tenant1.ref2');
undistribute_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
BEGIN;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
ROLLBACK;
-- show the schema is a regular schema
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
result
---------------------------------------------------------------------
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
result
---------------------------------------------------------------------
(3 rows)
-- errors not a tenant schema
SELECT citus_schema_undistribute('tenant1');
ERROR: schema tenant1 is not distributed
-- make it a tenant schema
BEGIN;
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
COMMIT;
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$);
result
---------------------------------------------------------------------
18
18
18
(3 rows)
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
-- already a tenant schema notice
SET client_min_messages TO NOTICE;
SELECT citus_schema_distribute('tenant1');
NOTICE: schema tenant1 is already distributed
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SET client_min_messages TO WARNING;
-- convert back to a regular schema
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a regular schema now
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
result
---------------------------------------------------------------------
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
result
---------------------------------------------------------------------
(3 rows)
-- tables still have valid data
SELECT COUNT(*) FROM tenant1.partitioned_table;
count
---------------------------------------------------------------------
20
(1 row)
SELECT COUNT(*) FROM citus_schema_distribute_undistribute.foreign_table;
count
---------------------------------------------------------------------
100
(1 row)
SELECT COUNT(*) FROM tenant1.table3;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE citus_schema_distribute_undistribute.ref CASCADE;
SELECT COUNT(*) FROM tenant1.table3;
count
---------------------------------------------------------------------
0
(1 row)
-- disallowed namespaces
SELECT citus_schema_distribute('public');
ERROR: public schema cannot be distributed
SELECT citus_schema_distribute('pg_catalog');
ERROR: pg_catalog schema cannot be distributed
SELECT citus_schema_distribute('pg_toast');
ERROR: pg_toast schema cannot be distributed
CREATE TEMP TABLE xx(id int); -- create a temp table in case we do not have any pg_temp_xx schema yet
SELECT nspname AS temp_schema_name FROM pg_namespace WHERE nspname LIKE 'pg_temp%' LIMIT 1 \gset
SELECT nspname AS temp_toast_schema_name FROM pg_namespace WHERE nspname LIKE 'pg_toast_temp%' LIMIT 1 \gset
SELECT citus_schema_distribute(:'temp_schema_name');
ERROR: temporary schema cannot be distributed
SELECT citus_schema_distribute(:'temp_toast_schema_name');
ERROR: temporary schema cannot be distributed
SELECT citus_schema_distribute('citus');
ERROR: schema citus, which is owned by an extension, cannot be distributed
CREATE SCHEMA extensionschema;
CREATE EXTENSION citext SCHEMA extensionschema;
SELECT citus_schema_distribute('extensionschema');
ERROR: schema extensionschema cannot be distributed since it is the schema of extension citext
DROP SCHEMA extensionschema CASCADE;
ALTER EXTENSION citus ADD TABLE tenant1.table1;
SELECT citus_schema_distribute('tenant1');
ERROR: schema cannot be distributed since it has table tenant1 which is owned by an extension
ALTER EXTENSION citus DROP TABLE tenant1.table1;
-- weird schema and table names
CREATE SCHEMA "CiTuS.TeeN";
CREATE TABLE "CiTuS.TeeN"."TeeNTabLE.1!?!"(id int PRIMARY KEY, name text);
INSERT INTO "CiTuS.TeeN"."TeeNTabLE.1!?!" SELECT i, 'asd'::text FROM generate_series(1,20) i;
SELECT citus_schema_distribute('"CiTuS.TeeN"');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''"CiTuS.TeeN"%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE '"CiTuS.TeeN"%' $$);
result
---------------------------------------------------------------------
1
1
1
(3 rows)
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT citus_schema_undistribute('"CiTuS.TeeN"');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a regular schema
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
result
---------------------------------------------------------------------
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
result
---------------------------------------------------------------------
(3 rows)
-- try setting the schema again after adding a distributed table into the schema. It should complain about distributed table.
CREATE TABLE tenant1.new_dist(id int);
SELECT create_distributed_table('tenant1.new_dist', 'id');
create_distributed_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
ERROR: schema already has distributed tables
HINT: Undistribute distributed tables under the schema before distributing the schema.
SELECT undistribute_table('tenant1.new_dist');
undistribute_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- try setting the schema again after adding a single shard table into the schema. It should complain about distributed table.
CREATE TABLE tenant1.single_shard_t(id int);
SELECT create_distributed_table('tenant1.single_shard_t', NULL);
create_distributed_table
---------------------------------------------------------------------
(1 row)
SELECT citus_schema_distribute('tenant1');
ERROR: schema already has distributed tables
HINT: Undistribute distributed tables under the schema before distributing the schema.
SELECT undistribute_table('tenant1.single_shard_t');
undistribute_table
---------------------------------------------------------------------
(1 row)
-- try setting the schema again. It should succeed now.
SELECT citus_schema_distribute('tenant1');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$);
result
---------------------------------------------------------------------
20
20
20
(3 rows)
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT citus_schema_undistribute('tenant1');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a regular schema now
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
result
---------------------------------------------------------------------
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
result
---------------------------------------------------------------------
(3 rows)
-- create an empty tenant schema to verify colocation id is removed successfully after we undistribute it
CREATE SCHEMA empty_tenant;
SELECT citus_schema_distribute('empty_tenant');
citus_schema_distribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a tenant schema now
SELECT colocationid AS empty_tenant_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace FROM pg_dist_colocation JOIN pg_dist_tenant_schema USING(colocationid) $$);
result
---------------------------------------------------------------------
empty_tenant
empty_tenant
empty_tenant
(3 rows)
SELECT citus_schema_undistribute('empty_tenant');
citus_schema_undistribute
---------------------------------------------------------------------
(1 row)
-- show the schema is a regular schema now
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
result
---------------------------------------------------------------------
(3 rows)
SELECT '$$' || 'SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = ' || :empty_tenant_colocid || '$$'
AS verify_empty_tenant_query \gset
SELECT result FROM run_command_on_all_nodes(:verify_empty_tenant_query);
result
---------------------------------------------------------------------
t
t
t
(3 rows)
-- cleanup
DROP SCHEMA "CiTuS.TeeN" CASCADE;
DROP SCHEMA tenant1 CASCADE;
DROP SCHEMA empty_tenant CASCADE;
DROP EXTENSION postgres_fdw CASCADE;
DROP SCHEMA citus_schema_distribute_undistribute CASCADE;
DROP USER tenantuser;
SELECT citus_remove_node('localhost', :master_port);
citus_remove_node
---------------------------------------------------------------------
(1 row)

File diff suppressed because it is too large Load Diff

View File

@ -1362,8 +1362,10 @@ SELECT * FROM multi_extension.print_extension_changes();
| function citus_internal_add_tenant_schema(oid,integer) void
| function citus_internal_delete_tenant_schema(oid) void
| function citus_internal_unregister_tenant_schema_globally(oid,text) void
| function citus_schema_distribute(regnamespace) void
| function citus_schema_undistribute(regnamespace) void
| table pg_dist_tenant_schema
(4 rows)
(6 rows)
DROP TABLE multi_extension.prev_objects, multi_extension.extension_diff;
-- show running version

View File

@ -76,28 +76,28 @@ SELECT citus_add_local_table_to_metadata('tenant_2.test_table');
ERROR: table "test_table" is already distributed
-- verify we don't allow update_distributed_table_colocation for tenant tables
SELECT update_distributed_table_colocation('tenant_2.test_table', colocate_with => 'none');
ERROR: tenant_2.test_table is not allowed for update_distributed_table_colocation because it is a tenant table
ERROR: tenant_2.test_table is not allowed for update_distributed_table_colocation because it belongs to a distributed schema
-- verify we also don't allow colocate_with a tenant table
SELECT update_distributed_table_colocation('regular_schema.test_table', colocate_with => 'tenant_2.test_table');
ERROR: tenant_2.test_table is not allowed for colocate_with because it is a tenant table
ERROR: tenant_2.test_table is not allowed for colocate_with because it belongs to a distributed schema
-- verify we don't allow undistribute_table for tenant tables
SELECT undistribute_table('tenant_2.test_table');
ERROR: tenant_2.test_table is not allowed for undistribute_table because it is a tenant table
ERROR: tenant_2.test_table is not allowed for undistribute_table because it belongs to a distributed schema
-- verify we don't allow alter_distributed_table for tenant tables
SELECT alter_distributed_table('tenant_2.test_table', colocate_with => 'none');
ERROR: tenant_2.test_table is not allowed for alter_distributed_table because it is a tenant table
ERROR: tenant_2.test_table is not allowed for alter_distributed_table because it belongs to a distributed schema
-- verify we also don't allow colocate_with a tenant table
SELECT alter_distributed_table('regular_schema.test_table', colocate_with => 'tenant_2.test_table');
ERROR: tenant_2.test_table is not allowed for colocate_with because it is a tenant table
ERROR: tenant_2.test_table is not allowed for colocate_with because it belongs to a distributed schema
-- verify we don't allow ALTER TABLE SET SCHEMA for tenant tables
ALTER TABLE tenant_2.test_table SET SCHEMA regular_schema;
ERROR: tenant_2.test_table is not allowed for ALTER TABLE SET SCHEMA because it is a tenant table
ERROR: tenant_2.test_table is not allowed for ALTER TABLE SET SCHEMA because it belongs to a distributed schema
-- verify we don't allow ALTER TABLE SET SCHEMA for tenant schemas
ALTER TABLE regular_schema.test_table SET SCHEMA tenant_2;
ERROR: tenant_2 is not allowed for ALTER TABLE SET SCHEMA because it is a distributed schema
-- the same, from tenant schema to tenant schema
ALTER TABLE tenant_2.test_table SET SCHEMA tenant_3;
ERROR: tenant_2.test_table is not allowed for ALTER TABLE SET SCHEMA because it is a tenant table
ERROR: tenant_2.test_table is not allowed for ALTER TABLE SET SCHEMA because it belongs to a distributed schema
-- (on coordinator) verify that colocation id is set for empty tenants too
SELECT colocationid > 0 FROM pg_dist_tenant_schema
WHERE schemaid::regnamespace::text IN ('tenant_1', 'tenant_3');
@ -200,7 +200,7 @@ CREATE FOREIGN TABLE tenant_4.foreign_table (
id bigint not null,
full_name text not null default ''
) SERVER fake_fdw_server OPTIONS (encoding 'utf-8', compression 'true', table_name 'foreign_table');
ERROR: cannot create a tenant table from a foreign table
ERROR: cannot create a foreign table in a distributed schema
-- verify that we don't allow creating a foreign table in a tenant schema
CREATE TEMPORARY TABLE tenant_4.temp_table (a int, b text);
ERROR: cannot create temporary relation in non-temporary schema
@ -275,13 +275,13 @@ CREATE TABLE tenant_5.tbl_1(a int, b text);
CREATE TABLE tenant_5.partitioned_table(a int, b text) PARTITION BY RANGE (a);
-- verify that we don't allow creating a partition table that is child of a partitioned table in a different tenant schema
CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
-- verify that we don't allow creating a local partition table that is child of a tenant partitioned table
CREATE TABLE regular_schema.local_child_table PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
SET citus.use_citus_managed_tables TO ON;
CREATE TABLE regular_schema.local_child_table PARTITION OF tenant_5.partitioned_table FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
RESET citus.use_citus_managed_tables;
CREATE TABLE regular_schema.local_partitioned_table(a int, b text) PARTITION BY RANGE (a);
CREATE TABLE regular_schema.citus_local_partitioned_table(a int, b text) PARTITION BY RANGE (a);
@ -300,11 +300,11 @@ SELECT create_distributed_table('regular_schema.dist_partitioned_table', 'a');
-- verify that we don't allow creating a partition table that is child of a non-tenant partitioned table
CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.local_partitioned_table FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.citus_local_partitioned_table FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
CREATE TABLE tenant_4.partitioned_table_child_2 PARTITION OF regular_schema.dist_partitioned_table FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
CREATE TABLE tenant_4.parent_attach_test(a int, b text) PARTITION BY RANGE (a);
CREATE TABLE tenant_4.child_attach_test(a int, b text);
CREATE TABLE tenant_5.parent_attach_test(a int, b text) PARTITION BY RANGE (a);
@ -341,21 +341,21 @@ SELECT create_distributed_table('regular_schema.child_attach_test_dist', 'a');
-- verify that we don't allow attaching a tenant table into a tenant partitioned table, if they are not in the same schema
ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_5.child_attach_test FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
-- verify that we don't allow attaching a non-tenant table into a tenant partitioned table
ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_local FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_citus_local FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION regular_schema.child_attach_test_dist FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
-- verify that we don't allow attaching a tenant table into a non-tenant partitioned table
ALTER TABLE regular_schema.parent_attach_test_local ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
ALTER TABLE regular_schema.parent_attach_test_citus_local ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
ALTER TABLE regular_schema.parent_attach_test_dist ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2);
ERROR: partitioning with tenant tables is not supported when the parent and the child are in different schemas
ERROR: partitioning within a distributed schema is not supported when the parent and the child are in different schemas
ALTER TABLE tenant_4.parent_attach_test ATTACH PARTITION tenant_4.child_attach_test FOR VALUES FROM (1) TO (2);
-- verify that we don't allow multi-level partitioning on tenant tables
CREATE TABLE tenant_4.multi_level_test(a int, b text) PARTITION BY RANGE (a);
@ -1330,7 +1330,7 @@ DROP ROLE test_non_super_user;
\c - - - :worker_1_port
-- test creating a tenant table from workers
CREATE TABLE tenant_3.tbl_1(a int, b text);
ERROR: cannot create a tenant table from a worker node
ERROR: cannot create tables in a distributed schema from a worker node
HINT: Connect to the coordinator node and try again.
-- test creating a tenant schema from workers
SET citus.enable_schema_based_sharding TO ON;

View File

@ -114,6 +114,8 @@ ORDER BY 1;
function citus_remote_connection_stats()
function citus_remove_node(text,integer)
function citus_run_local_command(text)
function citus_schema_distribute(regnamespace)
function citus_schema_undistribute(regnamespace)
function citus_server_id()
function citus_set_coordinator_host(text,integer,noderole,name)
function citus_set_default_rebalance_strategy(text)
@ -334,5 +336,5 @@ ORDER BY 1;
view citus_stat_tenants_local
view pg_dist_shard_placement
view time_partitions
(326 rows)
(328 rows)

View File

@ -77,6 +77,7 @@ test: isolation_global_pid
test: isolation_citus_locks
test: isolation_reference_table
test: isolation_schema_based_sharding
test: isolation_citus_schema_distribute_undistribute
# Rebalancer
test: isolation_blocking_move_single_shard_commands

View File

@ -36,6 +36,7 @@ test: create_single_shard_table
# don't parallelize single_shard_table_udfs to make sure colocation ids are sequential
test: single_shard_table_udfs
test: schema_based_sharding
test: citus_schema_distribute_undistribute
test: multi_test_catalog_views
test: multi_table_ddl

View File

@ -0,0 +1,171 @@
setup
{
SELECT citus_set_coordinator_host('localhost', 57636);
CREATE SCHEMA tenant1;
CREATE TABLE tenant1.table1(id int PRIMARY KEY, name text, col bigint);
INSERT INTO tenant1.table1 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i;
CREATE TABLE tenant1.table2(id int PRIMARY KEY, name text, col bigint);
CREATE TABLE public.ref(id int PRIMARY KEY);
SELECT create_reference_table('public.ref');
CREATE TABLE tenant1.table3(id int PRIMARY KEY, name text, col1 int, col int REFERENCES public.ref(id));
SELECT citus_add_local_table_to_metadata('tenant1.table3');
}
teardown
{
DROP TABLE public.ref CASCADE;
DROP SCHEMA IF EXISTS tenant1, tenant2 CASCADE;
SELECT citus_remove_node('localhost', 57636);
}
session "s1"
step "s1-begin"
{
BEGIN;
}
step "s1-schema-distribute"
{
SELECT citus_schema_distribute('tenant1');
}
step "s1-schema-undistribute"
{
SELECT citus_schema_undistribute('tenant1');
}
step "s1-verify-distributed-schema"
{
SELECT logicalrelid, partmethod, partkey, (colocationid = (SELECT colocationid AS tenant_colocid FROM pg_dist_tenant_schema)) AS is_correct_colocid, repmodel, autoconverted FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant%' ORDER BY logicalrelid;
}
step "s1-commit"
{
COMMIT;
}
session "s2"
step "s2-drop-schema"
{
DROP SCHEMA tenant1 CASCADE;
}
step "s2-rename-schema"
{
ALTER SCHEMA tenant1 RENAME TO tenant2;
}
step "s2-add-table"
{
CREATE TABLE tenant1.table4(id int PRIMARY KEY, name text, col bigint);
}
step "s2-drop-table"
{
DROP TABLE tenant1.table3;
}
step "s2-alter-col-type"
{
ALTER TABLE tenant1.table3 ALTER COLUMN col1 TYPE text;
}
step "s2-add-foreign-key"
{
ALTER TABLE tenant1.table3 ADD CONSTRAINT table3_fk1 FOREIGN KEY (id) REFERENCES tenant1.table2 (id);
}
step "s2-drop-foreign-key"
{
ALTER TABLE tenant1.table3 DROP CONSTRAINT table3_col_fkey;
}
step "s2-create-unique-index"
{
CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col);
}
step "s2-create-unique-index-concurrently"
{
CREATE UNIQUE INDEX CONCURRENTLY idx_3 ON tenant1.table3 (col);
}
step "s2-reindex-unique-concurrently"
{
REINDEX INDEX CONCURRENTLY tenant1.idx_2;
}
step "s2-insert"
{
// we insert into public.ref table as well to prevent fkey violation
INSERT INTO public.ref SELECT i FROM generate_series(11, 20) i;
INSERT INTO tenant1.table3 SELECT i, 'asd', i*1000 FROM generate_series(11, 20) i;
}
step "s2-delete"
{
DELETE FROM tenant1.table3;
}
step "s2-update"
{
UPDATE tenant1.table3 SET col = 11 WHERE col = 11;
}
// DROP SCHEMA
permutation "s1-begin" "s1-schema-distribute" "s2-drop-schema" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-drop-schema" "s1-commit" "s1-verify-distributed-schema"
// RENAME SCHEMA
permutation "s1-begin" "s1-schema-distribute" "s2-rename-schema" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-rename-schema" "s1-commit" "s1-verify-distributed-schema"
// CREATE TABLE
permutation "s1-begin" "s1-schema-distribute" "s2-add-table" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-add-table" "s1-commit" "s1-verify-distributed-schema"
// DROP TABLE
permutation "s1-begin" "s1-schema-distribute" "s2-drop-table" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-drop-table" "s1-commit" "s1-verify-distributed-schema"
// ALTER TABLE ALTER COLUMN TYPE
permutation "s1-begin" "s1-schema-distribute" "s2-alter-col-type" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-alter-col-type" "s1-commit" "s1-verify-distributed-schema"
// ADD FOREIGN KEY
permutation "s1-begin" "s1-schema-distribute" "s2-add-foreign-key" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-add-foreign-key" "s1-commit" "s1-verify-distributed-schema"
// DROP FOREIGN KEY
permutation "s1-begin" "s1-schema-distribute" "s2-drop-foreign-key" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-drop-foreign-key" "s1-commit" "s1-verify-distributed-schema"
// CREATE UNIQUE INDEX
permutation "s1-begin" "s1-schema-distribute" "s2-create-unique-index" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-create-unique-index" "s1-commit" "s1-verify-distributed-schema"
// CREATE UNIQUE INDEX CONCURRENTLY
permutation "s1-begin" "s1-schema-distribute" "s2-create-unique-index-concurrently" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-create-unique-index-concurrently" "s1-commit" "s1-verify-distributed-schema"
// REINDEX CONCURRENTLY
permutation "s2-create-unique-index" "s1-begin" "s1-schema-distribute" "s2-reindex-unique-concurrently" "s1-commit" "s1-verify-distributed-schema"
permutation "s2-create-unique-index" "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-reindex-unique-concurrently" "s1-commit" "s1-verify-distributed-schema"
// INSERT
permutation "s1-begin" "s1-schema-distribute" "s2-insert" "s1-commit" "s1-verify-distributed-schema"
permutation "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-insert" "s1-commit" "s1-verify-distributed-schema"
// UPDATE
permutation "s2-insert" "s1-begin" "s1-schema-distribute" "s2-update" "s1-commit" "s1-verify-distributed-schema"
permutation "s2-insert" "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-update" "s1-commit" "s1-verify-distributed-schema"
// DELETE
permutation "s2-insert" "s1-begin" "s1-schema-distribute" "s2-delete" "s1-commit" "s1-verify-distributed-schema"
permutation "s2-insert" "s1-schema-distribute" "s1-begin" "s1-schema-undistribute" "s2-delete" "s1-commit" "s1-verify-distributed-schema"

View File

@ -0,0 +1,437 @@
SET citus.next_shard_id TO 1730000;
SET citus.shard_replication_factor TO 1;
SET client_min_messages TO WARNING;
SET citus.enable_schema_based_sharding TO off;
SELECT 1 FROM citus_add_node('localhost', :master_port, groupid => 0);
CREATE USER tenantuser superuser;
SET role tenantuser;
-- check invalid input
SELECT citus_schema_distribute(1);
SELECT citus_schema_undistribute(1);
-- noop
SELECT citus_schema_distribute(null);
SELECT citus_schema_undistribute(null);
-- public and some others cannot be distributed as a tenant schema, but check what happens
-- if we try to call citus_schema_undistribute() for such a schema.
SELECT citus_schema_undistribute('public');
-- create non-tenant schema
CREATE SCHEMA citus_schema_distribute_undistribute;
-- create tenant schema
CREATE SCHEMA tenant1;
CREATE TABLE tenant1.table1(id int PRIMARY KEY, name text);
INSERT INTO tenant1.table1 SELECT i, 'asd'::text FROM generate_series(1,20) i;
CREATE TABLE tenant1.table2(id int REFERENCES tenant1.table1(id), num bigint UNIQUE);
INSERT INTO tenant1.table2 SELECT i, i FROM generate_series(1,20) i;
CREATE TABLE citus_schema_distribute_undistribute.ref(id int PRIMARY KEY);
SELECT create_reference_table('citus_schema_distribute_undistribute.ref');
INSERT INTO citus_schema_distribute_undistribute.ref SELECT i FROM generate_series(1,100) i;
-- autoconverted to Citus local table due to foreign key to reference table
CREATE TABLE tenant1.table3(id int REFERENCES citus_schema_distribute_undistribute.ref(id));
INSERT INTO tenant1.table3 SELECT i FROM generate_series(1,100) i;
-- Citus local table with autoconverted=false
CREATE TABLE tenant1.table3x(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.ref(id));
SELECT citus_add_local_table_to_metadata('tenant1.table3x');
INSERT INTO tenant1.table3x SELECT i FROM generate_series(1,100) i;
-- foreign key to another local table in the same schema
CREATE TABLE tenant1.table3y(id int PRIMARY KEY REFERENCES tenant1.table3x(id));
SELECT citus_add_local_table_to_metadata('tenant1.table3y');
INSERT INTO tenant1.table3y SELECT i FROM generate_series(1,100) i;
-- table with composite type
CREATE TYPE tenant1.catname AS ENUM ('baby', 'teen', 'mid');
CREATE TYPE tenant1.agecat AS (below_age int, name tenant1.catname);
CREATE TABLE tenant1.table4(id int, age tenant1.agecat);
-- create autoconverted partitioned table
CREATE TABLE tenant1.partitioned_table(id int REFERENCES citus_schema_distribute_undistribute.ref(id)) PARTITION BY RANGE(id);
CREATE TABLE tenant1.partition1 PARTITION OF tenant1.partitioned_table FOR VALUES FROM (1) TO (11);
CREATE TABLE tenant1.partition2 PARTITION OF tenant1.partitioned_table FOR VALUES FROM (11) TO (21);
INSERT INTO tenant1.partitioned_table SELECT i FROM generate_series(1,20) i;
-- create view
CREATE VIEW tenant1.view1 AS SELECT * FROM tenant1.table1 JOIN tenant1.table2 USING(id);
-- create view in regular schema
CREATE VIEW citus_schema_distribute_undistribute.view2 AS SELECT * FROM tenant1.view1;
-- create materialized view
CREATE MATERIALIZED VIEW tenant1.view2 AS SELECT * FROM tenant1.table1;
-- create collation
CREATE COLLATION citus_schema_distribute_undistribute.german_phonebook (provider = icu, locale = 'de-u-co-phonebk');
-- create type
CREATE TYPE citus_schema_distribute_undistribute.pair_type AS (a int, b int);
-- Create function
CREATE FUNCTION citus_schema_distribute_undistribute.one_as_result() RETURNS INT LANGUAGE SQL AS
$$
SELECT 1;
$$;
-- create text search dictionary
CREATE TEXT SEARCH DICTIONARY citus_schema_distribute_undistribute.my_german_dict (
template = snowball,
language = german,
stopwords = german
);
-- create text search config
CREATE TEXT SEARCH CONFIGURATION citus_schema_distribute_undistribute.my_ts_config ( parser = default );
ALTER TEXT SEARCH CONFIGURATION citus_schema_distribute_undistribute.my_ts_config ALTER MAPPING FOR asciiword WITH citus_schema_distribute_undistribute.my_german_dict;
-- create sequence
CREATE SEQUENCE citus_schema_distribute_undistribute.seq;
-- create complex table
CREATE TABLE tenant1.complextable (id int PRIMARY KEY default nextval('citus_schema_distribute_undistribute.seq'), col int default (citus_schema_distribute_undistribute.one_as_result()), myserial serial, phone text COLLATE citus_schema_distribute_undistribute.german_phonebook, initials citus_schema_distribute_undistribute.pair_type);
CREATE SEQUENCE tenant1.seq_owned OWNED BY tenant1.complextable.id;
-- not allowed from workers
SELECT run_command_on_workers($$SELECT citus_schema_distribute('tenant1');$$);
SELECT run_command_on_workers($$SELECT citus_schema_undistribute('tenant1');$$);
-- inherited table not allowed
CREATE TABLE citus_schema_distribute_undistribute.cities (
name text,
population real,
elevation int
);
CREATE TABLE citus_schema_distribute_undistribute.capitals (
state char(2) UNIQUE NOT NULL
) INHERITS (citus_schema_distribute_undistribute.cities);
-- temporarily move "cities" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a table that is inherited
ALTER TABLE citus_schema_distribute_undistribute.cities SET SCHEMA tenant1;
SELECT citus_schema_distribute('tenant1');
ALTER TABLE tenant1.cities SET SCHEMA citus_schema_distribute_undistribute;
-- temporarily move "capitals" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a table that inherits
ALTER TABLE citus_schema_distribute_undistribute.capitals SET SCHEMA tenant1;
SELECT citus_schema_distribute('tenant1');
ALTER TABLE tenant1.capitals SET SCHEMA citus_schema_distribute_undistribute;
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
CREATE TABLE citus_schema_distribute_undistribute.illegal_partitioned_table(id int) PARTITION BY RANGE(id);
CREATE TABLE citus_schema_distribute_undistribute.illegal_partition1 PARTITION OF citus_schema_distribute_undistribute.illegal_partitioned_table FOR VALUES FROM (1) TO (11);
-- temporarily move "illegal_partitioned_table" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a partition table whose parent is created in another schema
ALTER TABLE citus_schema_distribute_undistribute.illegal_partitioned_table SET SCHEMA tenant1;
SELECT citus_schema_distribute('tenant1');
ALTER TABLE tenant1.illegal_partitioned_table SET SCHEMA citus_schema_distribute_undistribute;
-- temporarily move "illegal_partition1" into tenant1 (not a tenant schema yet) to test citus_schema_distribute() with a parent table whose partition is created in another schema
ALTER TABLE citus_schema_distribute_undistribute.illegal_partition1 SET SCHEMA tenant1;
SELECT citus_schema_distribute('tenant1');
ALTER TABLE tenant1.illegal_partition1 SET SCHEMA citus_schema_distribute_undistribute;
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
-- foreign key to a local table in another schema is not allowed
CREATE TABLE citus_schema_distribute_undistribute.tbl1(id int PRIMARY KEY);
CREATE TABLE tenant1.table3z(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.tbl1(id));
SELECT citus_schema_distribute('tenant1');
SELECT create_reference_table('citus_schema_distribute_undistribute.tbl1');
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
-- foreign key to a distributed table in another schema is not allowed
CREATE TABLE citus_schema_distribute_undistribute.tbl2(id int PRIMARY KEY);
SELECT create_distributed_table('citus_schema_distribute_undistribute.tbl2','id');
CREATE TABLE tenant1.table3w(id int PRIMARY KEY REFERENCES citus_schema_distribute_undistribute.tbl2(id));
SELECT citus_schema_distribute('tenant1');
DROP TABLE citus_schema_distribute_undistribute.tbl2 CASCADE;
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
-- foreign key from a local table in another schema is not allowed
CREATE TABLE tenant1.table3q(id int PRIMARY KEY);
CREATE TABLE citus_schema_distribute_undistribute.tbl3(id int PRIMARY KEY REFERENCES tenant1.table3q(id));
SELECT citus_schema_distribute('tenant1');
DROP TABLE citus_schema_distribute_undistribute.tbl3 CASCADE;
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
-- foreign key from a reference table in another schema is not allowed
CREATE TABLE tenant1.table3t(id int PRIMARY KEY);
CREATE TABLE citus_schema_distribute_undistribute.tbl4(id int PRIMARY KEY REFERENCES tenant1.table3t(id));
SELECT create_reference_table('citus_schema_distribute_undistribute.tbl4');
SELECT citus_schema_distribute('tenant1');
DROP TABLE citus_schema_distribute_undistribute.tbl4 CASCADE;
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
-- only allowed for schema owner or superuser
CREATE USER dummyregular;
SET role dummyregular;
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
-- assign all tables to dummyregular except table5
SET role tenantuser;
SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY tenantuser TO dummyregular; $$);
CREATE TABLE tenant1.table5(id int);
-- table owner check fails the distribution
SET role dummyregular;
SELECT citus_schema_distribute('tenant1');
-- alter table owner, then redistribute
SET role tenantuser;
ALTER TABLE tenant1.table5 OWNER TO dummyregular;
SET role dummyregular;
SELECT citus_schema_distribute('tenant1');
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
-- below query verifies the same colocationid in pg_dist_tenant_schema, pg_dist_colocation and all entries in pg_dist_partition at the same time
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$);
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
SELECT citus_schema_undistribute('tenant1');
-- show the schema is a regular schema
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
-- below query verifies the tenant colocationid is removed from both pg_dist_colocation and all entries in pg_dist_partition at the same time
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
RESET role;
SELECT result FROM run_command_on_all_nodes($$ REASSIGN OWNED BY dummyregular TO tenantuser; $$);
DROP USER dummyregular;
CREATE USER dummysuper superuser;
SET role dummysuper;
SELECT citus_schema_distribute('tenant1');
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$);
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
SELECT citus_schema_undistribute('tenant1');
-- show the schema is a regular schema
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
RESET role;
DROP USER dummysuper;
-- foreign table
CREATE TABLE tenant1.foreign_table_test (id integer NOT NULL, data text, a bigserial);
INSERT INTO tenant1.foreign_table_test SELECT i FROM generate_series(1,100) i;
CREATE EXTENSION postgres_fdw;
CREATE SERVER foreign_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port :'master_port', dbname 'regression');
CREATE USER MAPPING FOR CURRENT_USER
SERVER foreign_server
OPTIONS (user 'postgres');
CREATE FOREIGN TABLE tenant1.foreign_table (
id integer NOT NULL,
data text,
a bigserial
)
SERVER foreign_server
OPTIONS (schema_name 'tenant1', table_name 'foreign_table_test');
-- foreign table not allowed
SELECT citus_schema_distribute('tenant1');
ALTER FOREIGN TABLE tenant1.foreign_table SET SCHEMA citus_schema_distribute_undistribute;
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
-- already have distributed table error
CREATE TABLE tenant1.dist(id int);
SELECT create_distributed_table('tenant1.dist', 'id');
SELECT citus_schema_distribute('tenant1');
SELECT undistribute_table('tenant1.dist');
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
CREATE TABLE tenant1.ref2(id int);
SELECT create_reference_table('tenant1.ref2');
SELECT citus_schema_distribute('tenant1');
SELECT undistribute_table('tenant1.ref2');
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
BEGIN;
SELECT citus_schema_distribute('tenant1');
ROLLBACK;
-- show the schema is a regular schema
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
-- errors not a tenant schema
SELECT citus_schema_undistribute('tenant1');
-- make it a tenant schema
BEGIN;
SELECT citus_schema_distribute('tenant1');
COMMIT;
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$);
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
-- already a tenant schema notice
SET client_min_messages TO NOTICE;
SELECT citus_schema_distribute('tenant1');
SET client_min_messages TO WARNING;
-- convert back to a regular schema
SELECT citus_schema_undistribute('tenant1');
-- show the schema is a regular schema now
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
-- tables still have valid data
SELECT COUNT(*) FROM tenant1.partitioned_table;
SELECT COUNT(*) FROM citus_schema_distribute_undistribute.foreign_table;
SELECT COUNT(*) FROM tenant1.table3;
TRUNCATE citus_schema_distribute_undistribute.ref CASCADE;
SELECT COUNT(*) FROM tenant1.table3;
-- disallowed namespaces
SELECT citus_schema_distribute('public');
SELECT citus_schema_distribute('pg_catalog');
SELECT citus_schema_distribute('pg_toast');
CREATE TEMP TABLE xx(id int); -- create a temp table in case we do not have any pg_temp_xx schema yet
SELECT nspname AS temp_schema_name FROM pg_namespace WHERE nspname LIKE 'pg_temp%' LIMIT 1 \gset
SELECT nspname AS temp_toast_schema_name FROM pg_namespace WHERE nspname LIKE 'pg_toast_temp%' LIMIT 1 \gset
SELECT citus_schema_distribute(:'temp_schema_name');
SELECT citus_schema_distribute(:'temp_toast_schema_name');
SELECT citus_schema_distribute('citus');
CREATE SCHEMA extensionschema;
CREATE EXTENSION citext SCHEMA extensionschema;
SELECT citus_schema_distribute('extensionschema');
DROP SCHEMA extensionschema CASCADE;
ALTER EXTENSION citus ADD TABLE tenant1.table1;
SELECT citus_schema_distribute('tenant1');
ALTER EXTENSION citus DROP TABLE tenant1.table1;
-- weird schema and table names
CREATE SCHEMA "CiTuS.TeeN";
CREATE TABLE "CiTuS.TeeN"."TeeNTabLE.1!?!"(id int PRIMARY KEY, name text);
INSERT INTO "CiTuS.TeeN"."TeeNTabLE.1!?!" SELECT i, 'asd'::text FROM generate_series(1,20) i;
SELECT citus_schema_distribute('"CiTuS.TeeN"');
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''"CiTuS.TeeN"%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE '"CiTuS.TeeN"%' $$);
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
SELECT citus_schema_undistribute('"CiTuS.TeeN"');
-- show the schema is a regular schema
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
-- try setting the schema again after adding a distributed table into the schema. It should complain about distributed table.
CREATE TABLE tenant1.new_dist(id int);
SELECT create_distributed_table('tenant1.new_dist', 'id');
SELECT citus_schema_distribute('tenant1');
SELECT undistribute_table('tenant1.new_dist');
SELECT citus_schema_distribute('tenant1');
SELECT citus_schema_undistribute('tenant1');
-- try setting the schema again after adding a single shard table into the schema. It should complain about distributed table.
CREATE TABLE tenant1.single_shard_t(id int);
SELECT create_distributed_table('tenant1.single_shard_t', NULL);
SELECT citus_schema_distribute('tenant1');
SELECT undistribute_table('tenant1.single_shard_t');
-- try setting the schema again. It should succeed now.
SELECT citus_schema_distribute('tenant1');
-- show the schema is a tenant schema now
SELECT colocationid AS tenant1_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT '$$' ||
' SELECT colocationid = ' || :tenant1_colocid ||
' FROM pg_dist_tenant_schema JOIN pg_dist_colocation USING(colocationid)' ||
' WHERE colocationid = ALL(SELECT colocationid FROM pg_dist_partition WHERE logicalrelid::text LIKE ''tenant1%'')' ||
'$$'
AS verify_tenant_query \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*) AS total FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1%' $$);
SELECT result FROM run_command_on_all_nodes(:verify_tenant_query);
SELECT citus_schema_undistribute('tenant1');
-- show the schema is a regular schema now
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT COUNT(*)=0 FROM pg_dist_colocation FULL JOIN pg_dist_partition USING(colocationid) WHERE (logicalrelid::text LIKE 'tenant1.%' OR logicalrelid is NULL) AND colocationid > 0 $$);
SELECT result FROM run_command_on_all_nodes($$ SELECT array_agg(logicalrelid ORDER BY logicalrelid) FROM pg_dist_partition WHERE logicalrelid::text LIKE 'tenant1.%' AND colocationid > 0 $$);
-- create an empty tenant schema to verify colocation id is removed successfully after we undistribute it
CREATE SCHEMA empty_tenant;
SELECT citus_schema_distribute('empty_tenant');
-- show the schema is a tenant schema now
SELECT colocationid AS empty_tenant_colocid FROM pg_dist_tenant_schema schemaid \gset
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace FROM pg_dist_colocation JOIN pg_dist_tenant_schema USING(colocationid) $$);
SELECT citus_schema_undistribute('empty_tenant');
-- show the schema is a regular schema now
SELECT result FROM run_command_on_all_nodes($$ SELECT schemaid::regnamespace as schemaname FROM pg_dist_tenant_schema $$);
SELECT '$$' || 'SELECT COUNT(*)=0 FROM pg_dist_colocation WHERE colocationid = ' || :empty_tenant_colocid || '$$'
AS verify_empty_tenant_query \gset
SELECT result FROM run_command_on_all_nodes(:verify_empty_tenant_query);
-- cleanup
DROP SCHEMA "CiTuS.TeeN" CASCADE;
DROP SCHEMA tenant1 CASCADE;
DROP SCHEMA empty_tenant CASCADE;
DROP EXTENSION postgres_fdw CASCADE;
DROP SCHEMA citus_schema_distribute_undistribute CASCADE;
DROP USER tenantuser;
SELECT citus_remove_node('localhost', :master_port);