From 213d363bc3c3a3544aea366c385c1ef4c49140dd Mon Sep 17 00:00:00 2001 From: aykut-bozkurt <51649454+aykut-bozkurt@users.noreply.github.com> Date: Mon, 12 Jun 2023 18:41:31 +0300 Subject: [PATCH] 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 --- .../distributed/commands/alter_table.c | 31 + .../commands/create_distributed_table.c | 10 +- .../commands/schema_based_sharding.c | 579 ++++++++- src/backend/distributed/commands/table.c | 14 +- src/backend/distributed/metadata/dependency.c | 41 + .../distributed/metadata/metadata_utility.c | 15 + .../distributed/sql/citus--11.3-1--12.0-1.sql | 4 + .../sql/downgrades/citus--12.0-1--11.3-1.sql | 3 + .../udfs/citus_schema_distribute/12.0-1.sql | 6 + .../udfs/citus_schema_distribute/latest.sql | 6 + .../udfs/citus_schema_undistribute/12.0-1.sql | 6 + .../udfs/citus_schema_undistribute/latest.sql | 6 + src/include/distributed/commands.h | 6 + src/include/distributed/metadata/distobject.h | 1 + src/include/distributed/metadata_utility.h | 2 + .../citus_schema_distribute_undistribute.out | 923 ++++++++++++++ ...n_citus_schema_distribute_undistribute.out | 1136 +++++++++++++++++ src/test/regress/expected/multi_extension.out | 4 +- .../expected/schema_based_sharding.out | 44 +- .../expected/upgrade_list_citus_objects.out | 4 +- src/test/regress/isolation_schedule | 1 + src/test/regress/multi_1_schedule | 1 + ..._citus_schema_distribute_undistribute.spec | 171 +++ .../citus_schema_distribute_undistribute.sql | 437 +++++++ 24 files changed, 3375 insertions(+), 76 deletions(-) create mode 100644 src/backend/distributed/sql/udfs/citus_schema_distribute/12.0-1.sql create mode 100644 src/backend/distributed/sql/udfs/citus_schema_distribute/latest.sql create mode 100644 src/backend/distributed/sql/udfs/citus_schema_undistribute/12.0-1.sql create mode 100644 src/backend/distributed/sql/udfs/citus_schema_undistribute/latest.sql create mode 100644 src/test/regress/expected/citus_schema_distribute_undistribute.out create mode 100644 src/test/regress/expected/isolation_citus_schema_distribute_undistribute.out create mode 100644 src/test/regress/spec/isolation_citus_schema_distribute_undistribute.spec create mode 100644 src/test/regress/sql/citus_schema_distribute_undistribute.sql diff --git a/src/backend/distributed/commands/alter_table.c b/src/backend/distributed/commands/alter_table.c index 40324cfcb..86dbe48e9 100644 --- a/src/backend/distributed/commands/alter_table.c +++ b/src/backend/distributed/commands/alter_table.c @@ -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. diff --git a/src/backend/distributed/commands/create_distributed_table.c b/src/backend/distributed/commands/create_distributed_table.c index 71fe0b9e3..a1ae2d9ea 100644 --- a/src/backend/distributed/commands/create_distributed_table.c +++ b/src/backend/distributed/commands/create_distributed_table.c @@ -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 | diff --git a/src/backend/distributed/commands/schema_based_sharding.c b/src/backend/distributed/commands/schema_based_sharding.c index 77daaa629..91bae0943 100644 --- a/src/backend/distributed/commands/schema_based_sharding.c +++ b/src/backend/distributed/commands/schema_based_sharding.c @@ -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 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))); } diff --git a/src/backend/distributed/commands/table.c b/src/backend/distributed/commands/table.c index df0aa757b..aa4570f5e 100644 --- a/src/backend/distributed/commands/table.c +++ b/src/backend/distributed/commands/table.c @@ -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)) { diff --git a/src/backend/distributed/metadata/dependency.c b/src/backend/distributed/metadata/dependency.c index a58e57be7..adc3fc1ab 100644 --- a/src/backend/distributed/metadata/dependency.c +++ b/src/backend/distributed/metadata/dependency.c @@ -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 [WITH] 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. diff --git a/src/backend/distributed/metadata/metadata_utility.c b/src/backend/distributed/metadata/metadata_utility.c index 00aceaf04..7585769ad 100644 --- a/src/backend/distributed/metadata/metadata_utility.c +++ b/src/backend/distributed/metadata/metadata_utility.c @@ -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 diff --git a/src/backend/distributed/sql/citus--11.3-1--12.0-1.sql b/src/backend/distributed/sql/citus--11.3-1--12.0-1.sql index d241fd235..6125f0d10 100644 --- a/src/backend/distributed/sql/citus--11.3-1--12.0-1.sql +++ b/src/backend/distributed/sql/citus--11.3-1--12.0-1.sql @@ -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" diff --git a/src/backend/distributed/sql/downgrades/citus--12.0-1--11.3-1.sql b/src/backend/distributed/sql/downgrades/citus--12.0-1--11.3-1.sql index 1cddc488c..ef668c97f 100644 --- a/src/backend/distributed/sql/downgrades/citus--12.0-1--11.3-1.sql +++ b/src/backend/distributed/sql/downgrades/citus--12.0-1--11.3-1.sql @@ -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" diff --git a/src/backend/distributed/sql/udfs/citus_schema_distribute/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_schema_distribute/12.0-1.sql new file mode 100644 index 000000000..7ca91fbbd --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schema_distribute/12.0-1.sql @@ -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'; diff --git a/src/backend/distributed/sql/udfs/citus_schema_distribute/latest.sql b/src/backend/distributed/sql/udfs/citus_schema_distribute/latest.sql new file mode 100644 index 000000000..7ca91fbbd --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schema_distribute/latest.sql @@ -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'; diff --git a/src/backend/distributed/sql/udfs/citus_schema_undistribute/12.0-1.sql b/src/backend/distributed/sql/udfs/citus_schema_undistribute/12.0-1.sql new file mode 100644 index 000000000..881dd3f5a --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schema_undistribute/12.0-1.sql @@ -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'; diff --git a/src/backend/distributed/sql/udfs/citus_schema_undistribute/latest.sql b/src/backend/distributed/sql/udfs/citus_schema_undistribute/latest.sql new file mode 100644 index 000000000..881dd3f5a --- /dev/null +++ b/src/backend/distributed/sql/udfs/citus_schema_undistribute/latest.sql @@ -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'; diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index 542e85326..9a1c22c64 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -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 */ diff --git a/src/include/distributed/metadata/distobject.h b/src/include/distributed/metadata/distobject.h index 91f4fb630..de56c0e1f 100644 --- a/src/include/distributed/metadata/distobject.h +++ b/src/include/distributed/metadata/distobject.h @@ -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); diff --git a/src/include/distributed/metadata_utility.h b/src/include/distributed/metadata_utility.h index 70fa32324..abed55e48 100644 --- a/src/include/distributed/metadata_utility.h +++ b/src/include/distributed/metadata_utility.h @@ -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); diff --git a/src/test/regress/expected/citus_schema_distribute_undistribute.out b/src/test/regress/expected/citus_schema_distribute_undistribute.out new file mode 100644 index 000000000..ffdc20154 --- /dev/null +++ b/src/test/regress/expected/citus_schema_distribute_undistribute.out @@ -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) + diff --git a/src/test/regress/expected/isolation_citus_schema_distribute_undistribute.out b/src/test/regress/expected/isolation_citus_schema_distribute_undistribute.out new file mode 100644 index 000000000..4fd00cc00 --- /dev/null +++ b/src/test/regress/expected/isolation_citus_schema_distribute_undistribute.out @@ -0,0 +1,1136 @@ +Parsed test spec with 2 sessions + +starting permutation: s1-begin s1-schema-distribute s2-drop-schema s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-schema: + DROP SCHEMA tenant1 CASCADE; + +step s1-commit: + COMMIT; + +step s2-drop-schema: <... completed> +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; + +logicalrelid|partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +(0 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-drop-schema s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-schema: + DROP SCHEMA tenant1 CASCADE; + +step s1-commit: + COMMIT; + +step s2-drop-schema: <... completed> +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; + +logicalrelid|partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +(0 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-rename-schema s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-rename-schema: + ALTER SCHEMA tenant1 RENAME TO tenant2; + +step s1-commit: + COMMIT; + +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant2.table1|n | |t |s |f +tenant2.table2|n | |t |s |f +tenant2.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-rename-schema s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-rename-schema: + ALTER SCHEMA tenant1 RENAME TO tenant2; + +step s1-commit: + COMMIT; + +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant2.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-add-table s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-add-table: + CREATE TABLE tenant1.table4(id int PRIMARY KEY, name text, col bigint); + +step s1-commit: + COMMIT; + +step s2-add-table: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +tenant1.table4|n | |t |s |f +(4 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-add-table s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-add-table: + CREATE TABLE tenant1.table4(id int PRIMARY KEY, name text, col bigint); + +step s1-commit: + COMMIT; + +step s2-add-table: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-drop-table s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-table: + DROP TABLE tenant1.table3; + +step s1-commit: + COMMIT; + +step s2-drop-table: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +(2 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-drop-table s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-table: + DROP TABLE tenant1.table3; + +step s1-commit: + COMMIT; + +step s2-drop-table: <... completed> +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; + +logicalrelid|partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +(0 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-alter-col-type s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-alter-col-type: + ALTER TABLE tenant1.table3 ALTER COLUMN col1 TYPE text; + +step s1-commit: + COMMIT; + +step s2-alter-col-type: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-alter-col-type s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-alter-col-type: + ALTER TABLE tenant1.table3 ALTER COLUMN col1 TYPE text; + +step s1-commit: + COMMIT; + +step s2-alter-col-type: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-add-foreign-key s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-add-foreign-key: + ALTER TABLE tenant1.table3 ADD CONSTRAINT table3_fk1 FOREIGN KEY (id) REFERENCES tenant1.table2 (id); + +step s1-commit: + COMMIT; + +step s2-add-foreign-key: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-add-foreign-key s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-add-foreign-key: + ALTER TABLE tenant1.table3 ADD CONSTRAINT table3_fk1 FOREIGN KEY (id) REFERENCES tenant1.table2 (id); + +step s1-commit: + COMMIT; + +step s2-add-foreign-key: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +tenant1.table2|n | | |s |t +(2 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-drop-foreign-key s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-foreign-key: + ALTER TABLE tenant1.table3 DROP CONSTRAINT table3_col_fkey; + +step s1-commit: + COMMIT; + +step s2-drop-foreign-key: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-drop-foreign-key s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-drop-foreign-key: + ALTER TABLE tenant1.table3 DROP CONSTRAINT table3_col_fkey; + +step s1-commit: + COMMIT; + +step s2-drop-foreign-key: <... completed> +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; + +logicalrelid|partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +(0 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-create-unique-index s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index: + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); + +step s1-commit: + COMMIT; + +step s2-create-unique-index: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-create-unique-index s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index: + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); + +step s1-commit: + COMMIT; + +step s2-create-unique-index: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-create-unique-index-concurrently s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index-concurrently: + CREATE UNIQUE INDEX CONCURRENTLY idx_3 ON tenant1.table3 (col); + +step s1-commit: + COMMIT; + +step s2-create-unique-index-concurrently: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-create-unique-index-concurrently s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index-concurrently: + CREATE UNIQUE INDEX CONCURRENTLY idx_3 ON tenant1.table3 (col); + +step s1-commit: + COMMIT; + +step s2-create-unique-index-concurrently: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-create-unique-index s1-begin s1-schema-distribute s2-reindex-unique-concurrently s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index: + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-reindex-unique-concurrently: + REINDEX INDEX CONCURRENTLY tenant1.idx_2; + +step s1-commit: + COMMIT; + +step s2-reindex-unique-concurrently: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-create-unique-index s1-schema-distribute s1-begin s1-schema-undistribute s2-reindex-unique-concurrently s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-create-unique-index: + CREATE UNIQUE INDEX idx_2 ON tenant1.table3 (col); + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-reindex-unique-concurrently: + REINDEX INDEX CONCURRENTLY tenant1.idx_2; + +step s1-commit: + COMMIT; + +step s2-reindex-unique-concurrently: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-begin s1-schema-distribute s2-insert s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + 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 s1-commit: + COMMIT; + +step s2-insert: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s1-schema-distribute s1-begin s1-schema-undistribute s2-insert s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + 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 s1-commit: + COMMIT; + +step s2-insert: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-insert s1-begin s1-schema-distribute s2-update s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + 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 s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-update: + UPDATE tenant1.table3 SET col = 11 WHERE col = 11; + +step s1-commit: + COMMIT; + +step s2-update: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-insert s1-schema-distribute s1-begin s1-schema-undistribute s2-update s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + 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 s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-update: + UPDATE tenant1.table3 SET col = 11 WHERE col = 11; + +step s1-commit: + COMMIT; + +step s2-update: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-insert s1-begin s1-schema-distribute s2-delete s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + 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 s1-begin: + BEGIN; + +step s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s2-delete: + DELETE FROM tenant1.table3; + +step s1-commit: + COMMIT; + +step s2-delete: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table1|n | |t |s |f +tenant1.table2|n | |t |s |f +tenant1.table3|n | |t |s |f +(3 rows) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + + +starting permutation: s2-insert s1-schema-distribute s1-begin s1-schema-undistribute s2-delete s1-commit s1-verify-distributed-schema +citus_add_local_table_to_metadata +--------------------------------------------------------------------- + +(1 row) + +step s2-insert: + 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 s1-schema-distribute: + SELECT citus_schema_distribute('tenant1'); + +citus_schema_distribute +--------------------------------------------------------------------- + +(1 row) + +step s1-begin: + BEGIN; + +step s1-schema-undistribute: + SELECT citus_schema_undistribute('tenant1'); + +citus_schema_undistribute +--------------------------------------------------------------------- + +(1 row) + +step s2-delete: + DELETE FROM tenant1.table3; + +step s1-commit: + COMMIT; + +step s2-delete: <... completed> +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; + +logicalrelid |partmethod|partkey|is_correct_colocid|repmodel|autoconverted +--------------------------------------------------------------------- +tenant1.table3|n | | |s |t +(1 row) + +citus_remove_node +--------------------------------------------------------------------- + +(1 row) + diff --git a/src/test/regress/expected/multi_extension.out b/src/test/regress/expected/multi_extension.out index ef94ebdd1..beb73a5be 100644 --- a/src/test/regress/expected/multi_extension.out +++ b/src/test/regress/expected/multi_extension.out @@ -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 diff --git a/src/test/regress/expected/schema_based_sharding.out b/src/test/regress/expected/schema_based_sharding.out index 1180fe573..1bc5d7963 100644 --- a/src/test/regress/expected/schema_based_sharding.out +++ b/src/test/regress/expected/schema_based_sharding.out @@ -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; diff --git a/src/test/regress/expected/upgrade_list_citus_objects.out b/src/test/regress/expected/upgrade_list_citus_objects.out index 1d44bf5a3..6e30d4ac7 100644 --- a/src/test/regress/expected/upgrade_list_citus_objects.out +++ b/src/test/regress/expected/upgrade_list_citus_objects.out @@ -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) diff --git a/src/test/regress/isolation_schedule b/src/test/regress/isolation_schedule index fed454ac3..1484c712f 100644 --- a/src/test/regress/isolation_schedule +++ b/src/test/regress/isolation_schedule @@ -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 diff --git a/src/test/regress/multi_1_schedule b/src/test/regress/multi_1_schedule index b9a1db0e2..67473e471 100644 --- a/src/test/regress/multi_1_schedule +++ b/src/test/regress/multi_1_schedule @@ -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 diff --git a/src/test/regress/spec/isolation_citus_schema_distribute_undistribute.spec b/src/test/regress/spec/isolation_citus_schema_distribute_undistribute.spec new file mode 100644 index 000000000..24699c7cf --- /dev/null +++ b/src/test/regress/spec/isolation_citus_schema_distribute_undistribute.spec @@ -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" diff --git a/src/test/regress/sql/citus_schema_distribute_undistribute.sql b/src/test/regress/sql/citus_schema_distribute_undistribute.sql new file mode 100644 index 000000000..f6b5ee32b --- /dev/null +++ b/src/test/regress/sql/citus_schema_distribute_undistribute.sql @@ -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);