diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminTestUtils.java index d34e36b56b..5bca7e29ac 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminTestUtils.java @@ -6,7 +6,6 @@ import com.azure.cosmos.CosmosClient; import com.azure.cosmos.CosmosClientBuilder; import com.azure.cosmos.CosmosContainer; -import com.azure.cosmos.CosmosException; import com.azure.cosmos.CosmosStoredProcedure; import com.azure.cosmos.models.CosmosItemRequestOptions; import com.azure.cosmos.models.CosmosQueryRequestOptions; @@ -34,26 +33,22 @@ public CosmosAdminTestUtils(Properties properties) { .buildClient(); metadataDatabase = new CosmosConfig(new DatabaseConfig(properties)) - .getTableMetadataDatabase() + .getMetadataDatabase() .orElse(CosmosAdmin.METADATA_DATABASE); } @Override public void dropMetadataTable() { - client.getDatabase(metadataDatabase).delete(); - try { - client.getDatabase(metadataDatabase).read(); - } catch (CosmosException e) { - if (e.getStatusCode() != 404) { - throw new RuntimeException("Dropping the metadata table failed", e); - } - } + client + .getDatabase(metadataDatabase) + .getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER) + .delete(); } @Override public void truncateMetadataTable() { CosmosContainer container = - client.getDatabase(metadataDatabase).getContainer(CosmosAdmin.METADATA_CONTAINER); + client.getDatabase(metadataDatabase).getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER); CosmosPagedIterable records = container.queryItems("SELECT t.id FROM t", new CosmosQueryRequestOptions(), Record.class); @@ -72,7 +67,7 @@ public void corruptMetadata(String namespace, String table) { .build(); CosmosContainer container = - client.getDatabase(metadataDatabase).getContainer(CosmosAdmin.METADATA_CONTAINER); + client.getDatabase(metadataDatabase).getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER); container.upsertItem(corruptedMetadata); } diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java index 4c96368e75..452da92503 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/ConsensusCommitAdminIntegrationTestWithDynamo.java @@ -3,8 +3,6 @@ import com.scalar.db.transaction.consensuscommit.ConsensusCommitAdminIntegrationTestBase; import java.util.Map; import java.util.Properties; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; public class ConsensusCommitAdminIntegrationTestWithDynamo extends ConsensusCommitAdminIntegrationTestBase { @@ -19,56 +17,8 @@ protected Map getCreationOptions() { return DynamoEnv.getCreationOptions(); } - // Since DynamoDB doesn't have the namespace concept, some behaviors around the namespace are - // different from the other adapters. So disable several tests that check such behaviors - @Override protected boolean isIndexOnBooleanColumnSupported() { return false; } - - @Disabled - @Test - @Override - public void createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperly() {} - - @Disabled - @Test - @Override - public void createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentException() {} - - @Disabled - @Test - @Override - public void createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyException() {} - - @Disabled - @Test - @Override - public void dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly() {} - - @Disabled - @Test - @Override - public void dropNamespace_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() {} - - @Disabled - @Test - @Override - public void dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentException() {} - - @Disabled - @Test - @Override - public void dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException() {} - - @Disabled - @Test - @Override - public void namespaceExists_ShouldReturnCorrectResults() {} - - @Disabled - @Test - @Override - public void createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() {} } diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java index 4cdb3acdc7..4e31b23555 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminIntegrationTest.java @@ -3,8 +3,6 @@ import com.scalar.db.api.DistributedStorageAdminIntegrationTestBase; import java.util.Map; import java.util.Properties; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; public class DynamoAdminIntegrationTest extends DistributedStorageAdminIntegrationTestBase { @@ -22,52 +20,4 @@ protected Map getCreationOptions() { protected boolean isIndexOnBooleanColumnSupported() { return false; } - - // Since DynamoDB doesn't have the namespace concept, some behaviors around the namespace are - // different from the other adapters. So disable several tests that check such behaviors - - @Disabled - @Test - @Override - public void createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperly() {} - - @Disabled - @Test - @Override - public void createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentException() {} - - @Disabled - @Test - @Override - public void createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyException() {} - - @Disabled - @Test - @Override - public void dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly() {} - - @Disabled - @Test - @Override - public void dropNamespace_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() {} - - @Disabled - @Test - @Override - public void dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentException() {} - - @Disabled - @Test - @Override - public void dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException() {} - - @Disabled - @Test - @Override - public void namespaceExists_ShouldReturnCorrectResults() {} - - @Disabled - @Test - @Override - public void createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() {} } diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminTestUtils.java index 36e18430d8..08a1a9a6ff 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminTestUtils.java @@ -48,7 +48,7 @@ public DynamoAdminTestUtils(Properties properties) { .build(); metadataNamespace = config.getNamespacePrefix().orElse("") - + config.getTableMetadataNamespace().orElse(DynamoAdmin.METADATA_NAMESPACE); + + config.getMetadataNamespace().orElse(DynamoAdmin.METADATA_NAMESPACE); namespacePrefix = config.getNamespacePrefix().orElse(""); } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminImportTableIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminImportTableIntegrationTestWithJdbcDatabase.java index 769a4102dc..2b3611e030 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminImportTableIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminImportTableIntegrationTestWithJdbcDatabase.java @@ -1,6 +1,7 @@ package com.scalar.db.storage.jdbc; import com.scalar.db.api.TableMetadata; +import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.transaction.consensuscommit.ConsensusCommitAdminImportTableIntegrationTestBase; import java.sql.SQLException; import java.util.Map; @@ -47,7 +48,8 @@ public void importTable_ShouldWorkProperly() throws Exception { @Test @Override @EnabledIf("isSqlite") - public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() { + public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() + throws ExecutionException { super.importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException(); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java index 025d4e2dd4..99851ef017 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/ConsensusCommitAdminIntegrationTestWithJdbcDatabase.java @@ -1,10 +1,7 @@ package com.scalar.db.storage.jdbc; -import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.transaction.consensuscommit.ConsensusCommitAdminIntegrationTestBase; import java.util.Properties; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; public class ConsensusCommitAdminIntegrationTestWithJdbcDatabase extends ConsensusCommitAdminIntegrationTestBase { @@ -13,78 +10,4 @@ public class ConsensusCommitAdminIntegrationTestWithJdbcDatabase protected Properties getProps(String testName) { return JdbcEnv.getProperties(testName); } - - // Since SQLite doesn't have persistent namespaces, some behaviors around the namespace are - // different from the other adapters. So disable several tests that check such behaviors. - - @SuppressWarnings("unused") - private boolean isSqlite() { - return JdbcEnv.isSqlite(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperly() - throws ExecutionException { - super.createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperly(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentException() { - super.createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyException() { - super.createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly() - throws ExecutionException { - super.dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() { - super.dropNamespace_ForNonExistingNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentException() - throws ExecutionException { - super.dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException() { - super.dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void namespaceExists_ShouldReturnCorrectResults() throws ExecutionException { - super.namespaceExists_ShouldReturnCorrectResults(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() { - super.createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentException(); - } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java index b36c0a96a8..d554b48843 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java @@ -2,6 +2,7 @@ import com.scalar.db.api.DistributedStorageAdminImportTableIntegrationTestBase; import com.scalar.db.api.TableMetadata; +import com.scalar.db.exception.storage.ExecutionException; import java.sql.SQLException; import java.util.Map; import java.util.Properties; @@ -47,7 +48,8 @@ public void importTable_ShouldWorkProperly() throws Exception { @Test @Override @EnabledIf("isSqlite") - public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() { + public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() + throws ExecutionException { super.importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException(); } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java index ab2f9f1de1..eddcc1050f 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminImportTestUtils.java @@ -168,15 +168,10 @@ public Map createExistingDatabaseWithAllDataTypes(String results.put(table, null); }); - createExistingDatabase(namespace); execute(sqls.toArray(new String[0])); return results; } - public void createExistingDatabase(String namespace) throws SQLException { - execute(rdbEngine.createNamespaceSqls(rdbEngine.enclose(namespace))); - } - public void dropTable(String namespace, String table) throws SQLException { String dropTable = "DROP TABLE " + rdbEngine.encloseFullTableName(namespace, table); execute(dropTable); diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java index 644ec62f63..c294c1378a 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminIntegrationTest.java @@ -1,10 +1,7 @@ package com.scalar.db.storage.jdbc; import com.scalar.db.api.DistributedStorageAdminIntegrationTestBase; -import com.scalar.db.exception.storage.ExecutionException; import java.util.Properties; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; public class JdbcAdminIntegrationTest extends DistributedStorageAdminIntegrationTestBase { @@ -12,78 +9,4 @@ public class JdbcAdminIntegrationTest extends DistributedStorageAdminIntegration protected Properties getProperties(String testName) { return JdbcEnv.getProperties(testName); } - - // Since SQLite doesn't have persistent namespaces, some behaviors around the namespace are - // different from the other adapters. So disable several tests that check such behaviors. - - @SuppressWarnings("unused") - private boolean isSqlite() { - return JdbcEnv.isSqlite(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperly() - throws ExecutionException { - super.createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperly(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentException() { - super.createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyException() { - super.createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly() - throws ExecutionException { - super.dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() { - super.dropNamespace_ForNonExistingNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentException() - throws ExecutionException { - super.dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException() { - super.dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void namespaceExists_ShouldReturnCorrectResults() throws ExecutionException { - super.namespaceExists_ShouldReturnCorrectResults(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() { - super.createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentException(); - } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminTestUtils.java index fc0d9e14d5..260d6b8c19 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminTestUtils.java @@ -19,7 +19,7 @@ public class JdbcAdminTestUtils extends AdminTestUtils { public JdbcAdminTestUtils(Properties properties) { super(properties); config = new JdbcConfig(new DatabaseConfig(properties)); - metadataSchema = config.getTableMetadataSchema().orElse(JdbcAdmin.METADATA_SCHEMA); + metadataSchema = config.getMetadataSchema().orElse(JdbcAdmin.METADATA_SCHEMA); rdbEngine = RdbEngineFactory.create(config); } @@ -27,9 +27,6 @@ public JdbcAdminTestUtils(Properties properties) { public void dropMetadataTable() throws SQLException { execute( "DROP TABLE " + rdbEngine.encloseFullTableName(metadataSchema, JdbcAdmin.METADATA_TABLE)); - - String dropNamespaceStatement = rdbEngine.dropNamespaceSql(metadataSchema); - execute(dropNamespaceStatement); } @Override diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcSchemaLoaderImportIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcSchemaLoaderImportIntegrationTest.java index 8db1b7612a..518cd53add 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcSchemaLoaderImportIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcSchemaLoaderImportIntegrationTest.java @@ -28,11 +28,6 @@ protected AdminTestUtils getAdminTestUtils(String testName) { return new JdbcAdminTestUtils(getProperties(testName)); } - @Override - protected void createExistingDatabase(String namespace) throws Exception { - testUtils.createExistingDatabase(namespace); - } - @SuppressFBWarnings("SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE") @Override protected void createImportableTable(String namespace, String table) throws Exception { diff --git a/core/src/integration-test/java/com/scalar/db/storage/multistorage/MultiStorageAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/multistorage/MultiStorageAdminIntegrationTest.java index 217a1056b5..a7e5489a98 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/multistorage/MultiStorageAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/multistorage/MultiStorageAdminIntegrationTest.java @@ -12,6 +12,7 @@ import com.scalar.db.service.StorageFactory; import java.util.Arrays; import java.util.Properties; +import java.util.Set; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -341,4 +342,15 @@ public void getTableMetadata_ForTable1InNamespace2_ShouldReturnMetadataFromJdbcA assertThat(tableMetadata.getSecondaryIndexNames()).isEmpty(); } + + @Test + public void getNamespaceNames_ShouldReturnExistingNamespaces() throws ExecutionException { + // Arrange + + // Act + Set namespaces = multiStorageAdmin.getNamespaceNames(); + + // Assert + assertThat(namespaces).containsExactlyInAnyOrder(NAMESPACE1, NAMESPACE2); + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTestUtils.java index db56e2c1c9..fcb76ffa78 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTestUtils.java @@ -7,7 +7,6 @@ import com.scalar.db.storage.jdbc.JdbcConfig; import com.scalar.db.storage.jdbc.JdbcUtils; import com.scalar.db.storage.jdbc.RdbEngineFactory; -import com.scalar.db.storage.jdbc.RdbEngineOracle; import com.scalar.db.storage.jdbc.RdbEngineStrategy; import com.scalar.db.util.AdminTestUtils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -19,6 +18,7 @@ public class MultiStorageAdminTestUtils extends AdminTestUtils { + // for JDBC private final JdbcConfig jdbcConfig; private final String jdbcMetadataSchema; private final RdbEngineStrategy rdbEngine; @@ -27,8 +27,9 @@ public MultiStorageAdminTestUtils(Properties cassandraProperties, Properties jdb // Cassandra has the coordinator tables super(cassandraProperties); + // for JDBC jdbcConfig = new JdbcConfig(new DatabaseConfig(jdbcProperties)); - jdbcMetadataSchema = jdbcConfig.getTableMetadataSchema().orElse(JdbcAdmin.METADATA_SCHEMA); + jdbcMetadataSchema = jdbcConfig.getMetadataSchema().orElse(JdbcAdmin.METADATA_SCHEMA); rdbEngine = RdbEngineFactory.create(jdbcConfig); } @@ -40,14 +41,6 @@ public void dropMetadataTable() throws SQLException { execute( "DROP TABLE " + rdbEngine.encloseFullTableName(jdbcMetadataSchema, JdbcAdmin.METADATA_TABLE)); - - String dropNamespaceStatement; - if (rdbEngine instanceof RdbEngineOracle) { - dropNamespaceStatement = "DROP USER " + rdbEngine.enclose(jdbcMetadataSchema); - } else { - dropNamespaceStatement = "DROP SCHEMA " + rdbEngine.enclose(jdbcMetadataSchema); - } - execute(dropNamespaceStatement); } @Override diff --git a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminImportTableIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminImportTableIntegrationTest.java index 253621aebd..b892917f63 100644 --- a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminImportTableIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminImportTableIntegrationTest.java @@ -3,6 +3,7 @@ import com.scalar.db.api.DistributedTransactionAdminImportTableIntegrationTestBase; import com.scalar.db.api.TableMetadata; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.storage.jdbc.JdbcAdminImportTestUtils; import com.scalar.db.storage.jdbc.JdbcEnv; import java.sql.SQLException; @@ -52,7 +53,8 @@ public void importTable_ShouldWorkProperly() throws Exception { @Test @Override @EnabledIf("isSqlite") - public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() { + public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() + throws ExecutionException { super.importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException(); } } diff --git a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java index d76ccf8e6b..fecdfd68d2 100644 --- a/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminIntegrationTest.java @@ -7,7 +7,6 @@ import java.util.Properties; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; public class JdbcTransactionAdminIntegrationTest extends DistributedTransactionAdminIntegrationTestBase { @@ -20,83 +19,8 @@ protected Properties getProperties(String testName) { return properties; } - // Since SQLite doesn't have persistent namespaces, some behaviors around the namespace are - // different from the other adapters. So disable several tests that check such behaviors. - - @SuppressWarnings("unused") - private boolean isSqlite() { - return JdbcEnv.isSqlite(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperly() - throws ExecutionException { - super.createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperly(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentException() { - super.createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyException() { - super.createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly() - throws ExecutionException { - super.dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() { - super.dropNamespace_ForNonExistingNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentException() - throws ExecutionException { - super.dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException() { - super.dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void namespaceExists_ShouldReturnCorrectResults() throws ExecutionException { - super.namespaceExists_ShouldReturnCorrectResults(); - } - - @Test - @Override - @DisabledIf("isSqlite") - public void createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentException() { - super.createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentException(); - } - // Disable several tests for the coordinator tables since JDBC transaction doesn't have // coordinator tables - @Disabled("JDBC transaction doesn't have coordinator tables") @Test @Override diff --git a/core/src/main/java/com/scalar/db/api/Admin.java b/core/src/main/java/com/scalar/db/api/Admin.java index e75774606a..518abb975a 100644 --- a/core/src/main/java/com/scalar/db/api/Admin.java +++ b/core/src/main/java/com/scalar/db/api/Admin.java @@ -334,7 +334,7 @@ default boolean indexExists(String namespace, String table, String columnName) * Returns the names of the table belonging to the given namespace. * * @param namespace a namespace - * @return a set of table names, an empty list if the namespace doesn't exist + * @return a set of table names, an empty set if the namespace doesn't exist * @throws ExecutionException if the operation fails */ Set getNamespaceTableNames(String namespace) throws ExecutionException; @@ -413,4 +413,12 @@ void addNewColumnToTable(String namespace, String table, String columnName, Data * @throws ExecutionException if the operation fails */ void importTable(String namespace, String table) throws ExecutionException; + + /** + * Returns the names of the existing namespaces created through Scalar DB. + * + * @return a set of namespaces names, an empty set if no namespaces exist + * @throws ExecutionException if the operation fails + */ + Set getNamespaceNames() throws ExecutionException; } diff --git a/core/src/main/java/com/scalar/db/common/CheckedDistributedStorageAdmin.java b/core/src/main/java/com/scalar/db/common/CheckedDistributedStorageAdmin.java index 514f4d202d..ef4b1fe495 100644 --- a/core/src/main/java/com/scalar/db/common/CheckedDistributedStorageAdmin.java +++ b/core/src/main/java/com/scalar/db/common/CheckedDistributedStorageAdmin.java @@ -14,26 +14,15 @@ public class CheckedDistributedStorageAdmin implements DistributedStorageAdmin { private final DistributedStorageAdmin admin; - /** - * Whether to check if the namespace exists or not. Set false when the storage does not support - * namespaces. - */ - private final boolean checkNamespace; - - public CheckedDistributedStorageAdmin(DistributedStorageAdmin admin) { - this(admin, true); - } - @SuppressFBWarnings("EI_EXPOSE_REP2") - public CheckedDistributedStorageAdmin(DistributedStorageAdmin admin, boolean checkNamespace) { + public CheckedDistributedStorageAdmin(DistributedStorageAdmin admin) { this.admin = admin; - this.checkNamespace = checkNamespace; } @Override public void createNamespace(String namespace, Map options) throws ExecutionException { - if (checkNamespace && namespaceExists(namespace)) { + if (namespaceExists(namespace)) { throw new IllegalArgumentException("Namespace already exists: " + namespace); } @@ -48,7 +37,7 @@ public void createNamespace(String namespace, Map options) public void createTable( String namespace, String table, TableMetadata metadata, Map options) throws ExecutionException { - if (checkNamespace && !namespaceExists(namespace)) { + if (!namespaceExists(namespace)) { throw new IllegalArgumentException("Namespace does not exist: " + namespace); } if (tableExists(namespace, table)) { @@ -81,7 +70,7 @@ public void dropTable(String namespace, String table) throws ExecutionException @Override public void dropNamespace(String namespace) throws ExecutionException { - if (checkNamespace && !namespaceExists(namespace)) { + if (!namespaceExists(namespace)) { throw new IllegalArgumentException("Namespace does not exist: " + namespace); } if (!getNamespaceTableNames(namespace).isEmpty()) { @@ -276,6 +265,15 @@ public void addNewColumnToTable( } } + @Override + public Set getNamespaceNames() throws ExecutionException { + try { + return admin.getNamespaceNames(); + } catch (ExecutionException e) { + throw new ExecutionException("Getting the namespace names failed", e); + } + } + @Override public TableMetadata getImportTableMetadata(String namespace, String table) throws ExecutionException { diff --git a/core/src/main/java/com/scalar/db/service/AdminService.java b/core/src/main/java/com/scalar/db/service/AdminService.java index 14d339785e..777078003e 100644 --- a/core/src/main/java/com/scalar/db/service/AdminService.java +++ b/core/src/main/java/com/scalar/db/service/AdminService.java @@ -111,6 +111,11 @@ public void importTable(String namespace, String table) throws ExecutionExceptio admin.importTable(namespace, table); } + @Override + public Set getNamespaceNames() throws ExecutionException { + return admin.getNamespaceNames(); + } + @Override public void close() { admin.close(); diff --git a/core/src/main/java/com/scalar/db/storage/cassandra/Cassandra.java b/core/src/main/java/com/scalar/db/storage/cassandra/Cassandra.java index 7271ced9f5..0860f992d8 100644 --- a/core/src/main/java/com/scalar/db/storage/cassandra/Cassandra.java +++ b/core/src/main/java/com/scalar/db/storage/cassandra/Cassandra.java @@ -61,7 +61,8 @@ public Cassandra(DatabaseConfig config) { metadataManager = new TableMetadataManager( - new CassandraAdmin(clusterManager), config.getMetadataCacheExpirationTimeSecs()); + new CassandraAdmin(clusterManager, config), + config.getMetadataCacheExpirationTimeSecs()); operationChecker = new OperationChecker(metadataManager); } diff --git a/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java b/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java index 7461bbc4de..1ec4eae3d0 100644 --- a/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java @@ -6,6 +6,8 @@ import com.datastax.driver.core.ClusteringOrder; import com.datastax.driver.core.ColumnMetadata; import com.datastax.driver.core.KeyspaceMetadata; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.schemabuilder.Create; import com.datastax.driver.core.schemabuilder.CreateKeyspace; @@ -23,6 +25,7 @@ import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Locale; import java.util.Map; @@ -36,17 +39,22 @@ public class CassandraAdmin implements DistributedStorageAdmin { public static final String REPLICATION_STRATEGY = "replication-strategy"; public static final String COMPACTION_STRATEGY = "compaction-strategy"; public static final String REPLICATION_FACTOR = "replication-factor"; + public static final String METADATA_KEYSPACE = "scalardb"; + public static final String NAMESPACES_TABLE = "namespaces"; + public static final String NAMESPACES_NAME_COL = "name"; @VisibleForTesting static final String INDEX_NAME_PREFIX = "index"; - private final ClusterManager clusterManager; + private final String metadataKeyspace; @Inject public CassandraAdmin(DatabaseConfig config) { - clusterManager = new ClusterManager(config); + this(new ClusterManager(config), config); } - CassandraAdmin(ClusterManager clusterManager) { + CassandraAdmin(ClusterManager clusterManager, DatabaseConfig config) { this.clusterManager = clusterManager; + CassandraConfig cassandraConfig = new CassandraConfig(config); + metadataKeyspace = cassandraConfig.getMetadataKeyspace().orElse(METADATA_KEYSPACE); } @Override @@ -60,7 +68,24 @@ public void createTable( @Override public void createNamespace(String namespace, Map options) throws ExecutionException { - CreateKeyspace query = SchemaBuilder.createKeyspace(quoteIfNecessary(namespace)); + try { + createKeyspace(namespace, options, false); + createKeyspace(metadataKeyspace, options, true); + createNamespacesTableIfNotExists(); + insertIntoNamespacesTable(namespace); + } catch (IllegalArgumentException e) { + // thrown by ReplicationStrategy.fromString() when the given replication strategy is unknown + throw e; + } catch (RuntimeException e) { + throw new ExecutionException(String.format("Creating the keyspace %s failed", namespace), e); + } + } + + private void createKeyspace(String keyspace, Map options, boolean ifNotExists) { + CreateKeyspace query = SchemaBuilder.createKeyspace(quoteIfNecessary(keyspace)); + if (ifNotExists) { + query = query.ifNotExists(); + } String replicationFactor = options.getOrDefault(REPLICATION_FACTOR, "1"); ReplicationStrategy replicationStrategy = options.containsKey(REPLICATION_STRATEGY) @@ -74,13 +99,18 @@ public void createNamespace(String namespace, Map options) replicationOptions.put("class", ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY.toString()); replicationOptions.put("dc1", replicationFactor); } - try { - clusterManager - .getSession() - .execute(query.with().replication(replicationOptions).getQueryString()); - } catch (RuntimeException e) { - throw new ExecutionException(String.format("Creating the keyspace %s failed", namespace), e); - } + String queryString = query.with().replication(replicationOptions).getQueryString(); + + clusterManager.getSession().execute(queryString); + } + + private void insertIntoNamespacesTable(String keyspace) { + String insertQuery = + QueryBuilder.insertInto( + quoteIfNecessary(metadataKeyspace), quoteIfNecessary(NAMESPACES_TABLE)) + .value(NAMESPACES_NAME_COL, quoteIfNecessary(keyspace)) + .toString(); + clusterManager.getSession().execute(insertQuery); } @Override @@ -98,14 +128,30 @@ public void dropTable(String namespace, String table) throws ExecutionException @Override public void dropNamespace(String namespace) throws ExecutionException { - String dropKeyspace = SchemaBuilder.dropKeyspace(quoteIfNecessary(namespace)).getQueryString(); try { - clusterManager.getSession().execute(dropKeyspace); + dropKeyspace(namespace); + deleteFromNamespacesTable(namespace); + dropNamespacesTableIfEmpty(); } catch (RuntimeException e) { throw new ExecutionException(String.format("Dropping the %s keyspace failed", namespace), e); } } + private void dropKeyspace(String keyspace) { + String dropKeyspaceQuery = + SchemaBuilder.dropKeyspace(quoteIfNecessary(keyspace)).getQueryString(); + clusterManager.getSession().execute(dropKeyspaceQuery); + } + + private void deleteFromNamespacesTable(String keyspace) { + String deleteQuery = + QueryBuilder.delete() + .from(quoteIfNecessary(metadataKeyspace), quoteIfNecessary(NAMESPACES_TABLE)) + .where(QueryBuilder.eq(NAMESPACES_NAME_COL, quoteIfNecessary(keyspace))) + .toString(); + clusterManager.getSession().execute(deleteQuery); + } + @Override public void truncateTable(String namespace, String table) throws ExecutionException { String truncateTableQuery = @@ -240,13 +286,18 @@ public Set getNamespaceTableNames(String namespace) throws ExecutionExce @Override public boolean namespaceExists(String namespace) throws ExecutionException { try { - KeyspaceMetadata keyspace = - clusterManager - .getSession() - .getCluster() - .getMetadata() - .getKeyspace(quoteIfNecessary(namespace)); - return keyspace != null; + if (clusterManager.getMetadata(metadataKeyspace, NAMESPACES_TABLE) == null) { + return false; + } + + String query = + QueryBuilder.select(NAMESPACES_NAME_COL) + .from(quoteIfNecessary(metadataKeyspace), quoteIfNecessary(NAMESPACES_TABLE)) + .where(QueryBuilder.eq(NAMESPACES_NAME_COL, quoteIfNecessary(namespace))) + .toString(); + ResultSet resultSet = clusterManager.getSession().execute(query); + + return resultSet.one() != null; } catch (RuntimeException e) { throw new ExecutionException("Checking if the namespace exists failed", e); } @@ -286,6 +337,50 @@ public void addNewColumnToTable( } } + @Override + public Set getNamespaceNames() throws ExecutionException { + try { + if (clusterManager.getMetadata(metadataKeyspace, NAMESPACES_TABLE) == null) { + return Collections.emptySet(); + } + + Set keyspaceNames = new HashSet<>(); + String selectQuery = + QueryBuilder.select(NAMESPACES_NAME_COL) + .from(quoteIfNecessary(metadataKeyspace), quoteIfNecessary(NAMESPACES_TABLE)) + .getQueryString(); + for (Row row : clusterManager.getSession().execute(selectQuery).all()) { + keyspaceNames.add(row.getString(NAMESPACES_NAME_COL)); + } + + return keyspaceNames; + } catch (RuntimeException e) { + throw new ExecutionException("Retrieving the existing namespace names failed", e); + } + } + + private void createNamespacesTableIfNotExists() { + String createTableQuery = + SchemaBuilder.createTable( + quoteIfNecessary(metadataKeyspace), quoteIfNecessary(NAMESPACES_TABLE)) + .ifNotExists() + .addPartitionKey(NAMESPACES_NAME_COL, com.datastax.driver.core.DataType.text()) + .getQueryString(); + clusterManager.getSession().execute(createTableQuery); + } + + private void dropNamespacesTableIfEmpty() { + String selectQuery = + QueryBuilder.select(NAMESPACES_NAME_COL) + .from(quoteIfNecessary(metadataKeyspace), quoteIfNecessary(NAMESPACES_TABLE)) + .limit(1) + .getQueryString(); + boolean isKeyspacesTableEmpty = clusterManager.getSession().execute(selectQuery).one() == null; + if (isKeyspacesTableEmpty) { + dropKeyspace(metadataKeyspace); + } + } + @VisibleForTesting void createTableInternal( String keyspace, String table, TableMetadata metadata, Map options) diff --git a/core/src/main/java/com/scalar/db/storage/cassandra/CassandraConfig.java b/core/src/main/java/com/scalar/db/storage/cassandra/CassandraConfig.java new file mode 100644 index 0000000000..290a5927d1 --- /dev/null +++ b/core/src/main/java/com/scalar/db/storage/cassandra/CassandraConfig.java @@ -0,0 +1,25 @@ +package com.scalar.db.storage.cassandra; + +import static com.scalar.db.config.ConfigUtils.getString; + +import com.scalar.db.config.DatabaseConfig; +import java.util.Optional; +import javax.annotation.Nullable; + +public class CassandraConfig { + public static final String PREFIX = DatabaseConfig.PREFIX + "cassandra."; + public static final String METADATA_KEYSPACE = PREFIX + "metadata.keyspace"; + @Nullable private final String metadataKeyspace; + + public CassandraConfig(DatabaseConfig databaseConfig) { + String storage = databaseConfig.getStorage(); + if (!storage.equals("cassandra")) { + throw new IllegalArgumentException(DatabaseConfig.STORAGE + " should be 'cassandra'"); + } + metadataKeyspace = getString(databaseConfig.getProperties(), METADATA_KEYSPACE, null); + } + + public Optional getMetadataKeyspace() { + return Optional.ofNullable(metadataKeyspace); + } +} diff --git a/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java index f525af7f86..56f4fbb855 100644 --- a/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java @@ -53,7 +53,8 @@ public class CosmosAdmin implements DistributedStorageAdmin { public static final String DEFAULT_NO_SCALING = "false"; public static final String METADATA_DATABASE = "scalardb"; - public static final String METADATA_CONTAINER = "metadata"; + public static final String TABLE_METADATA_CONTAINER = "metadata"; + public static final String NAMESPACES_CONTAINER = "namespaces"; private static final String ID = "id"; private static final String CONCATENATED_PARTITION_KEY = "concatenatedPartitionKey"; private static final String PARTITION_KEY_PATH = "/" + CONCATENATED_PARTITION_KEY; @@ -77,12 +78,12 @@ public CosmosAdmin(DatabaseConfig databaseConfig) { .directMode() .consistencyLevel(ConsistencyLevel.STRONG) .buildClient(); - metadataDatabase = config.getTableMetadataDatabase().orElse(METADATA_DATABASE); + metadataDatabase = config.getMetadataDatabase().orElse(METADATA_DATABASE); } CosmosAdmin(CosmosClient client, CosmosConfig config) { this.client = client; - metadataDatabase = config.getTableMetadataDatabase().orElse(METADATA_DATABASE); + metadataDatabase = config.getMetadataDatabase().orElse(METADATA_DATABASE); } @Override @@ -136,7 +137,7 @@ private boolean storedProcedureExists(String namespace, String table) { .read(); return true; } catch (CosmosException e) { - if (e.getStatusCode() == 404) { + if (e.getStatusCode() == CosmosErrorCode.NOT_FOUND.get()) { return false; } throw e; @@ -218,27 +219,27 @@ private IndexingPolicy computeIndexingPolicy(TableMetadata metadata) { private void putTableMetadata(String namespace, String table, TableMetadata metadata) throws ExecutionException { try { - createMetadataDatabaseAndContainerIfNotExists(); + createMetadataDatabaseAndTableMetadataContainerIfNotExists(); CosmosTableMetadata cosmosTableMetadata = convertToCosmosTableMetadata(getFullTableName(namespace, table), metadata); - getMetadataContainer().upsertItem(cosmosTableMetadata); + getTableMetadataContainer().upsertItem(cosmosTableMetadata); } catch (RuntimeException e) { throw new ExecutionException("Putting the table metadata failed", e); } } - private void createMetadataDatabaseAndContainerIfNotExists() { + private void createMetadataDatabaseAndTableMetadataContainerIfNotExists() { ThroughputProperties manualThroughput = ThroughputProperties.createManualThroughput(Integer.parseInt(DEFAULT_REQUEST_UNIT)); client.createDatabaseIfNotExists(metadataDatabase, manualThroughput); CosmosContainerProperties containerProperties = - new CosmosContainerProperties(METADATA_CONTAINER, "/id"); + new CosmosContainerProperties(TABLE_METADATA_CONTAINER, "/id"); client.getDatabase(metadataDatabase).createContainerIfNotExists(containerProperties); } - private CosmosContainer getMetadataContainer() { - return client.getDatabase(metadataDatabase).getContainer(METADATA_CONTAINER); + private CosmosContainer getTableMetadataContainer() { + return client.getDatabase(metadataDatabase).getContainer(TABLE_METADATA_CONTAINER); } private CosmosTableMetadata convertToCosmosTableMetadata( @@ -268,6 +269,8 @@ public void createNamespace(String namespace, Map options) throws ExecutionException { try { client.createDatabase(namespace, calculateThroughput(options)); + createMetadataDatabaseAndNamespaceContainerIfNotExists(); + getNamespacesContainer().createItem(new CosmosNamespace(namespace)); } catch (RuntimeException e) { throw new ExecutionException("Creating the database failed", e); } @@ -287,20 +290,19 @@ public void dropTable(String namespace, String table) throws ExecutionException private void deleteTableMetadata(String namespace, String table) throws ExecutionException { String fullTableName = getFullTableName(namespace, table); try { - getMetadataContainer() + getTableMetadataContainer() .deleteItem( fullTableName, new PartitionKey(fullTableName), new CosmosItemRequestOptions()); - // Delete the metadata container and table if there is no more metadata stored - if (!getMetadataContainer() + // Delete the table metadata container if there is no more metadata stored + if (!getTableMetadataContainer() .queryItems( - "SELECT 1 FROM " + METADATA_CONTAINER + " OFFSET 0 LIMIT 1", + "SELECT 1 FROM " + TABLE_METADATA_CONTAINER + " OFFSET 0 LIMIT 1", new CosmosQueryRequestOptions(), Object.class) .stream() .findFirst() .isPresent()) { - getMetadataContainer().delete(); - client.getDatabase(metadataDatabase).delete(); + getTableMetadataContainer().delete(); } } catch (RuntimeException e) { throw new ExecutionException("Deleting the table metadata failed", e); @@ -311,6 +313,22 @@ private void deleteTableMetadata(String namespace, String table) throws Executio public void dropNamespace(String namespace) throws ExecutionException { try { client.getDatabase(namespace).delete(); + CosmosContainer namespacesContainer = getNamespacesContainer(); + namespacesContainer.deleteItem( + new CosmosNamespace(namespace), new CosmosItemRequestOptions()); + + // Delete the namespaces container and metadata database if there is no more existing + // namespaces + if (!namespacesContainer + .queryItems( + "SELECT 1 FROM container OFFSET 0 LIMIT 1", + new CosmosQueryRequestOptions(), + Object.class) + .stream() + .findFirst() + .isPresent()) { + client.getDatabase(metadataDatabase).delete(); + } } catch (RuntimeException e) { throw new ExecutionException("Deleting the database failed", e); } @@ -389,7 +407,7 @@ private void updateIndexingPolicy( public TableMetadata getTableMetadata(String namespace, String table) throws ExecutionException { try { String fullName = getFullTableName(namespace, table); - CosmosTableMetadata cosmosTableMetadata = readMetadata(fullName); + CosmosTableMetadata cosmosTableMetadata = readTableMetadata(fullName); if (cosmosTableMetadata == null) { return null; } @@ -399,9 +417,9 @@ public TableMetadata getTableMetadata(String namespace, String table) throws Exe } } - private CosmosTableMetadata readMetadata(String fullName) { + private CosmosTableMetadata readTableMetadata(String fullName) { try { - return getMetadataContainer() + return getTableMetadataContainer() .readItem(fullName, new PartitionKey(fullName), CosmosTableMetadata.class) .getItem(); } catch (NotFoundException e) { @@ -466,7 +484,17 @@ private ThroughputProperties calculateThroughput(Map options) { @Override public boolean namespaceExists(String namespace) throws ExecutionException { - return databaseExists(namespace); + try { + getNamespacesContainer() + .readItem(namespace, new PartitionKey(namespace), CosmosNamespace.class); + return true; + } catch (RuntimeException e) { + if (e instanceof CosmosException + && ((CosmosException) e).getStatusCode() == CosmosErrorCode.NOT_FOUND.get()) { + return false; + } + throw new ExecutionException("Checking if the namespace exists failed", e); + } } @Override @@ -474,17 +502,11 @@ public void repairTable( String namespace, String table, TableMetadata metadata, Map options) throws ExecutionException { try { - try { - // Since the metadata table may be missing, we cannot use CosmosAdmin.tableExists() as it - // queries the metadata table to verify if the given table exists - client.getDatabase(namespace).getContainer(table).read(); - } catch (CosmosException e) { - if (e.getStatusCode() == 404) { - throw new IllegalArgumentException( - "The table " + getFullTableName(namespace, table) + " does not exist"); - } + if (!containerExists(namespace, table)) { + throw new IllegalArgumentException( + "The table " + getFullTableName(namespace, table) + " does not exist"); } - createMetadataDatabaseAndContainerIfNotExists(); + createMetadataDatabaseAndTableMetadataContainerIfNotExists(); putTableMetadata(namespace, table, metadata); if (!storedProcedureExists(namespace, table)) { addStoredProcedure(namespace, table); @@ -495,34 +517,21 @@ public void repairTable( } } - private boolean databaseExists(String id) throws ExecutionException { - try { - client.getDatabase(id).read(); - } catch (RuntimeException e) { - if (e instanceof CosmosException - && ((CosmosException) e).getStatusCode() == CosmosErrorCode.NOT_FOUND.get()) { - return false; - } - throw new ExecutionException(String.format("Reading the database %s failed", id), e); - } - return true; - } - @Override public Set getNamespaceTableNames(String namespace) throws ExecutionException { try { - if (!metadataContainerExists()) { + if (!tableMetadataContainerExists()) { return Collections.emptySet(); } String selectAllDatabaseContainer = "SELECT * FROM " - + METADATA_CONTAINER + + TABLE_METADATA_CONTAINER + " WHERE " - + METADATA_CONTAINER + + TABLE_METADATA_CONTAINER + ".id LIKE '" + namespace + ".%'"; - return getMetadataContainer() + return getTableMetadataContainer() .queryItems( selectAllDatabaseContainer, new CosmosQueryRequestOptions(), @@ -571,9 +580,48 @@ public void importTable(String namespace, String table) { "Import-related functionality is not supported in Cosmos DB"); } - private boolean metadataContainerExists() { + @Override + public Set getNamespaceNames() throws ExecutionException { + try { + if (!namespacesContainerExists()) { + return Collections.emptySet(); + } + + CosmosPagedIterable allNamespaces = + getNamespacesContainer() + .queryItems( + "SELECT * FROM container", + new CosmosQueryRequestOptions(), + CosmosNamespace.class); + + return allNamespaces.stream().map(CosmosNamespace::getId).collect(Collectors.toSet()); + } catch (RuntimeException e) { + throw new ExecutionException("Retrieving the namespaces names failed", e); + } + } + + private void createMetadataDatabaseAndNamespaceContainerIfNotExists() { + ThroughputProperties manualThroughput = + ThroughputProperties.createManualThroughput(Integer.parseInt(DEFAULT_REQUEST_UNIT)); + client.createDatabaseIfNotExists(metadataDatabase, manualThroughput); + client.getDatabase(metadataDatabase).createContainerIfNotExists(NAMESPACES_CONTAINER, "/id"); + } + + private CosmosContainer getNamespacesContainer() { + return client.getDatabase(metadataDatabase).getContainer(NAMESPACES_CONTAINER); + } + + private boolean tableMetadataContainerExists() { + return containerExists(metadataDatabase, TABLE_METADATA_CONTAINER); + } + + private boolean namespacesContainerExists() { + return containerExists(metadataDatabase, NAMESPACES_CONTAINER); + } + + private boolean containerExists(String databaseId, String containerId) throws CosmosException { try { - client.getDatabase(metadataDatabase).getContainer(METADATA_CONTAINER).read(); + client.getDatabase(databaseId).getContainer(containerId).read(); } catch (RuntimeException e) { if (e instanceof CosmosException && ((CosmosException) e).getStatusCode() == CosmosErrorCode.NOT_FOUND.get()) { diff --git a/core/src/main/java/com/scalar/db/storage/cosmos/CosmosConfig.java b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosConfig.java index 0a163847ab..6381dfd2b9 100644 --- a/core/src/main/java/com/scalar/db/storage/cosmos/CosmosConfig.java +++ b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosConfig.java @@ -6,19 +6,24 @@ import java.util.Optional; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Immutable public class CosmosConfig { - + private static final Logger logger = LoggerFactory.getLogger(CosmosConfig.class); public static final String PREFIX = DatabaseConfig.PREFIX + "cosmos."; + /** @deprecated As of 5.0, will be removed. Use {@link #METADATA_DATABASE} instead. */ + @Deprecated public static final String TABLE_METADATA_DATABASE = PREFIX + "table_metadata.database"; + public static final String METADATA_DATABASE = PREFIX + "metadata.database"; private final String endpoint; private final String key; - @Nullable private final String tableMetadataDatabase; + @Nullable private final String metadataDatabase; public CosmosConfig(DatabaseConfig databaseConfig) { - String storage = databaseConfig.getStorage(); + String storage = databaseConfig.getProperties().getProperty(DatabaseConfig.STORAGE); if (!"cosmos".equals(storage)) { throw new IllegalArgumentException(DatabaseConfig.STORAGE + " should be 'cosmos'"); } @@ -28,8 +33,23 @@ public CosmosConfig(DatabaseConfig databaseConfig) { } endpoint = databaseConfig.getContactPoints().get(0); key = databaseConfig.getPassword().orElse(null); - tableMetadataDatabase = - getString(databaseConfig.getProperties(), TABLE_METADATA_DATABASE, null); + + if (databaseConfig.getProperties().containsKey(METADATA_DATABASE) + && databaseConfig.getProperties().containsKey(TABLE_METADATA_DATABASE)) { + throw new IllegalArgumentException( + "Use either " + METADATA_DATABASE + " or " + TABLE_METADATA_DATABASE + " but not both"); + } + if (databaseConfig.getProperties().containsKey(TABLE_METADATA_DATABASE)) { + logger.warn( + "The configuration property \"" + + TABLE_METADATA_DATABASE + + "\" is deprecated and will be removed in 5.0.0. Please use \"" + + METADATA_DATABASE + + "\" instead"); + metadataDatabase = getString(databaseConfig.getProperties(), TABLE_METADATA_DATABASE, null); + } else { + metadataDatabase = getString(databaseConfig.getProperties(), METADATA_DATABASE, null); + } } public String getEndpoint() { @@ -40,7 +60,7 @@ public String getKey() { return key; } - public Optional getTableMetadataDatabase() { - return Optional.ofNullable(tableMetadataDatabase); + public Optional getMetadataDatabase() { + return Optional.ofNullable(metadataDatabase); } } diff --git a/core/src/main/java/com/scalar/db/storage/cosmos/CosmosNamespace.java b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosNamespace.java new file mode 100644 index 0000000000..7c20167a0a --- /dev/null +++ b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosNamespace.java @@ -0,0 +1,46 @@ +package com.scalar.db.storage.cosmos; + +import com.google.common.base.MoreObjects; +import java.util.Objects; +import javax.annotation.concurrent.Immutable; + +/** An entity class to store namespace metadata in a CosmosDB container */ +@Immutable +public class CosmosNamespace { + private final String id; + + public CosmosNamespace() { + this.id = null; + } + + public CosmosNamespace(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CosmosNamespace)) { + return false; + } + CosmosNamespace that = (CosmosNamespace) o; + + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("id", id).toString(); + } +} diff --git a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java index 2407b7cec9..c3d4ce3fd8 100644 --- a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java @@ -15,6 +15,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -57,8 +58,8 @@ import software.amazon.awssdk.services.dynamodb.model.DescribeContinuousBackupsResponse; import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest; import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse; -import software.amazon.awssdk.services.dynamodb.model.DynamoDbException; import software.amazon.awssdk.services.dynamodb.model.GetItemRequest; +import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndex; import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndexDescription; import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndexUpdate; @@ -114,7 +115,10 @@ public class DynamoAdmin implements DistributedStorageAdmin { @VisibleForTesting static final String METADATA_ATTR_SECONDARY_INDEX = "secondaryIndex"; @VisibleForTesting static final String METADATA_ATTR_COLUMNS = "columns"; @VisibleForTesting static final String METADATA_ATTR_TABLE = "table"; - private static final long METADATA_TABLE_REQUEST_UNIT = 1; + @VisibleForTesting static final long METADATA_TABLES_REQUEST_UNIT = 1; + public static final String NAMESPACES_TABLE = "namespaces"; + + @VisibleForTesting static final String NAMESPACES_ATTR_NAME = "namespace_name"; private static final ImmutableMap SECONDARY_INDEX_DATATYPE_MAP = ImmutableMap.builder() @@ -169,7 +173,7 @@ public DynamoAdmin(DatabaseConfig databaseConfig) { applicationAutoScalingClient = createApplicationAutoScalingClient(config); metadataNamespace = config.getNamespacePrefix().orElse("") - + config.getTableMetadataNamespace().orElse(METADATA_NAMESPACE); + + config.getMetadataNamespace().orElse(METADATA_NAMESPACE); namespacePrefix = config.getNamespacePrefix().orElse(""); waitingDurationSecs = DEFAULT_WAITING_DURATION_SECS; } @@ -180,7 +184,7 @@ public DynamoAdmin(DatabaseConfig databaseConfig) { applicationAutoScalingClient = createApplicationAutoScalingClient(config); metadataNamespace = config.getNamespacePrefix().orElse("") - + config.getTableMetadataNamespace().orElse(METADATA_NAMESPACE); + + config.getMetadataNamespace().orElse(METADATA_NAMESPACE); namespacePrefix = config.getNamespacePrefix().orElse(""); waitingDurationSecs = DEFAULT_WAITING_DURATION_SECS; } @@ -194,7 +198,7 @@ public DynamoAdmin(DatabaseConfig databaseConfig) { this.applicationAutoScalingClient = applicationAutoScalingClient; metadataNamespace = config.getNamespacePrefix().orElse("") - + config.getTableMetadataNamespace().orElse(METADATA_NAMESPACE); + + config.getMetadataNamespace().orElse(METADATA_NAMESPACE); namespacePrefix = config.getNamespacePrefix().orElse(""); waitingDurationSecs = 0; } @@ -214,9 +218,31 @@ private ApplicationAutoScalingClient createApplicationAutoScalingClient(DynamoCo } @Override - public void createNamespace(String nonPrefixedNamespace, Map options) { - // In Dynamo DB storage, namespace will be added to table name as prefix along with dot - // separator. + public void createNamespace(String nonPrefixedNamespace, Map options) + throws ExecutionException { + Namespace namespace = Namespace.of(namespacePrefix, nonPrefixedNamespace); + try { + boolean noBackup = Boolean.parseBoolean(options.getOrDefault(NO_BACKUP, DEFAULT_NO_BACKUP)); + createNamespacesTableIfNotExists(noBackup); + insertIntoNamespacesTable(namespace); + } catch (ExecutionException e) { + throw new ExecutionException("Creating the namespace " + namespace + " failed", e); + } + } + + private void insertIntoNamespacesTable(Namespace namespace) throws ExecutionException { + Map itemValues = new HashMap<>(); + itemValues.put(NAMESPACES_ATTR_NAME, AttributeValue.builder().s(namespace.prefixed()).build()); + try { + client.putItem( + PutItemRequest.builder() + .tableName(ScalarDbUtils.getFullTableName(metadataNamespace, NAMESPACES_TABLE)) + .item(itemValues) + .build()); + } catch (Exception e) { + throw new ExecutionException( + "Inserting the namespace " + namespace + " into the namespaces table failed", e); + } } @Override @@ -450,8 +476,8 @@ private void createMetadataTableIfNotExists(boolean noBackup) throws ExecutionEx .build()) .provisionedThroughput( ProvisionedThroughput.builder() - .readCapacityUnits(METADATA_TABLE_REQUEST_UNIT) - .writeCapacityUnits(METADATA_TABLE_REQUEST_UNIT) + .readCapacityUnits(METADATA_TABLES_REQUEST_UNIT) + .writeCapacityUnits(METADATA_TABLES_REQUEST_UNIT) .build()) .tableName(ScalarDbUtils.getFullTableName(metadataNamespace, METADATA_TABLE)) .build()); @@ -466,18 +492,23 @@ private void createMetadataTableIfNotExists(boolean noBackup) throws ExecutionEx } private boolean metadataTableExists() throws ExecutionException { + return tableExistsInternal(Namespace.of(metadataNamespace), METADATA_TABLE); + } + + private boolean namespacesTableExists() throws ExecutionException { + return tableExistsInternal(Namespace.of(metadataNamespace), NAMESPACES_TABLE); + } + + private boolean tableExistsInternal(Namespace namespace, String table) throws ExecutionException { try { client.describeTable( - DescribeTableRequest.builder() - .tableName(ScalarDbUtils.getFullTableName(metadataNamespace, METADATA_TABLE)) - .build()); + DescribeTableRequest.builder().tableName(getFullTableName(namespace, table)).build()); return true; + } catch (ResourceNotFoundException e) { + return false; } catch (Exception e) { - if (e instanceof ResourceNotFoundException) { - return false; - } else { - throw new ExecutionException("Checking the metadata table existence failed", e); - } + throw new ExecutionException( + "Checking the table " + getFullTableName(namespace, table) + " existence failed", e); } } @@ -739,8 +770,43 @@ private void waitForTableDeletion(Namespace namespace, String tableName) } @Override - public void dropNamespace(String nonPrefixedNamespace) { - // Do nothing since DynamoDB does not support namespace + public void dropNamespace(String nonPrefixedNamespace) throws ExecutionException { + Namespace namespace = Namespace.of(namespacePrefix, nonPrefixedNamespace); + try { + deleteFromNamespacesTable(namespace); + dropNamespacesTableIfEmpty(); + } catch (Exception e) { + throw new ExecutionException( + "Dropping the namespace " + + Namespace.of(namespacePrefix, nonPrefixedNamespace) + + " failed", + e); + } + } + + private void dropNamespacesTableIfEmpty() throws ExecutionException { + String namespaceTableFullName = + ScalarDbUtils.getFullTableName(metadataNamespace, NAMESPACES_TABLE); + ScanResponse scanResponse = + client.scan(ScanRequest.builder().tableName(namespaceTableFullName).limit(1).build()); + if (scanResponse.count() == 0) { + client.deleteTable(DeleteTableRequest.builder().tableName(namespaceTableFullName).build()); + waitForTableDeletion(Namespace.of(metadataNamespace), NAMESPACES_TABLE); + } + } + + private void deleteFromNamespacesTable(Namespace namespace) throws ExecutionException { + Map keyToDelete = new HashMap<>(); + keyToDelete.put(NAMESPACES_ATTR_NAME, AttributeValue.builder().s(namespace.prefixed()).build()); + String namespacesTableFullName = + ScalarDbUtils.getFullTableName(metadataNamespace, NAMESPACES_TABLE); + try { + client.deleteItem( + DeleteItemRequest.builder().tableName(namespacesTableFullName).key(keyToDelete).build()); + } catch (Exception e) { + throw new ExecutionException( + "Deleting the namespace " + namespace + " from the namespaces table failed", e); + } } @Override @@ -1134,26 +1200,28 @@ public Set getNamespaceTableNames(String nonPrefixedNamespace) throws Ex @Override public boolean namespaceExists(String nonPrefixedNamespace) throws ExecutionException { try { - boolean namespaceExists = false; - String lastEvaluatedTableName = null; - do { - ListTablesRequest listTablesRequest = - ListTablesRequest.builder().exclusiveStartTableName(lastEvaluatedTableName).build(); - ListTablesResponse listTablesResponse = client.listTables(listTablesRequest); - lastEvaluatedTableName = listTablesResponse.lastEvaluatedTableName(); - List tableNames = listTablesResponse.tableNames(); - Namespace namespace = Namespace.of(namespacePrefix, nonPrefixedNamespace); - for (String tableName : tableNames) { - if (tableName.startsWith(namespace.prefixed() + ".")) { - namespaceExists = true; - break; - } - } - } while (lastEvaluatedTableName != null); - - return namespaceExists; - } catch (DynamoDbException e) { - throw new ExecutionException("Checking the namespace existence failed", e); + Map key = + ImmutableMap.of( + NAMESPACES_ATTR_NAME, + AttributeValue.builder() + .s(Namespace.of(namespacePrefix, nonPrefixedNamespace).prefixed()) + .build()); + GetItemResponse response = + client.getItem( + GetItemRequest.builder() + .tableName(ScalarDbUtils.getFullTableName(metadataNamespace, NAMESPACES_TABLE)) + .key(key) + .build()); + + return response.hasItem(); + } catch (ResourceNotFoundException e) { + return false; + } catch (Exception e) { + throw new ExecutionException( + "Checking the namespace " + + Namespace.of(namespacePrefix, nonPrefixedNamespace) + + " existence failed", + e); } } @@ -1219,6 +1287,73 @@ public void importTable(String namespace, String table) { "Import-related functionality is not supported in DynamoDB"); } + @Override + public Set getNamespaceNames() throws ExecutionException { + try { + Set namespaceNames = new HashSet<>(); + Map lastEvaluatedKey = null; + do { + ScanResponse scanResponse = + client.scan( + ScanRequest.builder() + .tableName(ScalarDbUtils.getFullTableName(metadataNamespace, NAMESPACES_TABLE)) + .exclusiveStartKey(lastEvaluatedKey) + .build()); + lastEvaluatedKey = scanResponse.lastEvaluatedKey(); + + for (Map namespace : scanResponse.items()) { + String prefixedNamespaceName = namespace.get(NAMESPACES_ATTR_NAME).s(); + String nonPrefixedNamespaceName = + prefixedNamespaceName.substring(namespacePrefix.length()); + namespaceNames.add(nonPrefixedNamespaceName); + } + } while (!lastEvaluatedKey.isEmpty()); + + return namespaceNames; + } catch (ResourceNotFoundException e) { + return Collections.emptySet(); + } catch (Exception e) { + throw new ExecutionException("Retrieving the existing namespace names failed", e); + } + } + + private void createNamespacesTableIfNotExists(boolean noBackup) throws ExecutionException { + if (namespacesTableExists()) { + return; + } + + List columnsToAttributeDefinitions = new ArrayList<>(); + columnsToAttributeDefinitions.add( + AttributeDefinition.builder() + .attributeName(NAMESPACES_ATTR_NAME) + .attributeType(ScalarAttributeType.S) + .build()); + try { + client.createTable( + CreateTableRequest.builder() + .attributeDefinitions(columnsToAttributeDefinitions) + .keySchema( + KeySchemaElement.builder() + .attributeName(NAMESPACES_ATTR_NAME) + .keyType(KeyType.HASH) + .build()) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(METADATA_TABLES_REQUEST_UNIT) + .writeCapacityUnits(METADATA_TABLES_REQUEST_UNIT) + .build()) + .tableName(ScalarDbUtils.getFullTableName(metadataNamespace, NAMESPACES_TABLE)) + .build()); + } catch (Exception e) { + throw new ExecutionException("Creating the namespaces table failed", e); + } + waitForTableCreation(Namespace.of(metadataNamespace), NAMESPACES_TABLE); + + if (!noBackup) { + enableContinuousBackup(Namespace.of(metadataNamespace), NAMESPACES_TABLE); + } + } + private String getFullTableName(Namespace namespace, String table) { return ScalarDbUtils.getFullTableName(namespace.prefixed(), table); } diff --git a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoConfig.java b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoConfig.java index 96231141e3..a1ef3f9afb 100644 --- a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoConfig.java +++ b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoConfig.java @@ -15,14 +15,18 @@ public class DynamoConfig { public static final String PREFIX = DatabaseConfig.PREFIX + "dynamo."; public static final String ENDPOINT_OVERRIDE = PREFIX + "endpoint_override"; + /** @deprecated As of 5.0, will be removed. Use {@link #METADATA_NAMESPACE} instead. */ + @Deprecated public static final String TABLE_METADATA_NAMESPACE = PREFIX + "table_metadata.namespace"; + + public static final String METADATA_NAMESPACE = PREFIX + "metadata.namespace"; public static final String NAMESPACE_PREFIX = PREFIX + "namespace.prefix"; private final String region; private final String accessKeyId; private final String secretAccessKey; @Nullable private final String endpointOverride; - @Nullable private final String tableMetadataNamespace; + @Nullable private final String metadataNamespace; @Nullable private final String namespacePrefix; public DynamoConfig(DatabaseConfig databaseConfig) { @@ -52,8 +56,23 @@ public DynamoConfig(DatabaseConfig databaseConfig) { databaseConfig.getProperties(), "scalar.db.dynamo.endpoint-override", // for backward compatibility null)); - tableMetadataNamespace = - getString(databaseConfig.getProperties(), TABLE_METADATA_NAMESPACE, null); + if (databaseConfig.getProperties().containsKey(METADATA_NAMESPACE) + && databaseConfig.getProperties().containsKey(TABLE_METADATA_NAMESPACE)) { + throw new IllegalArgumentException( + "Use either " + METADATA_NAMESPACE + " or " + TABLE_METADATA_NAMESPACE + " but not both"); + } + if (databaseConfig.getProperties().containsKey(TABLE_METADATA_NAMESPACE)) { + logger.warn( + "The configuration property \"" + + TABLE_METADATA_NAMESPACE + + "\" is deprecated and will be removed in 5.0.0. Please use \"" + + METADATA_NAMESPACE + + "\" instead"); + + metadataNamespace = getString(databaseConfig.getProperties(), TABLE_METADATA_NAMESPACE, null); + } else { + metadataNamespace = getString(databaseConfig.getProperties(), METADATA_NAMESPACE, null); + } namespacePrefix = getString(databaseConfig.getProperties(), NAMESPACE_PREFIX, null); } @@ -73,8 +92,8 @@ public Optional getEndpointOverride() { return Optional.ofNullable(endpointOverride); } - public Optional getTableMetadataNamespace() { - return Optional.ofNullable(tableMetadataNamespace); + public Optional getMetadataNamespace() { + return Optional.ofNullable(metadataNamespace); } public Optional getNamespacePrefix() { diff --git a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoProvider.java b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoProvider.java index c3b2249a19..76693f8648 100644 --- a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoProvider.java +++ b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoProvider.java @@ -20,6 +20,6 @@ public DistributedStorage createDistributedStorage(DatabaseConfig config) { @Override public DistributedStorageAdmin createDistributedStorageAdmin(DatabaseConfig config) { // Set the namespace check to false because DynamoDB does not support namespaces. - return new CheckedDistributedStorageAdmin(new DynamoAdmin(config), false); + return new CheckedDistributedStorageAdmin(new DynamoAdmin(config)); } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java index 4698f828bc..7534cbd496 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java @@ -55,6 +55,8 @@ public class JdbcAdmin implements DistributedStorageAdmin { @VisibleForTesting static final String JDBC_COL_TYPE_NAME = "TYPE_NAME"; @VisibleForTesting static final String JDBC_COL_COLUMN_SIZE = "COLUMN_SIZE"; @VisibleForTesting static final String JDBC_COL_DECIMAL_DIGITS = "DECIMAL_DIGITS"; + public static final String NAMESPACES_TABLE = "namespaces"; + @VisibleForTesting static final String NAMESPACE_COL_NAMESPACE_NAME = "namespace_name"; private static final Logger logger = LoggerFactory.getLogger(JdbcAdmin.class); private static final String INDEX_NAME_PREFIX = "index"; @@ -67,22 +69,27 @@ public JdbcAdmin(DatabaseConfig databaseConfig) { JdbcConfig config = new JdbcConfig(databaseConfig); rdbEngine = RdbEngineFactory.create(config); dataSource = JdbcUtils.initDataSourceForAdmin(config, rdbEngine); - metadataSchema = config.getTableMetadataSchema().orElse(METADATA_SCHEMA); + metadataSchema = config.getMetadataSchema().orElse(METADATA_SCHEMA); } @SuppressFBWarnings("EI_EXPOSE_REP2") public JdbcAdmin(BasicDataSource dataSource, JdbcConfig config) { rdbEngine = RdbEngineFactory.create(config); this.dataSource = dataSource; - metadataSchema = config.getTableMetadataSchema().orElse(METADATA_SCHEMA); + metadataSchema = config.getMetadataSchema().orElse(METADATA_SCHEMA); } @Override public void createNamespace(String namespace, Map options) throws ExecutionException { + if (!rdbEngine.isValidNamespaceOrTableName(namespace)) { + throw new ExecutionException("Namespace name is not acceptable: " + namespace); + } String fullNamespace = enclose(namespace); try (Connection connection = dataSource.getConnection()) { execute(connection, rdbEngine.createNamespaceSqls(fullNamespace)); + createNamespacesTableIfNotExists(connection); + insertIntoNamespacesTable(connection, namespace); } catch (SQLException e) { throw new ExecutionException("Creating the schema failed", e); } @@ -92,7 +99,7 @@ public void createNamespace(String namespace, Map options) public void createTable( String namespace, String table, TableMetadata metadata, Map options) throws ExecutionException { - if (!rdbEngine.isValidTableName(table)) { + if (!rdbEngine.isValidNamespaceOrTableName(table)) { throw new ExecutionException("Table name is not acceptable: " + table); } @@ -219,6 +226,11 @@ void createMetadataTableIfNotExists(Connection connection) throws SQLException { + enclose(METADATA_COL_COLUMN_NAME) + "))"; + createTableIfNotExists(connection, createTableStatement); + } + + private void createTableIfNotExists(Connection connection, String createTableStatement) + throws SQLException { String stmt = rdbEngine.tryAddIfNotExistsToCreateTableSql(createTableStatement); try { execute(connection, stmt); @@ -314,7 +326,7 @@ private void deleteTableMetadata(Connection connection, String namespace, String throws SQLException { try { execute(connection, getDeleteTableMetadataStatement(namespace, table)); - deleteMetadataSchemaAndTableIfEmpty(connection); + deleteMetadataTableIfEmpty(connection); } catch (SQLException e) { if (e.getMessage().contains("Unknown table") || e.getMessage().contains("does not exist")) { return; @@ -333,10 +345,9 @@ private String getDeleteTableMetadataStatement(String schema, String table) { + "'"; } - private void deleteMetadataSchemaAndTableIfEmpty(Connection connection) throws SQLException { + private void deleteMetadataTableIfEmpty(Connection connection) throws SQLException { if (isMetadataTableEmpty(connection)) { - deleteMetadataTable(connection); - deleteMetadataSchema(connection); + deleteTable(connection, encloseFullTableName(metadataSchema, METADATA_TABLE)); } } @@ -352,9 +363,8 @@ private boolean isMetadataTableEmpty(Connection connection) throws SQLException } } - private void deleteMetadataTable(Connection connection) throws SQLException { - String dropTableStatement = - "DROP TABLE " + encloseFullTableName(metadataSchema, METADATA_TABLE); + private void deleteTable(Connection connection, String fullTableName) throws SQLException { + String dropTableStatement = "DROP TABLE " + fullTableName; execute(connection, dropTableStatement); } @@ -368,6 +378,8 @@ private void deleteMetadataSchema(Connection connection) throws SQLException { public void dropNamespace(String namespace) throws ExecutionException { try (Connection connection = dataSource.getConnection()) { execute(connection, rdbEngine.dropNamespaceSql(namespace)); + deleteFromNamespacesTable(connection, namespace); + deleteNamespacesTableIfEmpty(connection); } catch (SQLException e) { rdbEngine.dropNamespaceTranslateSQLException(e, namespace); } @@ -389,10 +401,6 @@ public TableMetadata getTableMetadata(String namespace, String table) throws Exe TableMetadata.Builder builder = TableMetadata.newBuilder(); boolean tableExists = false; - if (!namespaceExists(metadataSchema)) { - return null; - } - try (Connection connection = dataSource.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement(getSelectColumnsStatement())) { @@ -431,6 +439,11 @@ public TableMetadata getTableMetadata(String namespace, String table) throws Exe } } } catch (SQLException e) { + // An exception will be thrown if the namespace table does not exist when executing the select + // query + if (rdbEngine.isUndefinedTableError(e)) { + return null; + } throw new ExecutionException("Getting a table metadata failed", e); } @@ -553,13 +566,24 @@ public Set getNamespaceTableNames(String namespace) throws ExecutionExce @Override public boolean namespaceExists(String namespace) throws ExecutionException { - String namespaceExistsStatement = rdbEngine.namespaceExistsStatement(); + String selectQuery = + "SELECT 1 FROM " + + encloseFullTableName(metadataSchema, NAMESPACES_TABLE) + + " WHERE " + + enclose(NAMESPACE_COL_NAMESPACE_NAME) + + " = ?"; try (Connection connection = dataSource.getConnection(); - PreparedStatement preparedStatement = - connection.prepareStatement(namespaceExistsStatement)) { - preparedStatement.setString(1, rdbEngine.namespaceExistsPlaceholder(namespace)); - return preparedStatement.executeQuery().next(); + PreparedStatement statement = connection.prepareStatement(selectQuery); ) { + statement.setString(1, namespace); + try (ResultSet resultSet = statement.executeQuery()) { + return resultSet.next(); + } } catch (SQLException e) { + // An exception will be thrown if the namespaces table does not exist when executing the + // select query + if (rdbEngine.isUndefinedTableError(e)) { + return false; + } throw new ExecutionException("Checking if the namespace exists failed", e); } } @@ -824,4 +848,87 @@ private String enclose(String name) { private String encloseFullTableName(String schema, String table) { return rdbEngine.encloseFullTableName(schema, table); } + + @Override + public Set getNamespaceNames() throws ExecutionException { + try (Connection connection = dataSource.getConnection()) { + String selectQuery = + "SELECT * FROM " + encloseFullTableName(metadataSchema, NAMESPACES_TABLE); + Set namespaces = new HashSet<>(); + try (PreparedStatement preparedStatement = connection.prepareStatement(selectQuery); + ResultSet resultSet = preparedStatement.executeQuery()) { + while (resultSet.next()) { + namespaces.add(resultSet.getString(NAMESPACE_COL_NAMESPACE_NAME)); + } + return namespaces; + } + } catch (SQLException e) { + // An exception will be thrown if the namespace table does not exist when executing the select + // query + if (rdbEngine.isUndefinedTableError(e)) { + return Collections.emptySet(); + } + throw new ExecutionException("Getting the namespace names failed", e); + } + } + + private void createNamespacesTableIfNotExists(Connection connection) throws ExecutionException { + try { + createMetadataSchemaIfNotExists(connection); + String createTableStatement = + "CREATE TABLE " + + encloseFullTableName(metadataSchema, NAMESPACES_TABLE) + + "(" + + enclose(NAMESPACE_COL_NAMESPACE_NAME) + + " " + + getTextType(128) + + ", " + + "PRIMARY KEY (" + + enclose(NAMESPACE_COL_NAMESPACE_NAME) + + "))"; + createTableIfNotExists(connection, createTableStatement); + } catch (SQLException e) { + throw new ExecutionException("Creating the namespace table failed", e); + } + } + + private void insertIntoNamespacesTable(Connection connection, String namespaceName) + throws SQLException { + String insertStatement = + "INSERT INTO " + encloseFullTableName(metadataSchema, NAMESPACES_TABLE) + " VALUES (?)"; + try (PreparedStatement preparedStatement = connection.prepareStatement(insertStatement); ) { + preparedStatement.setString(1, namespaceName); + preparedStatement.execute(); + } + } + + private void deleteFromNamespacesTable(Connection connection, String namespaceName) + throws SQLException { + String deleteStatement = + "DELETE FROM " + + encloseFullTableName(metadataSchema, NAMESPACES_TABLE) + + " WHERE " + + enclose(NAMESPACE_COL_NAMESPACE_NAME) + + " = ?"; + try (PreparedStatement preparedStatement = connection.prepareStatement(deleteStatement)) { + preparedStatement.setString(1, namespaceName); + preparedStatement.execute(); + } + } + + private void deleteNamespacesTableIfEmpty(Connection connection) throws SQLException { + if (isNamespacesTableEmpty(connection)) { + deleteTable(connection, encloseFullTableName(metadataSchema, NAMESPACES_TABLE)); + deleteMetadataSchema(connection); + } + } + + private boolean isNamespacesTableEmpty(Connection connection) throws SQLException { + String selectAllTables = + "SELECT * FROM " + encloseFullTableName(metadataSchema, NAMESPACES_TABLE); + try (PreparedStatement preparedStatement = connection.prepareStatement(selectAllTables); + ResultSet results = preparedStatement.executeQuery()) { + return !results.next(); + } + } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcConfig.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcConfig.java index 868051c539..8d8159311c 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcConfig.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcConfig.java @@ -9,9 +9,12 @@ import java.util.Optional; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @Immutable public class JdbcConfig { + private static final Logger logger = LoggerFactory.getLogger(JdbcConfig.class); public static final String PREFIX = DatabaseConfig.PREFIX + "jdbc."; public static final String CONNECTION_POOL_MIN_IDLE = PREFIX + "connection_pool.min_idle"; public static final String CONNECTION_POOL_MAX_IDLE = PREFIX + "connection_pool.max_idle"; @@ -22,8 +25,10 @@ public class JdbcConfig { PREFIX + "prepared_statements_pool.max_open"; public static final String ISOLATION_LEVEL = PREFIX + "isolation_level"; + /** @deprecated As of 5.0, will be removed. Use {@link #METADATA_SCHEMA} instead. */ + @Deprecated public static final String TABLE_METADATA_SCHEMA = PREFIX + "table_metadata.schema"; - public static final String TABLE_METADATA_SCHEMA = PREFIX + "table_metadata.schema"; + public static final String METADATA_SCHEMA = PREFIX + "metadata.schema"; public static final String TABLE_METADATA_CONNECTION_POOL_MIN_IDLE = PREFIX + "table_metadata.connection_pool.min_idle"; public static final String TABLE_METADATA_CONNECTION_POOL_MAX_IDLE = @@ -64,7 +69,7 @@ public class JdbcConfig { @Nullable private final Isolation isolation; - @Nullable private final String tableMetadataSchema; + @Nullable private final String metadataSchema; private final int tableMetadataConnectionPoolMinIdle; private final int tableMetadataConnectionPoolMaxIdle; private final int tableMetadataConnectionPoolMaxTotal; @@ -124,7 +129,6 @@ public JdbcConfig(DatabaseConfig databaseConfig) { isolation = null; } - tableMetadataSchema = getString(databaseConfig.getProperties(), TABLE_METADATA_SCHEMA, null); tableMetadataConnectionPoolMinIdle = getInt( databaseConfig.getProperties(), @@ -156,6 +160,24 @@ public JdbcConfig(DatabaseConfig databaseConfig) { databaseConfig.getProperties(), ADMIN_CONNECTION_POOL_MAX_TOTAL, DEFAULT_ADMIN_CONNECTION_POOL_MAX_TOTAL); + + if (databaseConfig.getProperties().containsKey(METADATA_SCHEMA) + && databaseConfig.getProperties().containsKey(TABLE_METADATA_SCHEMA)) { + throw new IllegalArgumentException( + "Use either " + METADATA_SCHEMA + " or " + TABLE_METADATA_SCHEMA + " but not both"); + } + if (databaseConfig.getProperties().containsKey(TABLE_METADATA_SCHEMA)) { + logger.warn( + "The configuration property \"" + + TABLE_METADATA_SCHEMA + + "\" is deprecated and will be removed in 5.0.0. Please use \"" + + METADATA_SCHEMA + + "\" instead"); + + metadataSchema = getString(databaseConfig.getProperties(), TABLE_METADATA_SCHEMA, null); + } else { + metadataSchema = getString(databaseConfig.getProperties(), METADATA_SCHEMA, null); + } } public String getJdbcUrl() { @@ -194,8 +216,8 @@ public Optional getIsolation() { return Optional.ofNullable(isolation); } - public Optional getTableMetadataSchema() { - return Optional.ofNullable(tableMetadataSchema); + public Optional getMetadataSchema() { + return Optional.ofNullable(metadataSchema); } public int getTableMetadataConnectionPoolMinIdle() { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcProvider.java b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcProvider.java index 11166d818d..9b563373c8 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/JdbcProvider.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/JdbcProvider.java @@ -19,9 +19,6 @@ public DistributedStorage createDistributedStorage(DatabaseConfig config) { @Override public DistributedStorageAdmin createDistributedStorageAdmin(DatabaseConfig config) { - // If the database is SQLite, the namespace check is skipped because SQLite does not support - // namespaces. - boolean isSqlite = JdbcUtils.isSqlite(new JdbcConfig(config)); - return new CheckedDistributedStorageAdmin(new JdbcAdmin(config), !isSqlite); + return new CheckedDistributedStorageAdmin(new JdbcAdmin(config)); } } diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java index 90cddbaeeb..f992de6dca 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java @@ -84,15 +84,6 @@ public void dropNamespaceTranslateSQLException(SQLException e, String namespace) throw new ExecutionException("Dropping the schema failed: " + namespace, e); } - @Override - public String namespaceExistsStatement() { - return "SELECT 1 FROM " - + encloseFullTableName("information_schema", "schemata") - + " WHERE " - + enclose("schema_name") - + " = ?"; - } - @Override public String alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java index 9fe0c96a89..ae26120915 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java @@ -104,11 +104,6 @@ public void dropNamespaceTranslateSQLException(SQLException e, String namespace) throw new ExecutionException("Dropping the user failed: " + namespace, e); } - @Override - public String namespaceExistsStatement() { - return "SELECT 1 FROM " + enclose("ALL_USERS") + " WHERE " + enclose("USERNAME") + " = ?"; - } - @Override public String alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java index b3d9b8fc74..37ad832b92 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java @@ -93,15 +93,6 @@ public void dropNamespaceTranslateSQLException(SQLException e, String namespace) throw new ExecutionException("Dropping the schema failed: " + namespace, e); } - @Override - public String namespaceExistsStatement() { - return "SELECT 1 FROM " - + encloseFullTableName("information_schema", "schemata") - + " WHERE " - + enclose("schema_name") - + " = ?"; - } - @Override public String alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java index 494b5efdc6..79121e269c 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java @@ -85,15 +85,6 @@ public void dropNamespaceTranslateSQLException(SQLException e, String namespace) throw new ExecutionException("Dropping the schema failed: " + namespace, e); } - @Override - public String namespaceExistsStatement() { - return "SELECT 1 FROM " - + encloseFullTableName("sys", "schemas") - + " WHERE " - + enclose("name") - + " = ?"; - } - @Override public String alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java index 9dea8cbc29..d0c3d0b5d5 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java @@ -132,8 +132,8 @@ public DataType getDataTypeForScalarDb( } @Override - public boolean isValidTableName(String tableName) { - return !tableName.contains(NAMESPACE_SEPARATOR); + public boolean isValidNamespaceOrTableName(String namespaceOrTableName) { + return !namespaceOrTableName.contains(NAMESPACE_SEPARATOR); } @Override @@ -211,20 +211,6 @@ public void dropNamespaceTranslateSQLException(SQLException e, String namespace) throw new AssertionError("DropNamespace never happen in SQLite implementation"); } - @Override - public String namespaceExistsStatement() { - return "SELECT 1 FROM sqlite_master WHERE " - + enclose("type") - + " = \"table\" AND " - + enclose("tbl_name") - + " LIKE ?"; - } - - @Override - public String namespaceExistsPlaceholder(String namespace) { - return namespace + NAMESPACE_SEPARATOR + "%"; - } - @Override public String alterColumnTypeSql( String namespace, String table, String columnName, String columnType) { diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java index 5cf4080929..f42ee7b0f7 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java @@ -38,7 +38,7 @@ DataType getDataTypeForScalarDb( String[] createNamespaceSqls(String fullNamespace); - default boolean isValidTableName(String tableName) { + default boolean isValidNamespaceOrTableName(String tableName) { return true; } @@ -65,12 +65,6 @@ default String truncateTableSql(String namespace, String table) { void dropNamespaceTranslateSQLException(SQLException e, String namespace) throws ExecutionException; - String namespaceExistsStatement(); - - default String namespaceExistsPlaceholder(String namespace) { - return namespace; - } - String alterColumnTypeSql(String namespace, String table, String columnName, String columnType); String tableExistsInternalTableCheckSql(String fullTableName); diff --git a/core/src/main/java/com/scalar/db/storage/multistorage/MultiStorageAdmin.java b/core/src/main/java/com/scalar/db/storage/multistorage/MultiStorageAdmin.java index 7afb592da9..55f5e70028 100644 --- a/core/src/main/java/com/scalar/db/storage/multistorage/MultiStorageAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/multistorage/MultiStorageAdmin.java @@ -1,6 +1,7 @@ package com.scalar.db.storage.multistorage; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import com.scalar.db.api.DistributedStorageAdmin; import com.scalar.db.api.TableMetadata; @@ -10,6 +11,7 @@ import com.scalar.db.service.StorageFactory; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -56,7 +58,8 @@ public MultiStorageAdmin(DatabaseConfig databaseConfig) { config .getNamespaceStorageMap() .forEach( - (table, storageName) -> namespaceAdminMap.put(table, nameAdminMap.get(storageName))); + (namespace, storageName) -> + namespaceAdminMap.put(namespace, nameAdminMap.get(storageName))); defaultAdmin = nameAdminMap.get(config.getDefaultStorage()); } @@ -69,7 +72,11 @@ public MultiStorageAdmin(DatabaseConfig databaseConfig) { this.tableAdminMap = tableAdminMap; this.namespaceAdminMap = namespaceAdminMap; this.defaultAdmin = defaultAdmin; - admins = null; + ImmutableSet.Builder adminsSet = ImmutableSet.builder(); + adminsSet.addAll(tableAdminMap.values()); + adminsSet.addAll(namespaceAdminMap.values()); + adminsSet.add(defaultAdmin); + this.admins = adminsSet.build().asList(); } @Override @@ -200,6 +207,48 @@ public void importTable(String namespace, String table) throws ExecutionExceptio getAdmin(namespace, table).importTable(namespace, table); } + @Override + public Set getNamespaceNames() throws ExecutionException { + // Only return existing namespaces that are listed in the namespace mapping configuration or + // when they belong to the default storage + // + // For example, if the storages contain the following namespaces : + // - mysql : mysqlStorageAdmin.getNamespaceNames() = [ns1, ns2] + // - cassandra : cassandraStorageAdmin.getNamespaceNames() = [ns3] + // - cosmos : cosmosStorageAdmin.getNamespaceNames() = [ns4, ns5] + // And the default storage is cosmos : + // - scalar.db.multi_storage.default_storage=cosmos + // And the namespace mapping set in the configuration is : + // - scalar.db.multi_storage.namespace_mapping=ns1:mysql,ns2:cassandra,ns3:cassandra + // + // Then multiStorageAdmin.getNamespaceNames() = [ns1, ns3, ns4, ns5] + // The reasoning is: + // - ns1 is present in the mysql storage and listed in the mapping belonging to mysql + // => returned + // - ns2 is present in the mysql storage but listed in the mapping belonging to cassandra + // => not returned + // - ns3 is present in the cassandra storage and listed in the mapping belonging to cassandra + // => returned + // - ns4 and ns5 are in the default storage (cosmos) + // => returned + Set namespaceNames = new HashSet<>(defaultAdmin.getNamespaceNames()); + + Set adminsWithoutDefaultAdmin = + new HashSet<>(namespaceAdminMap.values()); + adminsWithoutDefaultAdmin.remove(defaultAdmin); + for (DistributedStorageAdmin admin : adminsWithoutDefaultAdmin) { + Set existingNamespaces = admin.getNamespaceNames(); + // Filter out namespace not in the mapping + for (String existingNamespace : existingNamespaces) { + if (admin.equals(namespaceAdminMap.get(existingNamespace))) { + namespaceNames.add(existingNamespace); + } + } + } + + return namespaceNames; + } + private DistributedStorageAdmin getAdmin(String namespace) { DistributedStorageAdmin admin = namespaceAdminMap.get(namespace); return admin != null ? admin : defaultAdmin; diff --git a/core/src/main/java/com/scalar/db/storage/rpc/GrpcAdmin.java b/core/src/main/java/com/scalar/db/storage/rpc/GrpcAdmin.java index 2b559fe140..9040f2a500 100644 --- a/core/src/main/java/com/scalar/db/storage/rpc/GrpcAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/rpc/GrpcAdmin.java @@ -294,17 +294,14 @@ public void repairTable( throws ExecutionException { execute( () -> - execute( - () -> - stub.withDeadlineAfter( - config.getDeadlineDurationMillis(), TimeUnit.MILLISECONDS) - .repairTable( - RepairTableRequest.newBuilder() - .setNamespace(namespace) - .setTable(table) - .setTableMetadata(ProtoUtils.toTableMetadata(metadata)) - .putAllOptions(options) - .build()))); + stub.withDeadlineAfter(config.getDeadlineDurationMillis(), TimeUnit.MILLISECONDS) + .repairTable( + RepairTableRequest.newBuilder() + .setNamespace(namespace) + .setTable(table) + .setTableMetadata(ProtoUtils.toTableMetadata(metadata)) + .putAllOptions(options) + .build())); } @Override @@ -313,17 +310,20 @@ public void addNewColumnToTable( throws ExecutionException { execute( () -> - execute( - () -> - stub.withDeadlineAfter( - config.getDeadlineDurationMillis(), TimeUnit.MILLISECONDS) - .addNewColumnToTable( - AddNewColumnToTableRequest.newBuilder() - .setNamespace(namespace) - .setTable(table) - .setColumnName(columnName) - .setColumnType(ProtoUtils.toDataType(columnType)) - .build()))); + stub.withDeadlineAfter(config.getDeadlineDurationMillis(), TimeUnit.MILLISECONDS) + .addNewColumnToTable( + AddNewColumnToTableRequest.newBuilder() + .setNamespace(namespace) + .setTable(table) + .setColumnName(columnName) + .setColumnType(ProtoUtils.toDataType(columnType)) + .build())); + } + + @Override + public Set getNamespaceNames() throws ExecutionException { + throw new UnsupportedOperationException( + "Retrieving the namespace names is not supported in ScalarDB Server"); } @Override diff --git a/core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdmin.java b/core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdmin.java index d54d05ec59..c21aced542 100644 --- a/core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdmin.java +++ b/core/src/main/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdmin.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import javax.annotation.concurrent.ThreadSafe; @ThreadSafe @@ -192,6 +193,13 @@ public void addNewColumnToTable( admin.addNewColumnToTable(namespace, table, beforeColumnName, columnType); } + @Override + public Set getNamespaceNames() throws ExecutionException { + return admin.getNamespaceNames().stream() + .filter(namespace -> !namespace.equals(coordinatorNamespace)) + .collect(Collectors.toSet()); + } + @Override public void importTable(String namespace, String table) throws ExecutionException { TableMetadata tableMetadata = getTableMetadata(namespace, table); diff --git a/core/src/main/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdmin.java b/core/src/main/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdmin.java index 6fe69e8a6e..3c4221d150 100644 --- a/core/src/main/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdmin.java +++ b/core/src/main/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdmin.java @@ -10,8 +10,6 @@ import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; import com.scalar.db.storage.jdbc.JdbcAdmin; -import com.scalar.db.storage.jdbc.JdbcConfig; -import com.scalar.db.storage.jdbc.JdbcUtils; import java.util.Map; import java.util.Set; import javax.annotation.concurrent.ThreadSafe; @@ -23,10 +21,7 @@ public class JdbcTransactionAdmin implements DistributedTransactionAdmin { @Inject public JdbcTransactionAdmin(DatabaseConfig databaseConfig) { - // If the database is SQLite, the namespace check is skipped because SQLite does not support - // namespaces. - boolean isSqlite = JdbcUtils.isSqlite(new JdbcConfig(databaseConfig)); - jdbcAdmin = new CheckedDistributedStorageAdmin(new JdbcAdmin(databaseConfig), !isSqlite); + jdbcAdmin = new CheckedDistributedStorageAdmin(new JdbcAdmin(databaseConfig)); } @VisibleForTesting @@ -135,6 +130,11 @@ public void addNewColumnToTable( jdbcAdmin.addNewColumnToTable(namespace, table, columnName, columnType); } + @Override + public Set getNamespaceNames() throws ExecutionException { + return jdbcAdmin.getNamespaceNames(); + } + @Override public void close() { jdbcAdmin.close(); diff --git a/core/src/main/java/com/scalar/db/transaction/rpc/GrpcTransactionAdmin.java b/core/src/main/java/com/scalar/db/transaction/rpc/GrpcTransactionAdmin.java index 5bc02c659c..49aa1a480a 100644 --- a/core/src/main/java/com/scalar/db/transaction/rpc/GrpcTransactionAdmin.java +++ b/core/src/main/java/com/scalar/db/transaction/rpc/GrpcTransactionAdmin.java @@ -400,6 +400,12 @@ public void importTable(String namespace, String table) { "Import-related functionality is not supported in ScalarDB Server"); } + @Override + public Set getNamespaceNames() throws ExecutionException { + throw new UnsupportedOperationException( + "Retrieving the namespace names is not supported in ScalarDB Server"); + } + private static T execute(ThrowableSupplier supplier) throws ExecutionException { try { diff --git a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTestBase.java similarity index 68% rename from core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java rename to core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTestBase.java index 73e1689b0d..ab5d2f9c18 100644 --- a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTestBase.java @@ -1,11 +1,13 @@ package com.scalar.db.storage.cassandra; import static com.datastax.driver.core.Metadata.quote; +import static com.datastax.driver.core.Metadata.quoteIfNecessary; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -14,9 +16,12 @@ import com.datastax.driver.core.ColumnMetadata; import com.datastax.driver.core.KeyspaceMetadata; import com.datastax.driver.core.Metadata; +import com.datastax.driver.core.ResultSet; +import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.schemabuilder.Create.Options; +import com.datastax.driver.core.schemabuilder.CreateKeyspace; import com.datastax.driver.core.schemabuilder.KeyspaceOptions; import com.datastax.driver.core.schemabuilder.SchemaBuilder; import com.datastax.driver.core.schemabuilder.SchemaBuilder.Direction; @@ -26,16 +31,20 @@ import com.google.common.collect.ImmutableSet; import com.scalar.db.api.Scan.Ordering.Order; import com.scalar.db.api.TableMetadata; +import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; import com.scalar.db.storage.cassandra.CassandraAdmin.CompactionStrategy; import com.scalar.db.storage.cassandra.CassandraAdmin.ReplicationStrategy; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.Properties; import java.util.Set; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -44,20 +53,42 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; -public class CassandraAdminTest { +/** + * Abstraction that defines unit tests for the {@link CassandraAdmin}. The class purpose is to be + * able to run the {@link CassandraAdmin} unit tests with different values for the {@link + * CassandraAdmin}, notably {@link CassandraAdmin#METADATA_KEYSPACE}. + */ +public abstract class CassandraAdminTestBase { private CassandraAdmin cassandraAdmin; + private String metadataKeyspaceName; @Mock private ClusterManager clusterManager; @Mock private Session cassandraSession; @Mock private Cluster cluster; @Mock private Metadata metadata; @Mock private KeyspaceMetadata keyspaceMetadata; + /** + * This sets the {@link CassandraConfig#METADATA_KEYSPACE} value that will be used to run the + * tests. + * + * @return {@link CassandraConfig#METADATA_KEYSPACE} value + */ + abstract Optional getMetadataKeyspaceConfig(); + @BeforeEach public void setUp() throws Exception { MockitoAnnotations.openMocks(this).close(); when(clusterManager.getSession()).thenReturn(cassandraSession); - cassandraAdmin = new CassandraAdmin(clusterManager); + Properties cassandraConfigProperties = new Properties(); + getMetadataKeyspaceConfig() + .ifPresent( + metadataKeyspace -> + cassandraConfigProperties.setProperty( + CassandraConfig.METADATA_KEYSPACE, metadataKeyspace)); + cassandraAdmin = + new CassandraAdmin(clusterManager, new DatabaseConfig(cassandraConfigProperties)); + metadataKeyspaceName = getMetadataKeyspaceConfig().orElse(CassandraAdmin.METADATA_KEYSPACE); } @Test @@ -91,10 +122,11 @@ public void createNamespace_UseSimpleStrategy_ShouldExecuteCreateKeyspaceStateme throws ExecutionException { // Arrange String namespace = "sample_ns"; + String replicationFactor = "3"; Map options = new HashMap<>(); options.put( CassandraAdmin.REPLICATION_STRATEGY, ReplicationStrategy.SIMPLE_STRATEGY.toString()); - options.put(CassandraAdmin.REPLICATION_FACTOR, "3"); + options.put(CassandraAdmin.REPLICATION_FACTOR, replicationFactor); // Act cassandraAdmin.createNamespace(namespace, options); @@ -102,10 +134,13 @@ public void createNamespace_UseSimpleStrategy_ShouldExecuteCreateKeyspaceStateme // Assert Map replicationOptions = new LinkedHashMap<>(); replicationOptions.put("class", ReplicationStrategy.SIMPLE_STRATEGY.toString()); - replicationOptions.put("replication_factor", "3"); + replicationOptions.put("replication_factor", replicationFactor); KeyspaceOptions query = SchemaBuilder.createKeyspace(namespace).with().replication(replicationOptions); verify(cassandraSession).execute(query.getQueryString()); + verifyCreateMetadataKeyspaceQuery(replicationOptions); + verifyCreateKeyspacesTableQuery(); + verifyInsertIntoKeyspacesTableQuery(namespace); } @Test @@ -113,11 +148,14 @@ public void createNamespace_UseNetworkTopologyStrategy_ShouldExecuteCreateKeyspa throws ExecutionException { // Arrange String namespace = "sample_ns"; + String replicationFactor = "5"; + Map options = new HashMap<>(); options.put( CassandraAdmin.REPLICATION_STRATEGY, ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY.toString()); - options.put(CassandraAdmin.REPLICATION_FACTOR, "5"); + + options.put(CassandraAdmin.REPLICATION_FACTOR, replicationFactor); // Act cassandraAdmin.createNamespace(namespace, options); @@ -125,10 +163,13 @@ public void createNamespace_UseNetworkTopologyStrategy_ShouldExecuteCreateKeyspa // Assert Map replicationOptions = new LinkedHashMap<>(); replicationOptions.put("class", ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY.toString()); - replicationOptions.put("dc1", "5"); + replicationOptions.put("dc1", replicationFactor); KeyspaceOptions query = SchemaBuilder.createKeyspace(namespace).with().replication(replicationOptions); verify(cassandraSession).execute(query.getQueryString()); + verifyCreateMetadataKeyspaceQuery(replicationOptions); + verifyCreateKeyspacesTableQuery(); + verifyInsertIntoKeyspacesTableQuery(namespace); } @Test @@ -148,8 +189,10 @@ public void createNamespace_UseNetworkTopologyStrategy_ShouldExecuteCreateKeyspa replicationOptions.put("replication_factor", "1"); KeyspaceOptions query = SchemaBuilder.createKeyspace(namespace).with().replication(replicationOptions); - verify(cassandraSession).execute(query.getQueryString()); + verifyCreateMetadataKeyspaceQuery(replicationOptions); + verifyCreateKeyspacesTableQuery(); + verifyInsertIntoKeyspacesTableQuery(namespace); } @Test @@ -170,6 +213,38 @@ public void createNamespace_WithReservedKeywords_ShouldExecuteCreateKeyspaceStat SchemaBuilder.createKeyspace(quote(namespace)).with().replication(replicationOptions); verify(cassandraSession).execute(query.getQueryString()); + verifyCreateMetadataKeyspaceQuery(replicationOptions); + verifyCreateKeyspacesTableQuery(); + verifyInsertIntoKeyspacesTableQuery(namespace); + } + + private void verifyCreateMetadataKeyspaceQuery(Map replicationOptions) { + CreateKeyspace query = + SchemaBuilder.createKeyspace(quoteIfNecessary(metadataKeyspaceName)).ifNotExists(); + String queryString = query.with().replication(replicationOptions).getQueryString(); + verify(cassandraSession).execute(queryString); + } + + private void verifyCreateKeyspacesTableQuery() { + String query = + SchemaBuilder.createTable( + quoteIfNecessary(metadataKeyspaceName), + quoteIfNecessary(CassandraAdmin.NAMESPACES_TABLE)) + .ifNotExists() + .addPartitionKey( + CassandraAdmin.NAMESPACES_NAME_COL, com.datastax.driver.core.DataType.text()) + .getQueryString(); + verify(cassandraSession).execute(query); + } + + private void verifyInsertIntoKeyspacesTableQuery(String keyspace) { + String query = + QueryBuilder.insertInto( + quoteIfNecessary(metadataKeyspaceName), + quoteIfNecessary(CassandraAdmin.NAMESPACES_TABLE)) + .value(CassandraAdmin.NAMESPACES_NAME_COL, quoteIfNecessary(keyspace)) + .toString(); + verify(cassandraSession).execute(query); } @Test @@ -381,9 +456,34 @@ public void dropTable_WithReservedKeywords_ShouldDropTable() throws ExecutionExc } @Test - public void dropNamespace_WithCorrectParameters_ShouldDropKeyspace() throws ExecutionException { + public void + dropNamespace_WithCorrectParametersWithNoMoreKeyspacesLeft_ShouldDropKeyspaceAndMetadataKeyspace() + throws ExecutionException { + // Arrange + String namespace = "sample_ns"; + ResultSet selectQueryResult = mock(ResultSet.class); + when(selectQueryResult.one()).thenReturn(null); + when(cassandraSession.execute(anyString())).thenReturn(null, null, selectQueryResult, null); + + // Act + cassandraAdmin.dropNamespace(namespace); + + // Assert + String dropKeyspaceStatement = SchemaBuilder.dropKeyspace(namespace).getQueryString(); + verify(cassandraSession).execute(dropKeyspaceStatement); + verifyDeleteFromKeyspacesTableQuery(namespace); + verifySelectOneFromKeyspacesTableQuery(); + verifyDropMetadataKeyspaceQuery(); + } + + @Test + public void dropNamespace_WithCorrectParametersWithSomeKeyspacesLeft_ShouldOnlyDropKeyspace() + throws ExecutionException { // Arrange String namespace = "sample_ns"; + ResultSet selectQueryResult = mock(ResultSet.class); + when(selectQueryResult.one()).thenReturn(mock(Row.class)); + when(cassandraSession.execute(anyString())).thenReturn(null, null, selectQueryResult, null); // Act cassandraAdmin.dropNamespace(namespace); @@ -391,6 +491,8 @@ public void dropNamespace_WithCorrectParameters_ShouldDropKeyspace() throws Exec // Assert String dropKeyspaceStatement = SchemaBuilder.dropKeyspace(namespace).getQueryString(); verify(cassandraSession).execute(dropKeyspaceStatement); + verifyDeleteFromKeyspacesTableQuery(namespace); + verifySelectOneFromKeyspacesTableQuery(); } @Test @@ -398,6 +500,9 @@ public void dropNamespace_WithReservedKeywordNamespace_ShouldDropKeyspace() throws ExecutionException { // Arrange String namespace = "keyspace"; + ResultSet selectQueryResult = mock(ResultSet.class); + when(selectQueryResult.one()).thenReturn(null); + when(cassandraSession.execute(anyString())).thenReturn(null, null, selectQueryResult, null); // Act cassandraAdmin.dropNamespace(namespace); @@ -405,6 +510,37 @@ public void dropNamespace_WithReservedKeywordNamespace_ShouldDropKeyspace() // Assert String dropKeyspaceStatement = SchemaBuilder.dropKeyspace(quote(namespace)).getQueryString(); verify(cassandraSession).execute(dropKeyspaceStatement); + verifyDeleteFromKeyspacesTableQuery(namespace); + verifySelectOneFromKeyspacesTableQuery(); + verifyDropMetadataKeyspaceQuery(); + } + + private void verifyDeleteFromKeyspacesTableQuery(String keyspace) { + String query = + QueryBuilder.delete() + .from( + quoteIfNecessary(metadataKeyspaceName), + quoteIfNecessary(CassandraAdmin.NAMESPACES_TABLE)) + .where(QueryBuilder.eq(CassandraAdmin.NAMESPACES_NAME_COL, quoteIfNecessary(keyspace))) + .toString(); + verify(cassandraSession).execute(query); + } + + private void verifySelectOneFromKeyspacesTableQuery() { + String query = + QueryBuilder.select(CassandraAdmin.NAMESPACES_NAME_COL) + .from( + quoteIfNecessary(metadataKeyspaceName), + quoteIfNecessary(CassandraAdmin.NAMESPACES_TABLE)) + .limit(1) + .getQueryString(); + verify(cassandraSession).execute(query); + } + + private void verifyDropMetadataKeyspaceQuery() { + String query = + SchemaBuilder.dropKeyspace(quoteIfNecessary(metadataKeyspaceName)).getQueryString(); + verify(cassandraSession).execute(query); } @Test @@ -467,19 +603,65 @@ public void truncateTable_WithReservedKeywordsParameters_ShouldTruncateTable() public void namespaceExists_WithExistingNamespace_ShouldReturnTrue() throws ExecutionException { // Arrange String namespace = "sample_ns"; - Cluster cluster = mock(Cluster.class); - Metadata metadata = mock(Metadata.class); - KeyspaceMetadata keyspace = mock(KeyspaceMetadata.class); - - when(cassandraSession.getCluster()).thenReturn(cluster); - when(cluster.getMetadata()).thenReturn(metadata); - when(metadata.getKeyspace(any())).thenReturn(keyspace); + when(clusterManager.getMetadata(anyString(), anyString())) + .thenReturn(mock(com.datastax.driver.core.TableMetadata.class)); + ResultSet resultSet = mock(ResultSet.class); + when(cassandraSession.execute(anyString())).thenReturn(resultSet); + when(resultSet.one()).thenReturn(mock(Row.class)); // Act // Assert assertThat(cassandraAdmin.namespaceExists(namespace)).isTrue(); - verify(metadata).getKeyspace(namespace); + verify(clusterManager).getMetadata(metadataKeyspaceName, CassandraAdmin.NAMESPACES_TABLE); + String query = + QueryBuilder.select(CassandraAdmin.NAMESPACES_NAME_COL) + .from( + quoteIfNecessary(metadataKeyspaceName), + quoteIfNecessary(CassandraAdmin.NAMESPACES_TABLE)) + .where(QueryBuilder.eq(CassandraAdmin.NAMESPACES_NAME_COL, quoteIfNecessary(namespace))) + .toString(); + verify(cassandraSession).execute(query); + } + + @Test + public void namespaceExists_WithNonExistingNamespace_ShouldReturnFalse() + throws ExecutionException { + // Arrange + String namespace = "sample_ns"; + when(clusterManager.getMetadata(anyString(), anyString())) + .thenReturn(mock(com.datastax.driver.core.TableMetadata.class)); + ResultSet resultSet = mock(ResultSet.class); + when(cassandraSession.execute(anyString())).thenReturn(resultSet); + when(resultSet.one()).thenReturn(null); + + // Act + // Assert + assertThat(cassandraAdmin.namespaceExists(namespace)).isFalse(); + + verify(clusterManager).getMetadata(metadataKeyspaceName, CassandraAdmin.NAMESPACES_TABLE); + String query = + QueryBuilder.select(CassandraAdmin.NAMESPACES_NAME_COL) + .from( + quoteIfNecessary(metadataKeyspaceName), + quoteIfNecessary(CassandraAdmin.NAMESPACES_TABLE)) + .where(QueryBuilder.eq(CassandraAdmin.NAMESPACES_NAME_COL, quoteIfNecessary(namespace))) + .toString(); + verify(cassandraSession).execute(query); + } + + @Test + public void namespaceExists_WithNonExistingKeyspacesTable_ShouldReturnFalse() + throws ExecutionException { + // Arrange + String namespace = "sample_ns"; + when(clusterManager.getMetadata(anyString(), anyString())).thenReturn(null); + + // Act + // Assert + assertThat(cassandraAdmin.namespaceExists(namespace)).isFalse(); + + verify(clusterManager).getMetadata(metadataKeyspaceName, CassandraAdmin.NAMESPACES_TABLE); } @Test @@ -593,6 +775,49 @@ public void addNewColumnToTable_ShouldWorkProperly() throws ExecutionException { verify(cassandraSession).execute(alterTableQuery); } + @Test + public void getNamespacesNames_WithNonExistingKeyspaces_ShouldReturnEmptySet() + throws ExecutionException { + // Arrange + when(clusterManager.getMetadata(anyString(), anyString())).thenReturn(null); + + // Act + Set actualKeyspaceNames = cassandraAdmin.getNamespaceNames(); + + // Assert + assertThat(actualKeyspaceNames).isEmpty(); + verify(clusterManager).getMetadata(metadataKeyspaceName, CassandraAdmin.NAMESPACES_TABLE); + } + + @Test + public void getNamespacesNames_WithExistingKeyspaces_ShouldReturnKeyspacesNames() + throws ExecutionException { + // Arrange + when(clusterManager.getMetadata(anyString(), anyString())) + .thenReturn(mock(com.datastax.driver.core.TableMetadata.class)); + ResultSet resultSet = mock(ResultSet.class); + Row row1 = mock(Row.class); + Row row2 = mock(Row.class); + when(row1.getString(CassandraAdmin.NAMESPACES_NAME_COL)).thenReturn("ns1"); + when(row2.getString(CassandraAdmin.NAMESPACES_NAME_COL)).thenReturn("ns2"); + when(resultSet.all()).thenReturn(Arrays.asList(row1, row2)); + when(cassandraSession.execute(anyString())).thenReturn(resultSet); + + // Act + Set actualKeyspaceNames = cassandraAdmin.getNamespaceNames(); + + // Assert + assertThat(actualKeyspaceNames).containsOnly("ns1", "ns2"); + verify(clusterManager).getMetadata(metadataKeyspaceName, CassandraAdmin.NAMESPACES_TABLE); + String selectQuery = + QueryBuilder.select(CassandraAdmin.NAMESPACES_NAME_COL) + .from( + quoteIfNecessary(metadataKeyspaceName), + quoteIfNecessary(CassandraAdmin.NAMESPACES_TABLE)) + .getQueryString(); + verify(cassandraSession).execute(selectQuery); + } + @Test public void unsupportedOperations_ShouldThrowUnsupportedException() { // Arrange diff --git a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminWithDefaultConfigTest.java b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminWithDefaultConfigTest.java new file mode 100644 index 0000000000..c912436fc3 --- /dev/null +++ b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminWithDefaultConfigTest.java @@ -0,0 +1,11 @@ +package com.scalar.db.storage.cassandra; + +import java.util.Optional; + +public class CassandraAdminWithDefaultConfigTest extends CassandraAdminTestBase { + + @Override + Optional getMetadataKeyspaceConfig() { + return Optional.empty(); + } +} diff --git a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminWithMetadataKeyspaceConfigTest.java b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminWithMetadataKeyspaceConfigTest.java new file mode 100644 index 0000000000..522e13941d --- /dev/null +++ b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminWithMetadataKeyspaceConfigTest.java @@ -0,0 +1,11 @@ +package com.scalar.db.storage.cassandra; + +import java.util.Optional; + +public class CassandraAdminWithMetadataKeyspaceConfigTest extends CassandraAdminTestBase { + + @Override + Optional getMetadataKeyspaceConfig() { + return Optional.of("my_ks"); + } +} diff --git a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraConfigTest.java b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraConfigTest.java new file mode 100644 index 0000000000..f5c31f22ba --- /dev/null +++ b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraConfigTest.java @@ -0,0 +1,44 @@ +package com.scalar.db.storage.cassandra; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.scalar.db.config.DatabaseConfig; +import java.util.Properties; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockitoAnnotations; + +public class CassandraConfigTest { + private static final String ANY_METADATA_NAMESPACE = "any_namespace"; + + @BeforeEach + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this).close(); + } + + @Test + public void constructor_MetadataNamespaceGiven_ShouldLoadProperly() { + // Arrange + Properties props = new Properties(); + props.setProperty(CassandraConfig.METADATA_KEYSPACE, ANY_METADATA_NAMESPACE); + + // Act + CassandraConfig config = new CassandraConfig(new DatabaseConfig(props)); + + // Assert + assertThat(config.getMetadataKeyspace()).isPresent(); + assertThat(config.getMetadataKeyspace().get()).isEqualTo(ANY_METADATA_NAMESPACE); + } + + @Test + public void constructor_WithNoPropertiesGiven_ShouldLoadProperly() { + // Arrange + Properties props = new Properties(); + + // Act + CassandraConfig config = new CassandraConfig(new DatabaseConfig(props)); + + // Assert + assertThat(config.getMetadataKeyspace()).isEmpty(); + } +} diff --git a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTestBase.java b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTestBase.java index c55dcf315a..f8da8907e1 100644 --- a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTestBase.java +++ b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTestBase.java @@ -64,6 +64,7 @@ public abstract class CosmosAdminTestBase { @Mock private CosmosConfig config; @Mock private CosmosDatabase database; @Mock private CosmosContainer container; + @Mock private CosmosException notFoundException; private CosmosAdmin admin; private String metadataDatabaseName; @@ -72,10 +73,11 @@ public void setUp() throws Exception { MockitoAnnotations.openMocks(this).close(); // Arrange - when(config.getTableMetadataDatabase()).thenReturn(getTableMetadataDatabaseConfig()); + when(config.getMetadataDatabase()).thenReturn(getTableMetadataDatabaseConfig()); admin = new CosmosAdmin(client, config); metadataDatabaseName = getTableMetadataDatabaseConfig().orElse("scalardb"); + when(notFoundException.getStatusCode()).thenReturn(CosmosErrorCode.NOT_FOUND.get()); } /** @@ -97,7 +99,7 @@ public void getTableMetadata_ShouldReturnCorrectTableMetadata() throws Execution CosmosItemResponse response = mock(CosmosItemResponse.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(database); - when(database.getContainer(CosmosAdmin.METADATA_CONTAINER)).thenReturn(container); + when(database.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)).thenReturn(container); when(container.readItem( anyString(), any(PartitionKey.class), @@ -126,7 +128,7 @@ public void getTableMetadata_ShouldReturnCorrectTableMetadata() throws Execution .build()); verify(client).getDatabase(metadataDatabaseName); - verify(database).getContainer(CosmosAdmin.METADATA_CONTAINER); + verify(database).getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER); verify(container).readItem(fullName, new PartitionKey(fullName), CosmosTableMetadata.class); verify(response).getItem(); } @@ -137,6 +139,10 @@ public void createNamespace_WithCustomRuBelow4000_ShouldCreateDatabaseWithManual // Arrange String namespace = "ns"; String throughput = "2000"; + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(client.getDatabase(anyString())).thenReturn(metadataDatabase); + when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); // Act admin.createNamespace( @@ -147,6 +153,15 @@ public void createNamespace_WithCustomRuBelow4000_ShouldCreateDatabaseWithManual .createDatabase( eq(namespace), refEq(ThroughputProperties.createManualThroughput(Integer.parseInt(throughput)))); + verify(client) + .createDatabaseIfNotExists( + eq(metadataDatabaseName), + refEq( + ThroughputProperties.createManualThroughput( + Integer.parseInt(CosmosAdmin.DEFAULT_REQUEST_UNIT)))); + verify(client, times(2)).getDatabase(metadataDatabaseName); + verify(metadataDatabase).createContainerIfNotExists(CosmosAdmin.NAMESPACES_CONTAINER, "/id"); + verify(namespacesContainer).createItem(new CosmosNamespace(namespace)); } @Test @@ -155,6 +170,10 @@ public void createNamespace_WithCustomRuEqualTo4000_ShouldCreateDatabaseWithAuto // Arrange String namespace = "ns"; String throughput = "4000"; + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(client.getDatabase(anyString())).thenReturn(metadataDatabase); + when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); // Act admin.createNamespace( @@ -165,6 +184,15 @@ public void createNamespace_WithCustomRuEqualTo4000_ShouldCreateDatabaseWithAuto .createDatabase( eq(namespace), refEq(ThroughputProperties.createAutoscaledThroughput(Integer.parseInt(throughput)))); + verify(client) + .createDatabaseIfNotExists( + eq(metadataDatabaseName), + refEq( + ThroughputProperties.createManualThroughput( + Integer.parseInt(CosmosAdmin.DEFAULT_REQUEST_UNIT)))); + verify(client, times(2)).getDatabase(metadataDatabaseName); + verify(metadataDatabase).createContainerIfNotExists(CosmosAdmin.NAMESPACES_CONTAINER, "/id"); + verify(namespacesContainer).createItem(new CosmosNamespace(namespace)); } @Test @@ -175,6 +203,10 @@ public void createNamespace_WithCustomRuEqualTo4000_ShouldCreateDatabaseWithAuto String namespace = "ns"; String throughput = "4000"; String noScaling = "true"; + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(client.getDatabase(anyString())).thenReturn(metadataDatabase); + when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); // Act admin.createNamespace( @@ -186,6 +218,15 @@ public void createNamespace_WithCustomRuEqualTo4000_ShouldCreateDatabaseWithAuto .createDatabase( eq(namespace), refEq(ThroughputProperties.createManualThroughput(Integer.parseInt(throughput)))); + verify(client) + .createDatabaseIfNotExists( + eq(metadataDatabaseName), + refEq( + ThroughputProperties.createManualThroughput( + Integer.parseInt(CosmosAdmin.DEFAULT_REQUEST_UNIT)))); + verify(client, times(2)).getDatabase(metadataDatabaseName); + verify(metadataDatabase).createContainerIfNotExists(CosmosAdmin.NAMESPACES_CONTAINER, "/id"); + verify(namespacesContainer).createItem(new CosmosNamespace(namespace)); } @Test @@ -217,7 +258,7 @@ public void createTable_ShouldCreateContainer() throws ExecutionException { CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); CosmosContainer metadataContainer = mock(CosmosContainer.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); - when(metadataDatabase.getContainer(CosmosAdmin.METADATA_CONTAINER)) + when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); // Act @@ -260,7 +301,7 @@ public void createTable_ShouldCreateContainer() throws ExecutionException { refEq(ThroughputProperties.createManualThroughput(Integer.parseInt("400")))); verify(metadataDatabase).createContainerIfNotExists(containerPropertiesCaptor.capture()); assertThat(containerPropertiesCaptor.getValue().getId()) - .isEqualTo(CosmosAdmin.METADATA_CONTAINER); + .isEqualTo(CosmosAdmin.TABLE_METADATA_CONTAINER); assertThat(containerPropertiesCaptor.getValue().getPartitionKeyDefinition().getPaths()) .containsExactly("/id"); CosmosTableMetadata cosmosTableMetadata = @@ -312,7 +353,7 @@ public void createTable_WithoutClusteringKeys_ShouldCreateContainerWithComposite CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); CosmosContainer metadataContainer = mock(CosmosContainer.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); - when(metadataDatabase.getContainer(CosmosAdmin.METADATA_CONTAINER)) + when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); // Act @@ -344,7 +385,7 @@ public void createTable_WithoutClusteringKeys_ShouldCreateContainerWithComposite refEq(ThroughputProperties.createManualThroughput(Integer.parseInt("400")))); verify(metadataDatabase).createContainerIfNotExists(containerPropertiesCaptor.capture()); assertThat(containerPropertiesCaptor.getValue().getId()) - .isEqualTo(CosmosAdmin.METADATA_CONTAINER); + .isEqualTo(CosmosAdmin.TABLE_METADATA_CONTAINER); assertThat(containerPropertiesCaptor.getValue().getPartitionKeyDefinition().getPaths()) .containsExactly("/id"); CosmosTableMetadata cosmosTableMetadata = @@ -386,7 +427,7 @@ public void createTable_WithBlobClusteringKey_ShouldThrowIllegalArgumentExceptio } @Test - public void dropTable_WithNoMetadataLeft_ShouldDropContainerAndDeleteMetadataAndDatabase() + public void dropTable_WithNoMetadataLeft_ShouldDropContainerAndDeleteTableMetadataContainer() throws ExecutionException { // Arrange String namespace = "ns"; @@ -399,7 +440,7 @@ public void dropTable_WithNoMetadataLeft_ShouldDropContainerAndDeleteMetadataAnd CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); CosmosContainer metadataContainer = mock(CosmosContainer.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); - when(metadataDatabase.getContainer(CosmosAdmin.METADATA_CONTAINER)) + when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); @SuppressWarnings("unchecked") CosmosPagedIterable queryResults = mock(CosmosPagedIterable.class); @@ -415,13 +456,12 @@ public void dropTable_WithNoMetadataLeft_ShouldDropContainerAndDeleteMetadataAnd // for metadata table verify(client, atLeastOnce()).getDatabase(metadataDatabaseName); - verify(metadataDatabase, atLeastOnce()).getContainer(CosmosAdmin.METADATA_CONTAINER); + verify(metadataDatabase, atLeastOnce()).getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER); String fullTable = getFullTableName(namespace, table); verify(metadataContainer) .deleteItem( eq(fullTable), eq(new PartitionKey(fullTable)), refEq(new CosmosItemRequestOptions())); verify(metadataContainer).delete(); - verify(metadataDatabase).delete(); } public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata() @@ -437,7 +477,7 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); CosmosContainer metadataContainer = mock(CosmosContainer.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); - when(metadataDatabase.getContainer(CosmosAdmin.METADATA_CONTAINER)) + when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); @SuppressWarnings("unchecked") CosmosPagedIterable queryResults = mock(CosmosPagedIterable.class); @@ -453,7 +493,7 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( // for metadata table verify(client, atLeastOnce()).getDatabase(metadataDatabaseName); - verify(metadataDatabase, atLeastOnce()).getContainer(CosmosAdmin.METADATA_CONTAINER); + verify(metadataDatabase, atLeastOnce()).getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER); String fullTable = getFullTableName(namespace, table); verify(metadataContainer) .deleteItem( @@ -463,16 +503,57 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( } @Test - public void dropNamespace_WithExistingDatabase_ShouldDropDatabase() throws ExecutionException { + public void + dropNamespace_WithExistingDatabaseAndNoMoreNamespaceLeft_ShouldDropDatabaseAndMetadataNamespace() + throws ExecutionException { // Arrange String namespace = "ns"; - when(client.getDatabase(any())).thenReturn(database); + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + when(client.getDatabase(any())).thenReturn(database, metadataDatabase); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); + + CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); + when(namespacesContainer.queryItems(anyString(), any(), any())).thenReturn(pagedIterable); + when(pagedIterable.stream()).thenReturn(Stream.empty()); // Act admin.dropNamespace(namespace); // Assert + verify(client).getDatabase(namespace); verify(database).delete(); + verify(client, times(2)).getDatabase(metadataDatabaseName); + verify(metadataDatabase).getContainer(CosmosAdmin.NAMESPACES_CONTAINER); + verify(namespacesContainer) + .deleteItem(eq(new CosmosNamespace(namespace)), refEq(new CosmosItemRequestOptions())); + verify(metadataDatabase).delete(); + } + + @Test + public void dropNamespace_WithExistingDatabaseAndSomeNamespacesLeft_ShouldDropDatabase() + throws ExecutionException { + // Arrange + String namespace = "ns"; + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + when(client.getDatabase(any())).thenReturn(database, metadataDatabase); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); + + CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); + when(namespacesContainer.queryItems(anyString(), any(), any())).thenReturn(pagedIterable); + when(pagedIterable.stream()).thenReturn(Stream.of(mock(Object.class))); + + // Act + admin.dropNamespace(namespace); + + // Assert + verify(client).getDatabase(namespace); + verify(database).delete(); + verify(client).getDatabase(metadataDatabaseName); + verify(metadataDatabase).getContainer(CosmosAdmin.NAMESPACES_CONTAINER); + verify(namespacesContainer) + .deleteItem(eq(new CosmosNamespace(namespace)), refEq(new CosmosItemRequestOptions())); } @Test @@ -526,7 +607,7 @@ public void getNamespaceTableNames_ShouldGetTableNamesProperly() throws Executio CosmosTableMetadata.newBuilder().id(getFullTableName(namespace, "t2")).build(); when(client.getDatabase(metadataDatabaseName)).thenReturn(database); - when(database.getContainer(CosmosAdmin.METADATA_CONTAINER)).thenReturn(container); + when(database.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)).thenReturn(container); @SuppressWarnings("unchecked") CosmosPagedIterable queryResults = mock(CosmosPagedIterable.class); when(container.queryItems(anyString(), any(), eq(CosmosTableMetadata.class))) @@ -539,7 +620,7 @@ public void getNamespaceTableNames_ShouldGetTableNamesProperly() throws Executio // Assert assertThat(actualTableNames).containsExactly("t1", "t2"); verify(client, atLeastOnce()).getDatabase(metadataDatabaseName); - verify(database, atLeastOnce()).getContainer(CosmosAdmin.METADATA_CONTAINER); + verify(database, atLeastOnce()).getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER); verify(container) .queryItems( eq("SELECT * FROM metadata WHERE metadata.id LIKE 'ns.%'"), @@ -565,7 +646,7 @@ public void createIndex_ShouldCreateIndexProperly() throws ExecutionException { CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); CosmosContainer metadataContainer = mock(CosmosContainer.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); - when(metadataDatabase.getContainer(CosmosAdmin.METADATA_CONTAINER)) + when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); @SuppressWarnings("unchecked") CosmosItemResponse itemResponse = mock(CosmosItemResponse.class); @@ -634,7 +715,7 @@ public void dropIndex_ShouldDropIndexProperly() throws ExecutionException { CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); CosmosContainer metadataContainer = mock(CosmosContainer.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); - when(metadataDatabase.getContainer(CosmosAdmin.METADATA_CONTAINER)) + when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); @SuppressWarnings("unchecked") CosmosItemResponse itemResponse = mock(CosmosItemResponse.class); @@ -726,7 +807,7 @@ public void repairTable_withStoredProcedure_ShouldUpsertMetadata() throws Execut CosmosContainer metadataContainer = mock(CosmosContainer.class); CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); - when(metadataDatabase.getContainer(CosmosAdmin.METADATA_CONTAINER)) + when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); CosmosScripts scripts = mock(CosmosScripts.class); @@ -774,7 +855,7 @@ public void repairTable_withoutStoredProcedure_ShouldUpsertMetadataAndCreateProc CosmosContainer metadataContainer = mock(CosmosContainer.class); CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); - when(metadataDatabase.getContainer(CosmosAdmin.METADATA_CONTAINER)) + when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); // Missing stored procedure @@ -812,7 +893,7 @@ public void addNewColumnToTable_ShouldWorkProperly() throws ExecutionException { CosmosItemResponse response = mock(CosmosItemResponse.class); when(client.getDatabase(metadataDatabaseName)).thenReturn(database); - when(database.getContainer(CosmosAdmin.METADATA_CONTAINER)).thenReturn(container); + when(database.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)).thenReturn(container); when(container.readItem( anyString(), any(PartitionKey.class), @@ -861,4 +942,73 @@ public void unsupportedOperations_ShouldThrowUnsupportedException() { assertThat(thrown2).isInstanceOf(UnsupportedOperationException.class); assertThat(thrown3).isInstanceOf(UnsupportedOperationException.class); } + + @Test + public void getNamespaceNames_NonExistingNamespacesContainer_ShouldReturnEmptySet() + throws ExecutionException { + // Arrange + when(client.getDatabase(anyString())).thenThrow(notFoundException); + + // Act + Set actualNamespaces = admin.getNamespaceNames(); + + // Assert + assertThat(actualNamespaces).isEmpty(); + verify(client).getDatabase(metadataDatabaseName); + } + + @Test + public void getNamespaceNames_ShouldWorkProperly() throws ExecutionException { + // Arrange + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(client.getDatabase(anyString())).thenReturn(metadataDatabase); + when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); + CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); + when(namespacesContainer.queryItems(anyString(), any(), eq(CosmosNamespace.class))) + .thenReturn(pagedIterable); + when(pagedIterable.stream()) + .thenReturn(Stream.of(new CosmosNamespace("ns1"), new CosmosNamespace("ns2"))); + + // Act + Set actualNamespaces = admin.getNamespaceNames(); + + // Assert + verify(client, times(2)).getDatabase(metadataDatabaseName); + verify(metadataDatabase, times(2)).getContainer(CosmosAdmin.NAMESPACES_CONTAINER); + verify(namespacesContainer) + .queryItems( + eq("SELECT * FROM container"), + refEq(new CosmosQueryRequestOptions()), + eq(CosmosNamespace.class)); + assertThat(actualNamespaces).containsOnly("ns1", "ns2"); + } + + @Test + public void namespaceExists_WithExistingNamespace_ShouldReturnTrue() throws ExecutionException { + // Arrange + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(client.getDatabase(anyString())).thenReturn(metadataDatabase); + when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); + + // Act Assert + assertThat(admin.namespaceExists("ns")).isTrue(); + + verify(client).getDatabase(metadataDatabaseName); + verify(metadataDatabase).getContainer(CosmosAdmin.NAMESPACES_CONTAINER); + verify(namespacesContainer).readItem("ns", new PartitionKey("ns"), CosmosNamespace.class); + } + + @Test + public void namespaceExists_WithNonExistingMetadataDatabase_ShouldReturnFalse() + throws ExecutionException { + // Arrange + when(client.getDatabase(anyString())).thenThrow(notFoundException); + + // Act Assert + assertThat(admin.namespaceExists("ns")).isFalse(); + + verify(client).getDatabase(metadataDatabaseName); + } } diff --git a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTableMetadataDatabaseConfigTest.java b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminWithMetadataDatabaseConfigTest.java similarity index 67% rename from core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTableMetadataDatabaseConfigTest.java rename to core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminWithMetadataDatabaseConfigTest.java index 34b5f3a9f5..759f82af91 100644 --- a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTableMetadataDatabaseConfigTest.java +++ b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminWithMetadataDatabaseConfigTest.java @@ -2,7 +2,7 @@ import java.util.Optional; -public class CosmosAdminTableMetadataDatabaseConfigTest extends CosmosAdminTestBase { +public class CosmosAdminWithMetadataDatabaseConfigTest extends CosmosAdminTestBase { @Override Optional getTableMetadataDatabaseConfig() { return Optional.of("my_meta_ns"); diff --git a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosConfigTest.java b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosConfigTest.java index 152defa945..4c7911d0bd 100644 --- a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosConfigTest.java +++ b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosConfigTest.java @@ -21,7 +21,7 @@ public void constructor_AllPropertiesGiven_ShouldLoadProperly() { props.setProperty(DatabaseConfig.CONTACT_POINTS, ANY_ENDPOINT); props.setProperty(DatabaseConfig.PASSWORD, ANY_KEY); props.setProperty(DatabaseConfig.STORAGE, COSMOS_STORAGE); - props.setProperty(CosmosConfig.TABLE_METADATA_DATABASE, ANY_TABLE_METADATA_DATABASE); + props.setProperty(CosmosConfig.METADATA_DATABASE, ANY_TABLE_METADATA_DATABASE); // Act CosmosConfig config = new CosmosConfig(new DatabaseConfig(props)); @@ -29,8 +29,8 @@ public void constructor_AllPropertiesGiven_ShouldLoadProperly() { // Assert assertThat(config.getEndpoint()).isEqualTo(ANY_ENDPOINT); assertThat(config.getKey()).isEqualTo(ANY_KEY); - assertThat(config.getTableMetadataDatabase()).isPresent(); - assertThat(config.getTableMetadataDatabase().get()).isEqualTo(ANY_TABLE_METADATA_DATABASE); + assertThat(config.getMetadataDatabase()).isPresent(); + assertThat(config.getMetadataDatabase().get()).isEqualTo(ANY_TABLE_METADATA_DATABASE); } @Test @@ -39,7 +39,7 @@ public void constructor_WithoutStorage_ShouldThrowIllegalArgumentException() { Properties props = new Properties(); props.setProperty(DatabaseConfig.CONTACT_POINTS, ANY_ENDPOINT); props.setProperty(DatabaseConfig.PASSWORD, ANY_KEY); - props.setProperty(CosmosConfig.TABLE_METADATA_DATABASE, ANY_TABLE_METADATA_DATABASE); + props.setProperty(CosmosConfig.METADATA_DATABASE, ANY_TABLE_METADATA_DATABASE); // Act Assert assertThatThrownBy(() -> new CosmosConfig(new DatabaseConfig(props))) @@ -60,7 +60,42 @@ public void constructor_WithoutTableMetadataDatabase_ShouldLoadProperly() { // Assert assertThat(config.getEndpoint()).isEqualTo(ANY_ENDPOINT); assertThat(config.getKey()).isEqualTo(ANY_KEY); - assertThat(config.getTableMetadataDatabase()).isNotPresent(); + assertThat(config.getMetadataDatabase()).isNotPresent(); + } + + @Test + public void + constructor_WithTableMetadataDatabaseAndMetadataDatabaseGiven_ShouldThrowIllegalArgumentException() { + // Arrange + Properties props = new Properties(); + props.setProperty(DatabaseConfig.CONTACT_POINTS, ANY_ENDPOINT); + props.setProperty(DatabaseConfig.PASSWORD, ANY_KEY); + props.setProperty(DatabaseConfig.STORAGE, COSMOS_STORAGE); + props.setProperty(CosmosConfig.METADATA_DATABASE, ANY_TABLE_METADATA_DATABASE); + props.setProperty(CosmosConfig.TABLE_METADATA_DATABASE, ANY_TABLE_METADATA_DATABASE); + + // Act Assert + assertThatThrownBy(() -> new CosmosConfig(new DatabaseConfig(props))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructor_WithTableMetadataDatabaseGiven_ShouldLoadProperly() { + // Arrange + Properties props = new Properties(); + props.setProperty(DatabaseConfig.CONTACT_POINTS, ANY_ENDPOINT); + props.setProperty(DatabaseConfig.PASSWORD, ANY_KEY); + props.setProperty(DatabaseConfig.STORAGE, COSMOS_STORAGE); + props.setProperty(CosmosConfig.TABLE_METADATA_DATABASE, ANY_TABLE_METADATA_DATABASE); + + // Act + CosmosConfig config = new CosmosConfig(new DatabaseConfig(props)); + + // Assert + assertThat(config.getEndpoint()).isEqualTo(ANY_ENDPOINT); + assertThat(config.getKey()).isEqualTo(ANY_KEY); + assertThat(config.getMetadataDatabase()).isPresent(); + assertThat(config.getMetadataDatabase().get()).isEqualTo(ANY_TABLE_METADATA_DATABASE); } @Test diff --git a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java index d8dca58715..8fb863b424 100644 --- a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java +++ b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java @@ -49,10 +49,13 @@ import software.amazon.awssdk.services.dynamodb.model.GetItemResponse; import software.amazon.awssdk.services.dynamodb.model.GlobalSecondaryIndexDescription; import software.amazon.awssdk.services.dynamodb.model.IndexStatus; +import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement; import software.amazon.awssdk.services.dynamodb.model.KeyType; import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest; import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse; +import software.amazon.awssdk.services.dynamodb.model.PointInTimeRecoverySpecification; import software.amazon.awssdk.services.dynamodb.model.ProjectionType; +import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException; import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType; @@ -66,7 +69,7 @@ /** * Abstraction that defines unit tests for the {@link DynamoAdmin}. The class purpose is to be able * to run the {@link DynamoAdmin} unit tests with different values for the {@link DynamoConfig}, - * notably {@link DynamoConfig#NAMESPACE_PREFIX} and {@link DynamoConfig#TABLE_METADATA_NAMESPACE} + * notably {@link DynamoConfig#NAMESPACE_PREFIX} and {@link DynamoConfig#METADATA_NAMESPACE} */ public abstract class DynamoAdminTestBase { @@ -76,24 +79,38 @@ public abstract class DynamoAdminTestBase { @Mock private DynamoConfig config; @Mock private DynamoDbClient client; @Mock private ApplicationAutoScalingClient applicationAutoScalingClient; + @Mock private DescribeTableResponse tableIsActiveResponse; + @Mock private DescribeContinuousBackupsResponse backupIsEnabledResponse; private DynamoAdmin admin; @BeforeEach public void setUp() throws Exception { MockitoAnnotations.openMocks(this).close(); when(config.getNamespacePrefix()).thenReturn(getNamespacePrefixConfig()); - when(config.getTableMetadataNamespace()).thenReturn(getTableMetadataNamespaceConfig()); + when(config.getMetadataNamespace()).thenReturn(getMetadataNamespaceConfig()); + + // prepare tableIsActiveResponse + TableDescription tableDescription = mock(TableDescription.class); + when(tableIsActiveResponse.table()).thenReturn(tableDescription); + when(tableDescription.tableStatus()).thenReturn(TableStatus.ACTIVE); + + // prepare backupIsEnabledResponse + ContinuousBackupsDescription continuousBackupsDescription = + mock(ContinuousBackupsDescription.class); + when(backupIsEnabledResponse.continuousBackupsDescription()) + .thenReturn(continuousBackupsDescription); + when(continuousBackupsDescription.continuousBackupsStatus()) + .thenReturn(ContinuousBackupsStatus.ENABLED); admin = new DynamoAdmin(client, applicationAutoScalingClient, config); } /** - * This sets the {@link DynamoConfig#TABLE_METADATA_NAMESPACE} value that will be used to run the - * tests + * This sets the {@link DynamoConfig#METADATA_NAMESPACE} value that will be used to run the tests * - * @return {@link DynamoConfig#TABLE_METADATA_NAMESPACE} value + * @return {@link DynamoConfig#METADATA_NAMESPACE} value */ - abstract Optional getTableMetadataNamespaceConfig(); + abstract Optional getMetadataNamespaceConfig(); /** * This sets the {@link DynamoConfig#NAMESPACE_PREFIX} value that will be used to run the tests @@ -112,11 +129,18 @@ private String getPrefixedNamespace() { private String getFullMetadataTableName() { return getNamespacePrefixConfig().orElse("") - + getTableMetadataNamespaceConfig().orElse(DynamoAdmin.METADATA_NAMESPACE) + + getMetadataNamespaceConfig().orElse(DynamoAdmin.METADATA_NAMESPACE) + "." + DynamoAdmin.METADATA_TABLE; } + private String getFullNamespaceTableName() { + return getNamespacePrefixConfig().orElse("") + + getMetadataNamespaceConfig().orElse(DynamoAdmin.METADATA_NAMESPACE) + + "." + + DynamoAdmin.NAMESPACES_TABLE; + } + @Test public void getTableMetadata_ShouldReturnCorrectTableMetadata() throws ExecutionException { // Arrange @@ -198,21 +222,60 @@ public void getTableMetadata_ShouldReturnCorrectTableMetadata() throws Execution assertThat(actualRequest.consistentRead()).isTrue(); } - // https://github.com/scalar-labs/scalardb/issues/784 @Test - public void namespaceExists_ShouldPerformExactMatch() throws ExecutionException { + public void dropNamespace_WithOtherNamespacesExisting_ShouldDropNamespace() + throws ExecutionException { + // Arrange + ScanResponse scanResponse = mock(ScanResponse.class); + when(scanResponse.count()).thenReturn(1); + when(client.scan(any(ScanRequest.class))).thenReturn(scanResponse); + + // Act + admin.dropNamespace(NAMESPACE); + // Assert + verify(client) + .deleteItem( + DeleteItemRequest.builder() + .tableName(getFullNamespaceTableName()) + .key( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build())) + .build()); + verify(client) + .scan(ScanRequest.builder().tableName(getFullNamespaceTableName()).limit(1).build()); + } + + @Test + public void dropNamespace_WithNoNamespacesLeft_ShouldDropNamespaceAndNamespacesTable() + throws ExecutionException { // Arrange ListTablesResponse listTablesResponse = mock(ListTablesResponse.class); when(client.listTables(any(ListTablesRequest.class))).thenReturn(listTablesResponse); when(listTablesResponse.lastEvaluatedTableName()).thenReturn(null); - when(listTablesResponse.tableNames()) - .thenReturn(ImmutableList.builder().add(getFullTableName()).build()); + when(listTablesResponse.tableNames()).thenReturn(Collections.emptyList()); + ScanResponse scanResponse = mock(ScanResponse.class); + when(scanResponse.count()).thenReturn(0); + when(client.scan(any(ScanRequest.class))).thenReturn(scanResponse); // Act + admin.dropNamespace(NAMESPACE); + // Assert - assertThat(admin.namespaceExists(NAMESPACE)).isTrue(); - // compare with namespace prefix - assertThat(admin.namespaceExists(NAMESPACE.substring(0, NAMESPACE.length() - 1))).isFalse(); + verify(client) + .deleteItem( + DeleteItemRequest.builder() + .tableName(getFullNamespaceTableName()) + .key( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build())) + .build()); + + verify(client) + .scan(ScanRequest.builder().tableName(getFullNamespaceTableName()).limit(1).build()); + verify(client) + .deleteTable(DeleteTableRequest.builder().tableName(getFullNamespaceTableName()).build()); } @Test @@ -234,33 +297,17 @@ public void createTable_WhenMetadataTableNotExist_ShouldCreateTableAndMetadataTa .addSecondaryIndex("c4") .build(); - DescribeTableResponse describeTableResponse = mock(DescribeTableResponse.class); when(client.describeTable(DescribeTableRequest.builder().tableName(getFullTableName()).build())) - .thenReturn(describeTableResponse); - TableDescription tableDescription = mock(TableDescription.class); - when(describeTableResponse.table()).thenReturn(tableDescription); - when(tableDescription.tableStatus()).thenReturn(TableStatus.ACTIVE); + .thenReturn(tableIsActiveResponse); - DescribeContinuousBackupsResponse describeContinuousBackupsResponse = - mock(DescribeContinuousBackupsResponse.class); when(client.describeContinuousBackups(any(DescribeContinuousBackupsRequest.class))) - .thenReturn(describeContinuousBackupsResponse); - ContinuousBackupsDescription continuousBackupsDescription = - mock(ContinuousBackupsDescription.class); - when(describeContinuousBackupsResponse.continuousBackupsDescription()) - .thenReturn(continuousBackupsDescription); - when(continuousBackupsDescription.continuousBackupsStatus()) - .thenReturn(ContinuousBackupsStatus.ENABLED); + .thenReturn(backupIsEnabledResponse); // for the table metadata table - describeTableResponse = mock(DescribeTableResponse.class); - tableDescription = mock(TableDescription.class); - when(describeTableResponse.table()).thenReturn(tableDescription); - when(tableDescription.tableStatus()).thenReturn(TableStatus.ACTIVE); when(client.describeTable( DescribeTableRequest.builder().tableName(getFullMetadataTableName()).build())) .thenThrow(ResourceNotFoundException.class) - .thenReturn(describeTableResponse); + .thenReturn(tableIsActiveResponse); // Act admin.createTable(NAMESPACE, TABLE, metadata); @@ -438,22 +485,10 @@ public void createTable_WhenMetadataTableExists_ShouldCreateOnlyTable() .addSecondaryIndex("c4") .build(); - DescribeTableResponse describeTableResponse = mock(DescribeTableResponse.class); - when(client.describeTable(any(DescribeTableRequest.class))).thenReturn(describeTableResponse); - TableDescription tableDescription = mock(TableDescription.class); - when(describeTableResponse.table()).thenReturn(tableDescription); - when(tableDescription.tableStatus()).thenReturn(TableStatus.ACTIVE); + when(client.describeTable(any(DescribeTableRequest.class))).thenReturn(tableIsActiveResponse); - DescribeContinuousBackupsResponse describeContinuousBackupsResponse = - mock(DescribeContinuousBackupsResponse.class); when(client.describeContinuousBackups(any(DescribeContinuousBackupsRequest.class))) - .thenReturn(describeContinuousBackupsResponse); - ContinuousBackupsDescription continuousBackupsDescription = - mock(ContinuousBackupsDescription.class); - when(describeContinuousBackupsResponse.continuousBackupsDescription()) - .thenReturn(continuousBackupsDescription); - when(continuousBackupsDescription.continuousBackupsStatus()) - .thenReturn(ContinuousBackupsStatus.ENABLED); + .thenReturn(backupIsEnabledResponse); Map options = new HashMap<>(); options.put(DynamoAdmin.REQUEST_UNIT, "100"); @@ -1044,29 +1079,15 @@ public void repairTable_WithNonExistingMetadataForTable_shouldAddMetadataForTabl when(listTablesResponse.tableNames()).thenReturn(ImmutableList.of(getFullTableName())); // Wait for metadata table creation - DescribeTableResponse describeTableResponseMetadataTableCreation = - mock(DescribeTableResponse.class); - TableDescription tableDescriptionMetadataTableCreation = mock(TableDescription.class); - when(describeTableResponseMetadataTableCreation.table()) - .thenReturn(tableDescriptionMetadataTableCreation); - when(tableDescriptionMetadataTableCreation.tableStatus()).thenReturn(TableStatus.ACTIVE); when(client.describeTable( DescribeTableRequest.builder().tableName(getFullMetadataTableName()).build())) .thenThrow(ResourceNotFoundException.class) - .thenReturn(describeTableResponseMetadataTableCreation); + .thenReturn(tableIsActiveResponse); // Continuous backup check - DescribeContinuousBackupsResponse describeContinuousBackupsResponse = - mock(DescribeContinuousBackupsResponse.class); when(client.describeContinuousBackups(any(DescribeContinuousBackupsRequest.class))) - .thenReturn(describeContinuousBackupsResponse); - ContinuousBackupsDescription continuousBackupsDescription = - mock(ContinuousBackupsDescription.class); - when(describeContinuousBackupsResponse.continuousBackupsDescription()) - .thenReturn(continuousBackupsDescription); - when(continuousBackupsDescription.continuousBackupsStatus()) - .thenReturn(ContinuousBackupsStatus.ENABLED); + .thenReturn(backupIsEnabledResponse); // Act admin.repairTable(NAMESPACE, TABLE, metadata, ImmutableMap.of()); @@ -1177,6 +1198,249 @@ public void addNewColumnToTable_ShouldWorkProperly() throws ExecutionException { .build()); } + @Test + public void + createNamespace_WithNonExistingNamespacesTable_ShouldCreateNamespacesTableAndAddNamespace() + throws ExecutionException { + // Arrange + when(client.describeTable(any(DescribeTableRequest.class))) + .thenThrow(mock(ResourceNotFoundException.class)) + .thenReturn(tableIsActiveResponse); + when(client.describeContinuousBackups(any(DescribeContinuousBackupsRequest.class))) + .thenReturn(backupIsEnabledResponse); + + // Act + admin.createNamespace(NAMESPACE, Collections.emptyMap()); + + // Assert + verify(client, times(2)) + .describeTable( + DescribeTableRequest.builder().tableName(getFullNamespaceTableName()).build()); + CreateTableRequest createNamespaceTableRequest = + CreateTableRequest.builder() + .attributeDefinitions( + ImmutableList.of( + AttributeDefinition.builder() + .attributeName(DynamoAdmin.NAMESPACES_ATTR_NAME) + .attributeType(ScalarAttributeType.S) + .build())) + .keySchema( + KeySchemaElement.builder() + .attributeName(DynamoAdmin.NAMESPACES_ATTR_NAME) + .keyType(KeyType.HASH) + .build()) + .provisionedThroughput( + ProvisionedThroughput.builder() + .readCapacityUnits(DynamoAdmin.METADATA_TABLES_REQUEST_UNIT) + .writeCapacityUnits(DynamoAdmin.METADATA_TABLES_REQUEST_UNIT) + .build()) + .tableName(getFullNamespaceTableName()) + .build(); + verify(client).createTable(createNamespaceTableRequest); + verify(client) + .describeContinuousBackups( + DescribeContinuousBackupsRequest.builder() + .tableName(getFullNamespaceTableName()) + .build()); + verify(client) + .updateContinuousBackups( + UpdateContinuousBackupsRequest.builder() + .tableName(getFullNamespaceTableName()) + .pointInTimeRecoverySpecification( + PointInTimeRecoverySpecification.builder() + .pointInTimeRecoveryEnabled(true) + .build()) + .build()); + verify(client) + .putItem( + PutItemRequest.builder() + .tableName(getFullNamespaceTableName()) + .item( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build())) + .build()); + } + + @Test + public void createNamespace_WithExistingNamespacesTable_ShouldAddNamespace() + throws ExecutionException { + // Arrange + + // Act + admin.createNamespace(NAMESPACE, Collections.emptyMap()); + + // Assert + verify(client) + .describeTable( + DescribeTableRequest.builder().tableName(getFullNamespaceTableName()).build()); + verify(client) + .putItem( + PutItemRequest.builder() + .tableName(getFullNamespaceTableName()) + .item( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build())) + .build()); + } + + @Test + public void namespaceExists_WithExistingNamespace_ShouldReturnTrue() throws ExecutionException { + // Arrange + GetItemResponse getItemResponse = mock(GetItemResponse.class); + when(client.getItem(any(GetItemRequest.class))).thenReturn(getItemResponse); + when(getItemResponse.hasItem()).thenReturn(true); + + // Act + boolean actualNamespaceExists = admin.namespaceExists(NAMESPACE); + + // Assert] + assertThat(actualNamespaceExists).isTrue(); + verify(client) + .getItem( + GetItemRequest.builder() + .tableName(getFullNamespaceTableName()) + .key( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build())) + .build()); + } + + @Test + public void namespaceExists_WithNonExistingNamespace_ShouldReturnFalse() + throws ExecutionException { + // Arrange + GetItemResponse getItemResponse = mock(GetItemResponse.class); + when(client.getItem(any(GetItemRequest.class))).thenReturn(getItemResponse); + when(getItemResponse.hasItem()).thenReturn(false); + + // Act + boolean actualNamespaceExists = admin.namespaceExists(NAMESPACE); + + // Assert] + assertThat(actualNamespaceExists).isFalse(); + verify(client) + .getItem( + GetItemRequest.builder() + .tableName(getFullNamespaceTableName()) + .key( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build())) + .build()); + } + + @Test + public void namespaceExists_WithNonExistingNamespacesTable_ShouldReturnFalse() + throws ExecutionException { + // Arrange + when(client.getItem(any(GetItemRequest.class))).thenThrow(ResourceNotFoundException.class); + + // Act + boolean actualNamespaceExists = admin.namespaceExists(NAMESPACE); + + // Assert] + assertThat(actualNamespaceExists).isFalse(); + verify(client) + .getItem( + GetItemRequest.builder() + .tableName(getFullNamespaceTableName()) + .key( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build())) + .build()); + } + + @Test + public void getNamespacesNames_WithExistingNamespacesTable_ShouldReturnNamespaceNames() + throws ExecutionException { + // Arrange + ScanResponse scanResponse = mock(ScanResponse.class); + when(client.scan(any(ScanRequest.class))).thenReturn(scanResponse); + when(scanResponse.items()) + .thenReturn( + ImmutableList.of( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace() + 1).build()), + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace() + 2).build()))) + .thenReturn( + Collections.singletonList( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace() + 3).build()))); + Map lastEvaluatedKey = + ImmutableMap.of("foo", AttributeValue.builder().build()); + when(scanResponse.lastEvaluatedKey()) + .thenReturn(lastEvaluatedKey) + .thenReturn(Collections.emptyMap()); + + // Act + Set actualNamespaceNames = admin.getNamespaceNames(); + + // Assert + assertThat(actualNamespaceNames).containsOnly(NAMESPACE + 1, NAMESPACE + 2, NAMESPACE + 3); + verify(client) + .scan( + ScanRequest.builder() + .tableName(getFullNamespaceTableName()) + .exclusiveStartKey(null) + .build()); + verify(client) + .scan( + ScanRequest.builder() + .tableName(getFullNamespaceTableName()) + .exclusiveStartKey(lastEvaluatedKey) + .build()); + } + + @Test + public void getNamespacesNames_WithNonExistingNamespaces_ShouldReturnEmptySet() + throws ExecutionException { + // Arrange + ScanResponse scanResponse = mock(ScanResponse.class); + when(client.scan(any(ScanRequest.class))).thenReturn(scanResponse); + when(scanResponse.items()).thenReturn(Collections.emptyList()); + + when(scanResponse.lastEvaluatedKey()).thenReturn(Collections.emptyMap()); + + // Act + Set actualNamespaceNames = admin.getNamespaceNames(); + + // Assert + assertThat(actualNamespaceNames).isEmpty(); + verify(client) + .scan( + ScanRequest.builder() + .tableName(getFullNamespaceTableName()) + .exclusiveStartKey(null) + .build()); + } + + @Test + public void getNamespacesNames_WithNonExistingNamespacesTable_ShouldReturnEmptySet() + throws ExecutionException { + // Arrange + when(client.scan(any(ScanRequest.class))).thenThrow(ResourceNotFoundException.class); + + // Act + Set actualNamespaceNames = admin.getNamespaceNames(); + + // Assert + assertThat(actualNamespaceNames).isEmpty(); + verify(client) + .scan( + ScanRequest.builder() + .tableName(getFullNamespaceTableName()) + .exclusiveStartKey(null) + .build()); + } + @Test public void unsupportedOperations_ShouldThrowUnsupportedException() { // Arrange Act diff --git a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithDefaultConfigTest.java b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithDefaultConfigTest.java index a344bd3d34..51cc9ffc66 100644 --- a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithDefaultConfigTest.java +++ b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithDefaultConfigTest.java @@ -5,7 +5,7 @@ public class DynamoAdminWithDefaultConfigTest extends DynamoAdminTestBase { @Override - Optional getTableMetadataNamespaceConfig() { + Optional getMetadataNamespaceConfig() { return Optional.empty(); } diff --git a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixAndTableMetadataNamespaceConfigTest.java b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixAndMetadataNamespaceConfigTest.java similarity index 65% rename from core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixAndTableMetadataNamespaceConfigTest.java rename to core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixAndMetadataNamespaceConfigTest.java index 6563ee42eb..5ffe3a8a41 100644 --- a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixAndTableMetadataNamespaceConfigTest.java +++ b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixAndMetadataNamespaceConfigTest.java @@ -2,10 +2,10 @@ import java.util.Optional; -public class DynamoAdminWithNamespacePrefixAndTableMetadataNamespaceConfigTest +public class DynamoAdminWithNamespacePrefixAndMetadataNamespaceConfigTest extends DynamoAdminTestBase { @Override - Optional getTableMetadataNamespaceConfig() { + Optional getMetadataNamespaceConfig() { return Optional.of("my_meta_ns"); } diff --git a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixConfigTest.java b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixConfigTest.java index 324760db9f..e9d2e61621 100644 --- a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixConfigTest.java +++ b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminWithNamespacePrefixConfigTest.java @@ -5,7 +5,7 @@ public class DynamoAdminWithNamespacePrefixConfigTest extends DynamoAdminTestBase { @Override - Optional getTableMetadataNamespaceConfig() { + Optional getMetadataNamespaceConfig() { return Optional.empty(); } diff --git a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoConfigTest.java b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoConfigTest.java index 833cf0b7cb..4c3de571ef 100644 --- a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoConfigTest.java +++ b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoConfigTest.java @@ -14,7 +14,7 @@ public class DynamoConfigTest { private static final String ANY_SECRET_ACCESS_ID = "any_secret_access_id"; private static final String DYNAMO_STORAGE = "dynamo"; private static final String ANY_ENDPOINT_OVERRIDE = "http://localhost:8000"; - private static final String ANY_TABLE_METADATA_NAMESPACE = "any_namespace"; + private static final String ANY_METADATA_NAMESPACE = "any_namespace"; private static final String ANY_NAMESPACE_PREFIX = "any_prefix"; @Test @@ -26,7 +26,7 @@ public void constructor_AllPropertiesGiven_ShouldLoadProperly() { props.setProperty(DatabaseConfig.PASSWORD, ANY_SECRET_ACCESS_ID); props.setProperty(DatabaseConfig.STORAGE, DYNAMO_STORAGE); props.setProperty(DynamoConfig.ENDPOINT_OVERRIDE, ANY_ENDPOINT_OVERRIDE); - props.setProperty(DynamoConfig.TABLE_METADATA_NAMESPACE, ANY_TABLE_METADATA_NAMESPACE); + props.setProperty(DynamoConfig.METADATA_NAMESPACE, ANY_METADATA_NAMESPACE); props.setProperty(DynamoConfig.NAMESPACE_PREFIX, ANY_NAMESPACE_PREFIX); // Act @@ -38,8 +38,8 @@ public void constructor_AllPropertiesGiven_ShouldLoadProperly() { assertThat(config.getSecretAccessKey()).isEqualTo(ANY_SECRET_ACCESS_ID); assertThat(config.getEndpointOverride().isPresent()).isTrue(); assertThat(config.getEndpointOverride().get()).isEqualTo(ANY_ENDPOINT_OVERRIDE); - assertThat(config.getTableMetadataNamespace()).isPresent(); - assertThat(config.getTableMetadataNamespace().get()).isEqualTo(ANY_TABLE_METADATA_NAMESPACE); + assertThat(config.getMetadataNamespace()).isPresent(); + assertThat(config.getMetadataNamespace().get()).isEqualTo(ANY_METADATA_NAMESPACE); assertThat(config.getNamespacePrefix()).isPresent(); assertThat(config.getNamespacePrefix().get()).isEqualTo(ANY_NAMESPACE_PREFIX); } @@ -52,7 +52,7 @@ public void constructor_WithoutStorage_ShouldThrowIllegalArgumentException() { props.setProperty(DatabaseConfig.USERNAME, ANY_ACCESS_KEY_ID); props.setProperty(DatabaseConfig.PASSWORD, ANY_SECRET_ACCESS_ID); props.setProperty(DynamoConfig.ENDPOINT_OVERRIDE, ANY_ENDPOINT_OVERRIDE); - props.setProperty(DynamoConfig.TABLE_METADATA_NAMESPACE, ANY_TABLE_METADATA_NAMESPACE); + props.setProperty(DynamoConfig.METADATA_NAMESPACE, ANY_METADATA_NAMESPACE); // Act Assert assertThatThrownBy(() -> new DynamoConfig(new DatabaseConfig(props))) @@ -67,7 +67,7 @@ public void constructor_PropertiesWithoutEndpointOverrideGiven_ShouldLoadProperl props.setProperty(DatabaseConfig.USERNAME, ANY_ACCESS_KEY_ID); props.setProperty(DatabaseConfig.PASSWORD, ANY_SECRET_ACCESS_ID); props.setProperty(DatabaseConfig.STORAGE, DYNAMO_STORAGE); - props.setProperty(DynamoConfig.TABLE_METADATA_NAMESPACE, ANY_TABLE_METADATA_NAMESPACE); + props.setProperty(DynamoConfig.METADATA_NAMESPACE, ANY_METADATA_NAMESPACE); // Act DynamoConfig config = new DynamoConfig(new DatabaseConfig(props)); @@ -77,8 +77,8 @@ public void constructor_PropertiesWithoutEndpointOverrideGiven_ShouldLoadProperl assertThat(config.getAccessKeyId()).isEqualTo(ANY_ACCESS_KEY_ID); assertThat(config.getSecretAccessKey()).isEqualTo(ANY_SECRET_ACCESS_ID); assertThat(config.getEndpointOverride().isPresent()).isFalse(); - assertThat(config.getTableMetadataNamespace()).isPresent(); - assertThat(config.getTableMetadataNamespace().get()).isEqualTo(ANY_TABLE_METADATA_NAMESPACE); + assertThat(config.getMetadataNamespace()).isPresent(); + assertThat(config.getMetadataNamespace().get()).isEqualTo(ANY_METADATA_NAMESPACE); } @Test @@ -117,7 +117,7 @@ public void constructor_PropertiesWithoutTableMetadataNamespaceGiven_ShouldLoadP assertThat(config.getSecretAccessKey()).isEqualTo(ANY_SECRET_ACCESS_ID); assertThat(config.getEndpointOverride().isPresent()).isTrue(); assertThat(config.getEndpointOverride().get()).isEqualTo(ANY_ENDPOINT_OVERRIDE); - assertThat(config.getTableMetadataNamespace()).isNotPresent(); + assertThat(config.getMetadataNamespace()).isNotPresent(); } @Test @@ -147,10 +147,48 @@ public void constructor_PropertiesWithoutNamespacePrefixGiven_ShouldLoadProperly props.setProperty(DatabaseConfig.USERNAME, ANY_ACCESS_KEY_ID); props.setProperty(DatabaseConfig.PASSWORD, ANY_SECRET_ACCESS_ID); props.setProperty(DatabaseConfig.STORAGE, DYNAMO_STORAGE); - props.setProperty(DynamoConfig.TABLE_METADATA_NAMESPACE, ANY_TABLE_METADATA_NAMESPACE); + props.setProperty(DynamoConfig.TABLE_METADATA_NAMESPACE, ANY_METADATA_NAMESPACE); // Act Assert assertThatThrownBy(() -> new DynamoConfig(new DatabaseConfig(props))) .isInstanceOf(IllegalArgumentException.class); } + + @Test + public void + constructor_WithTableMetadataNamespaceAndMetadataNamespaceGiven_ShouldThrowIllegalArgumentException() { + // Arrange + Properties props = new Properties(); + props.setProperty(DatabaseConfig.CONTACT_POINTS, ANY_REGION); + props.setProperty(DatabaseConfig.USERNAME, ANY_ACCESS_KEY_ID); + props.setProperty(DatabaseConfig.PASSWORD, ANY_SECRET_ACCESS_ID); + props.setProperty(DatabaseConfig.STORAGE, DYNAMO_STORAGE); + props.setProperty(DynamoConfig.METADATA_NAMESPACE, "aaa"); + props.setProperty(DynamoConfig.TABLE_METADATA_NAMESPACE, "bbb"); + + // Act Assert + assertThatThrownBy(() -> new DynamoConfig(new DatabaseConfig(props))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructor_PropertiesWithTableMetadataNamespaceGiven_ShouldLoadProperly() { + // Arrange + Properties props = new Properties(); + props.setProperty(DatabaseConfig.CONTACT_POINTS, ANY_REGION); + props.setProperty(DatabaseConfig.USERNAME, ANY_ACCESS_KEY_ID); + props.setProperty(DatabaseConfig.PASSWORD, ANY_SECRET_ACCESS_ID); + props.setProperty(DatabaseConfig.STORAGE, DYNAMO_STORAGE); + props.setProperty(DynamoConfig.TABLE_METADATA_NAMESPACE, ANY_METADATA_NAMESPACE); + + // Act + DynamoConfig config = new DynamoConfig(new DatabaseConfig(props)); + + // Assert + assertThat(config.getRegion()).isEqualTo(ANY_REGION); + assertThat(config.getAccessKeyId()).isEqualTo(ANY_ACCESS_KEY_ID); + assertThat(config.getSecretAccessKey()).isEqualTo(ANY_SECRET_ACCESS_ID); + assertThat(config.getMetadataNamespace()).isPresent(); + assertThat(config.getMetadataNamespace().get()).isEqualTo(ANY_METADATA_NAMESPACE); + } } diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTestBase.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTestBase.java index 4f5b894dec..8c37713eeb 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTestBase.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTestBase.java @@ -10,11 +10,9 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.description; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; @@ -29,7 +27,6 @@ import com.scalar.db.api.TableMetadata; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; -import com.scalar.db.storage.jdbc.JdbcAdminTestBase.GetColumnsResultSetMocker.Row; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; @@ -63,7 +60,7 @@ /** * Abstraction that defines unit tests for the {@link JdbcAdmin}. The class purpose is to be able to * run the {@link JdbcAdmin} unit tests with different values for the {@link JdbcConfig}, notably - * {@link JdbcConfig#TABLE_METADATA_SCHEMA}. + * {@link JdbcConfig#METADATA_SCHEMA}. */ public abstract class JdbcAdminTestBase { private static final String NAMESPACE = "namespace"; @@ -81,16 +78,16 @@ public abstract class JdbcAdminTestBase { @Mock private Connection connection; @Mock private JdbcConfig config; - private String tableMetadataSchemaName; + private String metadataSchemaName; @BeforeEach public void setUp() throws Exception { MockitoAnnotations.openMocks(this).close(); // Arrange - when(config.getTableMetadataSchema()).thenReturn(getTableMetadataSchemaConfig()); + when(config.getMetadataSchema()).thenReturn(getTableMetadataSchemaConfig()); - tableMetadataSchemaName = getTableMetadataSchemaConfig().orElse("scalardb"); + metadataSchemaName = getTableMetadataSchemaConfig().orElse("scalardb"); } private JdbcAdmin createJdbcAdminFor(RdbEngine rdbEngine) { @@ -124,10 +121,9 @@ private void mockUndefinedTableError(RdbEngine rdbEngine, SQLException sqlExcept } /** - * This sets the {@link JdbcConfig#TABLE_METADATA_SCHEMA} value that will be used to run the - * tests. + * This sets the {@link JdbcConfig#METADATA_SCHEMA} value that will be used to run the tests. * - * @return {@link JdbcConfig#TABLE_METADATA_SCHEMA} value + * @return {@link JdbcConfig#METADATA_SCHEMA} value */ abstract Optional getTableMetadataSchemaConfig(); @@ -137,7 +133,7 @@ public void getTableMetadata_forMysql_ShouldReturnTableMetadata() getTableMetadata_forX_ShouldReturnTableMetadata( RdbEngine.MYSQL, "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC"); } @@ -147,7 +143,7 @@ public void getTableMetadata_forPostgresql_ShouldReturnTableMetadata() getTableMetadata_forX_ShouldReturnTableMetadata( RdbEngine.POSTGRESQL, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC"); } @@ -157,7 +153,7 @@ public void getTableMetadata_forSqlServer_ShouldReturnTableMetadata() getTableMetadata_forX_ShouldReturnTableMetadata( RdbEngine.SQL_SERVER, "SELECT [column_name],[data_type],[key_type],[clustering_order],[indexed] FROM [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] WHERE [full_table_name]=? ORDER BY [ordinal_position] ASC"); } @@ -167,7 +163,7 @@ public void getTableMetadata_forOracle_ShouldReturnTableMetadata() getTableMetadata_forX_ShouldReturnTableMetadata( RdbEngine.ORACLE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC"); } @@ -177,7 +173,7 @@ public void getTableMetadata_forSqlite_ShouldReturnTableMetadata() getTableMetadata_forX_ShouldReturnTableMetadata( RdbEngine.SQLITE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC"); } @@ -188,26 +184,25 @@ private void getTableMetadata_forX_ShouldReturnTableMetadata( String namespace = "ns"; String table = "table"; - PreparedStatement checkStatement = prepareStatementForNamespaceCheck(); PreparedStatement selectStatement = mock(PreparedStatement.class); ResultSet resultSet = mockResultSet( - Arrays.asList( - new GetColumnsResultSetMocker.Row( - "c3", DataType.BOOLEAN.toString(), "PARTITION", null, false), - new GetColumnsResultSetMocker.Row( - "c1", DataType.TEXT.toString(), "CLUSTERING", Order.DESC.toString(), false), - new GetColumnsResultSetMocker.Row( - "c4", DataType.BLOB.toString(), "CLUSTERING", Order.ASC.toString(), true), - new GetColumnsResultSetMocker.Row( - "c2", DataType.BIGINT.toString(), null, null, false), - new GetColumnsResultSetMocker.Row("c5", DataType.INT.toString(), null, null, false), - new GetColumnsResultSetMocker.Row( - "c6", DataType.DOUBLE.toString(), null, null, false), - new GetColumnsResultSetMocker.Row( - "c7", DataType.FLOAT.toString(), null, null, false))); + new SelectAllFromMetadataTableResultSetMocker.Row( + "c3", DataType.BOOLEAN.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + "c1", DataType.TEXT.toString(), "CLUSTERING", Order.DESC.toString(), false), + new SelectAllFromMetadataTableResultSetMocker.Row( + "c4", DataType.BLOB.toString(), "CLUSTERING", Order.ASC.toString(), true), + new SelectAllFromMetadataTableResultSetMocker.Row( + "c2", DataType.BIGINT.toString(), null, null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + "c5", DataType.INT.toString(), null, null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + "c6", DataType.DOUBLE.toString(), null, null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + "c7", DataType.FLOAT.toString(), null, null, false)); when(selectStatement.executeQuery()).thenReturn(resultSet); - when(connection.prepareStatement(any())).thenReturn(checkStatement).thenReturn(selectStatement); + when(connection.prepareStatement(any())).thenReturn(selectStatement); when(dataSource.getConnection()).thenReturn(connection); JdbcAdmin admin = createJdbcAdminFor(rdbEngine); @@ -234,11 +229,36 @@ private void getTableMetadata_forX_ShouldReturnTableMetadata( verify(connection).prepareStatement(expectedSelectStatements); } - public ResultSet mockResultSet(List rows) throws SQLException { + public ResultSet mockResultSet(SelectAllFromMetadataTableResultSetMocker.Row... rows) + throws SQLException { ResultSet resultSet = mock(ResultSet.class); // Everytime the ResultSet.next() method will be called, the ResultSet.getXXX methods call be // mocked to return the current row data - doAnswer(new GetColumnsResultSetMocker(rows)).when(resultSet).next(); + doAnswer(new SelectAllFromMetadataTableResultSetMocker(Arrays.asList(rows))) + .when(resultSet) + .next(); + return resultSet; + } + + public ResultSet mockResultSet(SelectFullTableNameFromMetadataTableResultSetMocker.Row... rows) + throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + // Everytime the ResultSet.next() method will be called, the ResultSet.getXXX methods call be + // mocked to return the current row data + doAnswer(new SelectFullTableNameFromMetadataTableResultSetMocker(Arrays.asList(rows))) + .when(resultSet) + .next(); + return resultSet; + } + + public ResultSet mockResultSet(SelectNamespaceNameFromNamespaceTableResultSetMocker.Row... rows) + throws SQLException { + ResultSet resultSet = mock(ResultSet.class); + // Everytime the ResultSet.next() method will be called, the ResultSet.getXXX methods call be + // mocked to return the current row data + doAnswer(new SelectNamespaceNameFromNamespaceTableResultSetMocker(Arrays.asList(rows))) + .when(resultSet) + .next(); return resultSet; } @@ -246,20 +266,23 @@ public ResultSet mockResultSet(List rows) throws public void getTableMetadata_MetadataSchemaNotExistsForX_ShouldReturnNull() throws SQLException, ExecutionException { for (RdbEngine rdbEngine : RDB_ENGINES.keySet()) { - getTableMetadata_MetadataSchemaNotExistsForX_ShouldReturnNull(rdbEngine); + getTableMetadata_MetadataTableNotExistsForX_ShouldReturnNull(rdbEngine); } } - private void getTableMetadata_MetadataSchemaNotExistsForX_ShouldReturnNull(RdbEngine rdbEngine) + private void getTableMetadata_MetadataTableNotExistsForX_ShouldReturnNull(RdbEngine rdbEngine) throws SQLException, ExecutionException { // Arrange JdbcAdmin admin = createJdbcAdminFor(rdbEngine); Connection connection = mock(Connection.class); - PreparedStatement selectStatement = prepareStatementForNamespaceCheck(false); + PreparedStatement selectStatement = mock(PreparedStatement.class); when(dataSource.getConnection()).thenReturn(connection); when(connection.prepareStatement(any())).thenReturn(selectStatement); + SQLException sqlException = mock(SQLException.class); + mockUndefinedTableError(rdbEngine, sqlException); + when(selectStatement.executeQuery()).thenThrow(sqlException); // Act TableMetadata actual = admin.getTableMetadata("my_ns", "my_tbl"); @@ -272,21 +295,39 @@ private void getTableMetadata_MetadataSchemaNotExistsForX_ShouldReturnNull(RdbEn public void createNamespace_forMysql_shouldExecuteCreateNamespaceStatement() throws ExecutionException, SQLException { createNamespace_forX_shouldExecuteCreateNamespaceStatement( - RdbEngine.MYSQL, "CREATE SCHEMA `my_ns` character set utf8 COLLATE utf8_bin"); + RdbEngine.MYSQL, + "CREATE SCHEMA `my_ns` character set utf8 COLLATE utf8_bin", + "CREATE SCHEMA IF NOT EXISTS `" + metadataSchemaName + "`", + "CREATE TABLE IF NOT EXISTS `" + + metadataSchemaName + + "`.`namespaces`(`namespace_name` VARCHAR(128), PRIMARY KEY (`namespace_name`))", + "INSERT INTO `" + metadataSchemaName + "`.`namespaces` VALUES (?)"); } @Test public void createNamespace_forPostgresql_shouldExecuteCreateNamespaceStatement() throws ExecutionException, SQLException { createNamespace_forX_shouldExecuteCreateNamespaceStatement( - RdbEngine.POSTGRESQL, "CREATE SCHEMA \"my_ns\""); + RdbEngine.POSTGRESQL, + "CREATE SCHEMA \"my_ns\"", + "CREATE SCHEMA IF NOT EXISTS \"" + metadataSchemaName + "\"", + "CREATE TABLE IF NOT EXISTS \"" + + metadataSchemaName + + "\".\"namespaces\"(\"namespace_name\" VARCHAR(128), PRIMARY KEY (\"namespace_name\"))", + "INSERT INTO \"" + metadataSchemaName + "\".\"namespaces\" VALUES (?)"); } @Test public void createNamespace_forSqlServer_shouldExecuteCreateNamespaceStatement() throws ExecutionException, SQLException { createNamespace_forX_shouldExecuteCreateNamespaceStatement( - RdbEngine.SQL_SERVER, "CREATE SCHEMA [my_ns]"); + RdbEngine.SQL_SERVER, + "CREATE SCHEMA [my_ns]", + "CREATE SCHEMA [" + metadataSchemaName + "]", + "CREATE TABLE [" + + metadataSchemaName + + "].[namespaces]([namespace_name] VARCHAR(128), PRIMARY KEY ([namespace_name]))", + "INSERT INTO [" + metadataSchemaName + "].[namespaces] VALUES (?)"); } @Test @@ -295,12 +336,22 @@ public void createNamespace_forOracle_shouldExecuteCreateNamespaceStatement() createNamespace_forX_shouldExecuteCreateNamespaceStatement( RdbEngine.ORACLE, "CREATE USER \"my_ns\" IDENTIFIED BY \"oracle\"", - "ALTER USER \"my_ns\" quota unlimited on USERS"); + "ALTER USER \"my_ns\" quota unlimited on USERS", + "CREATE TABLE \"" + + metadataSchemaName + + "\".\"namespaces\"(\"namespace_name\" VARCHAR2(128), PRIMARY KEY (\"namespace_name\"))", + "INSERT INTO \"" + metadataSchemaName + "\".\"namespaces\" VALUES (?)"); } @Test - public void createNamespace_forSqlite_shouldExecuteCreateNamespaceStatement() { - // no sql is executed + public void createNamespace_forSqlite_shouldExecuteCreateNamespaceStatement() + throws SQLException, ExecutionException { + createNamespace_forX_shouldExecuteCreateNamespaceStatement( + RdbEngine.SQLITE, + "CREATE TABLE IF NOT EXISTS \"" + + metadataSchemaName + + "$namespaces\"(\"namespace_name\" TEXT, PRIMARY KEY (\"namespace_name\"))", + "INSERT INTO \"" + metadataSchemaName + "$namespaces\" VALUES (?)"); } private void createNamespace_forX_shouldExecuteCreateNamespaceStatement( @@ -311,22 +362,26 @@ private void createNamespace_forX_shouldExecuteCreateNamespaceStatement( JdbcAdmin admin = createJdbcAdminFor(rdbEngine); List mockedStatements = new ArrayList<>(); - for (int i = 0; i < expectedSqlStatements.length; i++) { + for (int i = 0; i < expectedSqlStatements.length - 1; i++) { mockedStatements.add(mock(Statement.class)); } + PreparedStatement preparedStatement = mock(PreparedStatement.class); when(connection.createStatement()) .thenReturn( mockedStatements.get(0), mockedStatements.subList(1, mockedStatements.size()).toArray(new Statement[0])); + when(connection.prepareStatement(anyString())).thenReturn(preparedStatement); when(dataSource.getConnection()).thenReturn(connection); // Act admin.createNamespace(namespace); // Assert - for (int i = 0; i < expectedSqlStatements.length; i++) { + for (int i = 0; i < expectedSqlStatements.length - 1; i++) { verify(mockedStatements.get(i)).execute(expectedSqlStatements[i]); } + verify(connection).prepareStatement(expectedSqlStatements[expectedSqlStatements.length - 1]); + verify(preparedStatement).execute(); } @Test @@ -353,9 +408,9 @@ public void createTable_forMysql_shouldExecuteCreateTableStatement() "CREATE TABLE `my_ns`.`foo_table`(`c3` BOOLEAN,`c1` VARCHAR(64),`c4` VARBINARY(64),`c2` BIGINT,`c5` INT,`c6` DOUBLE,`c7` DOUBLE, PRIMARY KEY (`c3`,`c1`,`c4`))", "CREATE INDEX `index_my_ns_foo_table_c4` ON `my_ns`.`foo_table` (`c4`)", "CREATE INDEX `index_my_ns_foo_table_c1` ON `my_ns`.`foo_table` (`c1`)", - "CREATE SCHEMA IF NOT EXISTS `" + tableMetadataSchemaName + "`", + "CREATE SCHEMA IF NOT EXISTS `" + metadataSchemaName + "`", "CREATE TABLE IF NOT EXISTS `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata`(" + "`full_table_name` VARCHAR(128)," + "`column_name` VARCHAR(128)," @@ -366,25 +421,25 @@ public void createTable_forMysql_shouldExecuteCreateTableStatement() + "`ordinal_position` INTEGER NOT NULL," + "PRIMARY KEY (`full_table_name`, `column_name`))", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,false,1)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','ASC',true,2)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',true,3)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,false,4)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,false,5)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,false,6)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,false,7)"); } @@ -396,9 +451,9 @@ public void createTable_forPostgresql_shouldExecuteCreateTableStatement() "CREATE TABLE \"my_ns\".\"foo_table\"(\"c3\" BOOLEAN,\"c1\" VARCHAR(10485760),\"c4\" BYTEA,\"c2\" BIGINT,\"c5\" INT,\"c6\" DOUBLE PRECISION,\"c7\" FLOAT, PRIMARY KEY (\"c3\",\"c1\",\"c4\"))", "CREATE INDEX \"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", "CREATE INDEX \"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")", - "CREATE SCHEMA IF NOT EXISTS \"" + tableMetadataSchemaName + "\"", + "CREATE SCHEMA IF NOT EXISTS \"" + metadataSchemaName + "\"", "CREATE TABLE IF NOT EXISTS \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\"(" + "\"full_table_name\" VARCHAR(128)," + "\"column_name\" VARCHAR(128)," @@ -409,25 +464,25 @@ public void createTable_forPostgresql_shouldExecuteCreateTableStatement() + "\"ordinal_position\" INTEGER NOT NULL," + "PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,false,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','ASC',true,2)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',true,3)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,false,4)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,false,5)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,false,6)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,false,7)"); } @@ -440,9 +495,9 @@ public void createTable_forSqlServer_shouldExecuteCreateTableStatement() + "[c4] VARBINARY(8000),[c2] BIGINT,[c5] INT,[c6] FLOAT,[c7] FLOAT(24), PRIMARY KEY ([c3],[c1],[c4]))", "CREATE INDEX [index_my_ns_foo_table_c4] ON [my_ns].[foo_table] ([c4])", "CREATE INDEX [index_my_ns_foo_table_c1] ON [my_ns].[foo_table] ([c1])", - "CREATE SCHEMA [" + tableMetadataSchemaName + "]", + "CREATE SCHEMA [" + metadataSchemaName + "]", "CREATE TABLE [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata](" + "[full_table_name] VARCHAR(128)," + "[column_name] VARCHAR(128)," @@ -453,25 +508,25 @@ public void createTable_forSqlServer_shouldExecuteCreateTableStatement() + "[ordinal_position] INTEGER NOT NULL," + "PRIMARY KEY ([full_table_name], [column_name]))", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,0,1)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','ASC',1,2)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',1,3)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,0,4)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,0,5)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,0,6)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,0,7)"); } @@ -484,31 +539,31 @@ public void createTable_forOracle_shouldExecuteCreateTableStatement() "ALTER TABLE \"my_ns\".\"foo_table\" INITRANS 3 MAXTRANS 255", "CREATE INDEX \"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", "CREATE INDEX \"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")", - "CREATE USER \"" + tableMetadataSchemaName + "\" IDENTIFIED BY \"oracle\"", - "ALTER USER \"" + tableMetadataSchemaName + "\" quota unlimited on USERS", + "CREATE USER \"" + metadataSchemaName + "\" IDENTIFIED BY \"oracle\"", + "ALTER USER \"" + metadataSchemaName + "\" quota unlimited on USERS", "CREATE TABLE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\"(\"full_table_name\" VARCHAR2(128),\"column_name\" VARCHAR2(128),\"data_type\" VARCHAR2(20) NOT NULL,\"key_type\" VARCHAR2(20),\"clustering_order\" VARCHAR2(10),\"indexed\" NUMBER(1) NOT NULL,\"ordinal_position\" INTEGER NOT NULL,PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,0,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','ASC',1,2)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',1,3)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,0,4)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,0,5)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,0,6)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,0,7)"); } @@ -521,7 +576,7 @@ public void createTable_forSqlite_shouldExecuteCreateTableStatement() "CREATE INDEX \"index_my_ns_foo_table_c4\" ON \"my_ns$foo_table\" (\"c4\")", "CREATE INDEX \"index_my_ns_foo_table_c1\" ON \"my_ns$foo_table\" (\"c1\")", "CREATE TABLE IF NOT EXISTS \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\"(" + "\"full_table_name\" TEXT," + "\"column_name\" TEXT," @@ -532,25 +587,25 @@ public void createTable_forSqlite_shouldExecuteCreateTableStatement() + "\"ordinal_position\" INTEGER NOT NULL," + "PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,FALSE,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','ASC',TRUE,2)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',TRUE,3)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,FALSE,4)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,FALSE,5)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,FALSE,6)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,FALSE,7)"); } @@ -605,9 +660,9 @@ public void createTable_WithClusteringOrderForMysql_shouldExecuteCreateTableStat "CREATE TABLE `my_ns`.`foo_table`(`c3` BOOLEAN,`c1` VARCHAR(64),`c4` VARBINARY(64),`c2` BIGINT,`c5` INT,`c6` DOUBLE,`c7` DOUBLE, PRIMARY KEY (`c3` ASC,`c1` DESC,`c4` ASC))", "CREATE INDEX `index_my_ns_foo_table_c4` ON `my_ns`.`foo_table` (`c4`)", "CREATE INDEX `index_my_ns_foo_table_c1` ON `my_ns`.`foo_table` (`c1`)", - "CREATE SCHEMA IF NOT EXISTS `" + tableMetadataSchemaName + "`", + "CREATE SCHEMA IF NOT EXISTS `" + metadataSchemaName + "`", "CREATE TABLE IF NOT EXISTS `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata`(" + "`full_table_name` VARCHAR(128)," + "`column_name` VARCHAR(128)," @@ -618,25 +673,25 @@ public void createTable_WithClusteringOrderForMysql_shouldExecuteCreateTableStat + "`ordinal_position` INTEGER NOT NULL," + "PRIMARY KEY (`full_table_name`, `column_name`))", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,false,1)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','DESC',true,2)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',true,3)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,false,4)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,false,5)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,false,6)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,false,7)"); } @@ -649,9 +704,9 @@ public void createTable_WithClusteringOrderForPostgresql_shouldExecuteCreateTabl "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", "CREATE INDEX \"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", "CREATE INDEX \"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")", - "CREATE SCHEMA IF NOT EXISTS \"" + tableMetadataSchemaName + "\"", + "CREATE SCHEMA IF NOT EXISTS \"" + metadataSchemaName + "\"", "CREATE TABLE IF NOT EXISTS \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\"(" + "\"full_table_name\" VARCHAR(128)," + "\"column_name\" VARCHAR(128)," @@ -662,25 +717,25 @@ public void createTable_WithClusteringOrderForPostgresql_shouldExecuteCreateTabl + "\"ordinal_position\" INTEGER NOT NULL," + "PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,false,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','DESC',true,2)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',true,3)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,false,4)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,false,5)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,false,6)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,false,7)"); } @@ -693,9 +748,9 @@ public void createTable_WithClusteringOrderForSqlServer_shouldExecuteCreateTable + "[c4] VARBINARY(8000),[c2] BIGINT,[c5] INT,[c6] FLOAT,[c7] FLOAT(24), PRIMARY KEY ([c3] ASC,[c1] DESC,[c4] ASC))", "CREATE INDEX [index_my_ns_foo_table_c4] ON [my_ns].[foo_table] ([c4])", "CREATE INDEX [index_my_ns_foo_table_c1] ON [my_ns].[foo_table] ([c1])", - "CREATE SCHEMA [" + tableMetadataSchemaName + "]", + "CREATE SCHEMA [" + metadataSchemaName + "]", "CREATE TABLE [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata](" + "[full_table_name] VARCHAR(128)," + "[column_name] VARCHAR(128)," @@ -706,25 +761,25 @@ public void createTable_WithClusteringOrderForSqlServer_shouldExecuteCreateTable + "[ordinal_position] INTEGER NOT NULL," + "PRIMARY KEY ([full_table_name], [column_name]))", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,0,1)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','DESC',1,2)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',1,3)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,0,4)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,0,5)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,0,6)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,0,7)"); } @@ -738,31 +793,31 @@ public void createTable_WithClusteringOrderForOracle_shouldExecuteCreateTableSta "CREATE UNIQUE INDEX \"my_ns.foo_table_clustering_order_idx\" ON \"my_ns\".\"foo_table\" (\"c3\" ASC,\"c1\" DESC,\"c4\" ASC)", "CREATE INDEX \"index_my_ns_foo_table_c4\" ON \"my_ns\".\"foo_table\" (\"c4\")", "CREATE INDEX \"index_my_ns_foo_table_c1\" ON \"my_ns\".\"foo_table\" (\"c1\")", - "CREATE USER \"" + tableMetadataSchemaName + "\" IDENTIFIED BY \"oracle\"", - "ALTER USER \"" + tableMetadataSchemaName + "\" quota unlimited on USERS", + "CREATE USER \"" + metadataSchemaName + "\" IDENTIFIED BY \"oracle\"", + "ALTER USER \"" + metadataSchemaName + "\" quota unlimited on USERS", "CREATE TABLE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\"(\"full_table_name\" VARCHAR2(128),\"column_name\" VARCHAR2(128),\"data_type\" VARCHAR2(20) NOT NULL,\"key_type\" VARCHAR2(20),\"clustering_order\" VARCHAR2(10),\"indexed\" NUMBER(1) NOT NULL,\"ordinal_position\" INTEGER NOT NULL,PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,0,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','DESC',1,2)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',1,3)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,0,4)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,0,5)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,0,6)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,0,7)"); } @@ -775,7 +830,7 @@ public void createTable_WithClusteringOrderForSqlite_shouldExecuteCreateTableSta "CREATE INDEX \"index_my_ns_foo_table_c4\" ON \"my_ns$foo_table\" (\"c4\")", "CREATE INDEX \"index_my_ns_foo_table_c1\" ON \"my_ns$foo_table\" (\"c1\")", "CREATE TABLE IF NOT EXISTS \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\"(" + "\"full_table_name\" TEXT," + "\"column_name\" TEXT," @@ -786,25 +841,25 @@ public void createTable_WithClusteringOrderForSqlite_shouldExecuteCreateTableSta + "\"ordinal_position\" INTEGER NOT NULL," + "PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c3','BOOLEAN','PARTITION',NULL,FALSE,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c1','TEXT','CLUSTERING','DESC',TRUE,2)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c4','BLOB','CLUSTERING','ASC',TRUE,3)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c2','BIGINT',NULL,NULL,FALSE,4)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c5','INT',NULL,NULL,FALSE,5)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c6','DOUBLE',NULL,NULL,FALSE,6)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c7','FLOAT',NULL,NULL,FALSE,7)"); } @@ -948,11 +1003,10 @@ public void dropTable_forMysqlWithNoMoreMetadataAfterDeletion_shouldDropTableAnd RdbEngine.MYSQL, "DROP TABLE `my_ns`.`foo_table`", "DELETE FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name` = 'my_ns.foo_table'", - "SELECT DISTINCT `full_table_name` FROM `" + tableMetadataSchemaName + "`.`metadata`", - "DROP TABLE `" + tableMetadataSchemaName + "`.`metadata`", - "DROP SCHEMA `" + tableMetadataSchemaName + "`"); + "SELECT DISTINCT `full_table_name` FROM `" + metadataSchemaName + "`.`metadata`", + "DROP TABLE `" + metadataSchemaName + "`.`metadata`"); } @Test @@ -963,11 +1017,10 @@ public void dropTable_forMysqlWithNoMoreMetadataAfterDeletion_shouldDropTableAnd RdbEngine.POSTGRESQL, "DROP TABLE \"my_ns\".\"foo_table\"", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" + tableMetadataSchemaName + "\".\"metadata\"", - "DROP TABLE \"" + tableMetadataSchemaName + "\".\"metadata\"", - "DROP SCHEMA \"" + tableMetadataSchemaName + "\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + metadataSchemaName + "\".\"metadata\"", + "DROP TABLE \"" + metadataSchemaName + "\".\"metadata\""); } @Test @@ -978,11 +1031,10 @@ public void dropTable_forMysqlWithNoMoreMetadataAfterDeletion_shouldDropTableAnd RdbEngine.SQL_SERVER, "DROP TABLE [my_ns].[foo_table]", "DELETE FROM [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] WHERE [full_table_name] = 'my_ns.foo_table'", - "SELECT DISTINCT [full_table_name] FROM [" + tableMetadataSchemaName + "].[metadata]", - "DROP TABLE [" + tableMetadataSchemaName + "].[metadata]", - "DROP SCHEMA [" + tableMetadataSchemaName + "]"); + "SELECT DISTINCT [full_table_name] FROM [" + metadataSchemaName + "].[metadata]", + "DROP TABLE [" + metadataSchemaName + "].[metadata]"); } @Test @@ -992,11 +1044,10 @@ public void dropTable_forOracleWithNoMoreMetadataAfterDeletion_shouldDropTableAn RdbEngine.ORACLE, "DROP TABLE \"my_ns\".\"foo_table\"", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" + tableMetadataSchemaName + "\".\"metadata\"", - "DROP TABLE \"" + tableMetadataSchemaName + "\".\"metadata\"", - "DROP USER \"" + tableMetadataSchemaName + "\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + metadataSchemaName + "\".\"metadata\"", + "DROP TABLE \"" + metadataSchemaName + "\".\"metadata\""); } @Test @@ -1006,10 +1057,10 @@ public void dropTable_forSqliteWithNoMoreMetadataAfterDeletion_shouldDropTableAn RdbEngine.SQLITE, "DROP TABLE \"my_ns$foo_table\"", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" + tableMetadataSchemaName + "$metadata\"", - "DROP TABLE \"" + tableMetadataSchemaName + "$metadata\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + metadataSchemaName + "$metadata\"", + "DROP TABLE \"" + metadataSchemaName + "$metadata\""); } private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDeleteMetadata( @@ -1059,9 +1110,9 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel RdbEngine.MYSQL, "DROP TABLE `my_ns`.`foo_table`", "DELETE FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name` = 'my_ns.foo_table'", - "SELECT DISTINCT `full_table_name` FROM `" + tableMetadataSchemaName + "`.`metadata`"); + "SELECT DISTINCT `full_table_name` FROM `" + metadataSchemaName + "`.`metadata`"); } @Test @@ -1072,11 +1123,9 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel RdbEngine.POSTGRESQL, "DROP TABLE \"my_ns\".\"foo_table\"", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" - + tableMetadataSchemaName - + "\".\"metadata\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + metadataSchemaName + "\".\"metadata\""); } @Test @@ -1087,9 +1136,9 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel RdbEngine.SQL_SERVER, "DROP TABLE [my_ns].[foo_table]", "DELETE FROM [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] WHERE [full_table_name] = 'my_ns.foo_table'", - "SELECT DISTINCT [full_table_name] FROM [" + tableMetadataSchemaName + "].[metadata]"); + "SELECT DISTINCT [full_table_name] FROM [" + metadataSchemaName + "].[metadata]"); } @Test @@ -1100,11 +1149,9 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel RdbEngine.ORACLE, "DROP TABLE \"my_ns\".\"foo_table\"", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" - + tableMetadataSchemaName - + "\".\"metadata\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + metadataSchemaName + "\".\"metadata\""); } @Test @@ -1115,9 +1162,9 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel RdbEngine.SQLITE, "DROP TABLE \"my_ns$foo_table\"", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" + tableMetadataSchemaName + "$metadata\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + metadataSchemaName + "$metadata\""); } private void @@ -1161,47 +1208,220 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel } @Test - public void dropNamespace_forMysql_shouldDropNamespace() throws Exception { - dropSchema_forX_shouldDropSchema(RdbEngine.MYSQL, "DROP SCHEMA `my_ns`"); + public void dropNamespace_WithLastExistingSchemaForMysql_shouldDropSchemaAndNamespacesTable() + throws Exception { + dropNamespace_WithLastExistingSchemaForX_shouldDropSchemaAndNamespacesTable( + RdbEngine.MYSQL, + "DROP SCHEMA `my_ns`", + "DELETE FROM `" + metadataSchemaName + "`.`namespaces` WHERE `namespace_name` = ?", + "SELECT * FROM `" + metadataSchemaName + "`.`namespaces`", + "DROP TABLE `" + metadataSchemaName + "`.`namespaces`", + "DROP SCHEMA `" + metadataSchemaName + "`"); } @Test - public void dropNamespace_forPostgresql_shouldDropNamespace() throws Exception { - dropSchema_forX_shouldDropSchema(RdbEngine.POSTGRESQL, "DROP SCHEMA \"my_ns\""); + public void dropNamespace_WithLastExistingSchemaForPostgresql_shouldDropSchemaAndNamespacesTable() + throws Exception { + dropNamespace_WithLastExistingSchemaForX_shouldDropSchemaAndNamespacesTable( + RdbEngine.POSTGRESQL, + "DROP SCHEMA \"my_ns\"", + "DELETE FROM \"" + metadataSchemaName + "\".\"namespaces\" WHERE \"namespace_name\" = ?", + "SELECT * FROM \"" + metadataSchemaName + "\".\"namespaces\"", + "DROP TABLE \"" + metadataSchemaName + "\".\"namespaces\"", + "DROP SCHEMA \"" + metadataSchemaName + "\""); } @Test - public void dropNamespace_forSqlServer_shouldDropNamespace() throws Exception { - dropSchema_forX_shouldDropSchema(RdbEngine.SQL_SERVER, "DROP SCHEMA [my_ns]"); + public void dropNamespace_WithLastExistingSchemaForSqlServer_shouldDropSchemaAndNamespacesTable() + throws Exception { + dropNamespace_WithLastExistingSchemaForX_shouldDropSchemaAndNamespacesTable( + RdbEngine.SQL_SERVER, + "DROP SCHEMA [my_ns]", + "DELETE FROM [" + metadataSchemaName + "].[namespaces] WHERE [namespace_name] = ?", + "SELECT * FROM [" + metadataSchemaName + "].[namespaces]", + "DROP TABLE [" + metadataSchemaName + "].[namespaces]", + "DROP SCHEMA [" + metadataSchemaName + "]"); } @Test - public void dropNamespace_forOracle_shouldDropNamespace() throws Exception { - dropSchema_forX_shouldDropSchema(RdbEngine.ORACLE, "DROP USER \"my_ns\""); + public void dropNamespace_WithLastExistingSchemaForOracle_shouldDropSchemaAndNamespacesTable() + throws Exception { + dropNamespace_WithLastExistingSchemaForX_shouldDropSchemaAndNamespacesTable( + RdbEngine.ORACLE, + "DROP USER \"my_ns\"", + "DELETE FROM \"" + metadataSchemaName + "\".\"namespaces\" WHERE \"namespace_name\" = ?", + "SELECT * FROM \"" + metadataSchemaName + "\".\"namespaces\"", + "DROP TABLE \"" + metadataSchemaName + "\".\"namespaces\"", + "DROP USER \"" + metadataSchemaName + "\""); } @Test - public void dropNamespace_forSqlite_shouldDropNamespace() { - // no SQL is executed + public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception { + // Arrange + String deleteFromNamespaceTableQuery = + "DELETE FROM \"" + metadataSchemaName + "$namespaces\" WHERE \"namespace_name\" = ?"; + String selectAllFromNamespaceTableQuery = + "SELECT * FROM \"" + metadataSchemaName + "$namespaces\""; + String dropNamespaceTableQuery = "DROP TABLE \"" + metadataSchemaName + "$namespaces\""; + + String namespace = "my_ns"; + JdbcAdmin admin = createJdbcAdminFor(RdbEngine.SQLITE); + + Connection connection = mock(Connection.class); + PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class); + PreparedStatement selectAllFromNamespaceTablePrepStmt = mock(PreparedStatement.class); + Statement dropNamespaceTableStmt = mock(Statement.class); + when(dataSource.getConnection()).thenReturn(connection); + when(connection.createStatement()).thenReturn(dropNamespaceTableStmt); + when(connection.prepareStatement(anyString())) + .thenReturn(deleteFromNamespaceTablePrepStmt, selectAllFromNamespaceTablePrepStmt); + when(dataSource.getConnection()).thenReturn(connection); + // Namespaces table is empty + ResultSet resultSet = + mockResultSet(new SelectFullTableNameFromMetadataTableResultSetMocker.Row[0]); + when(selectAllFromNamespaceTablePrepStmt.executeQuery()).thenReturn(resultSet); + + // Act + admin.dropNamespace(namespace); + + // Assert + verify(deleteFromNamespaceTablePrepStmt).setString(1, namespace); + verify(connection).prepareStatement(deleteFromNamespaceTableQuery); + verify(connection).prepareStatement(selectAllFromNamespaceTableQuery); + verify(dropNamespaceTableStmt).execute(dropNamespaceTableQuery); } - private void dropSchema_forX_shouldDropSchema( - RdbEngine rdbEngine, String expectedDropSchemaStatement) throws Exception { + private void dropNamespace_WithLastExistingSchemaForX_shouldDropSchemaAndNamespacesTable( + RdbEngine rdbEngine, + String dropNamespaceQuery, + String deleteFromNamespaceTableQuery, + String selectAllFromNamespaceTableQuery, + String dropNamespaceTableQuery, + String dropMetadataSchemaQuery) + throws Exception { // Arrange String namespace = "my_ns"; JdbcAdmin admin = createJdbcAdminFor(rdbEngine); Connection connection = mock(Connection.class); - Statement dropSchemaStatement = mock(Statement.class); + Statement dropNamespaceStmt = mock(Statement.class); + PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class); + PreparedStatement selectAllFromNamespaceTablePrepStmt = mock(PreparedStatement.class); + Statement dropNamespaceTableStmt = mock(Statement.class); + Statement dropMetadataSchemaStmt = mock(Statement.class); + when(dataSource.getConnection()).thenReturn(connection); + when(connection.createStatement()) + .thenReturn(dropNamespaceStmt, dropNamespaceTableStmt, dropMetadataSchemaStmt); + when(connection.prepareStatement(anyString())) + .thenReturn(deleteFromNamespaceTablePrepStmt, selectAllFromNamespaceTablePrepStmt); + when(dataSource.getConnection()).thenReturn(connection); + // Namespaces table is empty + ResultSet resultSet = + mockResultSet(new SelectFullTableNameFromMetadataTableResultSetMocker.Row[0]); + when(selectAllFromNamespaceTablePrepStmt.executeQuery()).thenReturn(resultSet); + // Act + admin.dropNamespace(namespace); + + // Assert + verify(dropNamespaceStmt).execute(dropNamespaceQuery); + verify(deleteFromNamespaceTablePrepStmt).setString(1, namespace); + verify(connection).prepareStatement(deleteFromNamespaceTableQuery); + verify(deleteFromNamespaceTablePrepStmt).execute(); + verify(connection).prepareStatement(selectAllFromNamespaceTableQuery); + verify(selectAllFromNamespaceTablePrepStmt).executeQuery(); + verify(dropNamespaceTableStmt).execute(dropNamespaceTableQuery); + verify(dropMetadataSchemaStmt).execute(dropMetadataSchemaQuery); + } + + @Test + public void dropNamespace_WithOtherNamespaceLeftForMysql_shouldOnlyDropNamespace() + throws Exception { + dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( + RdbEngine.MYSQL, + "DROP SCHEMA `my_ns`", + "DELETE FROM `" + metadataSchemaName + "`.`namespaces` WHERE `namespace_name` = ?", + "SELECT * FROM `" + metadataSchemaName + "`.`namespaces`"); + } + + @Test + public void dropNamespace_WithOtherNamespaceLeftForPostgresql_shouldOnlyDropNamespace() + throws Exception { + dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( + RdbEngine.POSTGRESQL, + "DROP SCHEMA \"my_ns\"", + "DELETE FROM \"" + metadataSchemaName + "\".\"namespaces\" WHERE \"namespace_name\" = ?", + "SELECT * FROM \"" + metadataSchemaName + "\".\"namespaces\""); + } + + @Test + public void dropNamespace_WithOtherNamespaceLeftForSqlServer_shouldOnlyDropNamespace() + throws Exception { + dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( + RdbEngine.SQL_SERVER, + "DROP SCHEMA [my_ns]", + "DELETE FROM [" + metadataSchemaName + "].[namespaces] WHERE [namespace_name] = ?", + "SELECT * FROM [" + metadataSchemaName + "].[namespaces]"); + } + + @Test + public void dropNamespace_WithOtherNamespaceLeftForOracle_shouldOnlyDropNamespace() + throws Exception { + dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( + RdbEngine.ORACLE, + "DROP USER \"my_ns\"", + "DELETE FROM \"" + metadataSchemaName + "\".\"namespaces\" WHERE \"namespace_name\" = ?", + "SELECT * FROM \"" + metadataSchemaName + "\".\"namespaces\""); + } + + @Test + public void dropNamespace_WithOtherNamespaceLeftForSqlLite_shouldOnlyDropNamespace() + throws Exception { + dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( + RdbEngine.SQLITE, + "unused", + "DELETE FROM \"" + metadataSchemaName + "$namespaces\" WHERE \"namespace_name\" = ?", + "SELECT * FROM \"" + metadataSchemaName + "$namespaces\""); + } + + private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( + RdbEngine rdbEngine, + String dropNamespaceStatement, + String deleteFromNamespaceTable, + String selectNamespaceStatement) + throws Exception { + // Arrange + String namespace = "my_ns"; + JdbcAdmin admin = createJdbcAdminFor(rdbEngine); + + Connection connection = mock(Connection.class); + Statement dropNamespaceStatementMock = mock(Statement.class); + PreparedStatement deleteFromNamespaceTableMock = mock(PreparedStatement.class); + PreparedStatement selectNamespaceStatementMock = mock(PreparedStatement.class); + if (rdbEngine != RdbEngine.SQLITE) { + when(connection.createStatement()).thenReturn(dropNamespaceStatementMock); + } + when(connection.prepareStatement(anyString())) + .thenReturn(deleteFromNamespaceTableMock, selectNamespaceStatementMock); when(dataSource.getConnection()).thenReturn(connection); - when(connection.createStatement()).thenReturn(dropSchemaStatement); + // Namespaces table contains other namespaces + ResultSet resultSet = + mockResultSet( + new SelectFullTableNameFromMetadataTableResultSetMocker.Row(namespace + ".tbl1")); + when(selectNamespaceStatementMock.executeQuery()).thenReturn(resultSet); // Act admin.dropNamespace(namespace); // Assert - verify(dropSchemaStatement).execute(expectedDropSchemaStatement); + if (rdbEngine != RdbEngine.SQLITE) { + verify(dropNamespaceStatementMock).execute(dropNamespaceStatement); + } + verify(connection).prepareStatement(deleteFromNamespaceTable); + verify(deleteFromNamespaceTableMock).setString(1, namespace); + verify(deleteFromNamespaceTableMock).execute(); + verify(connection).prepareStatement(selectNamespaceStatement); + verify(selectNamespaceStatementMock).executeQuery(); } @Test @@ -1209,7 +1429,7 @@ public void getNamespaceTables_forMysql_ShouldReturnTableNames() throws Exceptio getNamespaceTables_forX_ShouldReturnTableNames( RdbEngine.MYSQL, "SELECT DISTINCT `full_table_name` FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name` LIKE ?"); } @@ -1218,7 +1438,7 @@ public void getNamespaceTables_forPostgresql_ShouldReturnTableNames() throws Exc getNamespaceTables_forX_ShouldReturnTableNames( RdbEngine.POSTGRESQL, "SELECT DISTINCT \"full_table_name\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" LIKE ?"); } @@ -1227,7 +1447,7 @@ public void getNamespaceTables_forSqlServer_ShouldReturnTableNames() throws Exce getNamespaceTables_forX_ShouldReturnTableNames( RdbEngine.SQL_SERVER, "SELECT DISTINCT [full_table_name] FROM [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] WHERE [full_table_name] LIKE ?"); } @@ -1236,7 +1456,7 @@ public void getNamespaceTables_forOracle_ShouldReturnTableNames() throws Excepti getNamespaceTables_forX_ShouldReturnTableNames( RdbEngine.ORACLE, "SELECT DISTINCT \"full_table_name\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" LIKE ?"); } @@ -1245,7 +1465,7 @@ public void getNamespaceTables_forSqlite_ShouldReturnTableNames() throws Excepti getNamespaceTables_forX_ShouldReturnTableNames( RdbEngine.SQLITE, "SELECT DISTINCT \"full_table_name\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\" LIKE ?"); } @@ -1260,10 +1480,11 @@ private void getNamespaceTables_forX_ShouldReturnTableNames( // Everytime the ResultSet.next() method will be called, the ResultSet.getXXX methods call be // mocked to return the current row data doAnswer( - new GetTablesNamesResultSetMocker( + new SelectFullTableNameFromMetadataTableResultSetMocker( Arrays.asList( - new GetTablesNamesResultSetMocker.Row(namespace + ".t1"), - new GetTablesNamesResultSetMocker.Row(namespace + ".t2")))) + new SelectFullTableNameFromMetadataTableResultSetMocker.Row(namespace + ".t1"), + new SelectFullTableNameFromMetadataTableResultSetMocker.Row( + namespace + ".t2")))) .when(resultSet) .next(); PreparedStatement preparedStatement = mock(PreparedStatement.class); @@ -1286,8 +1507,7 @@ private void getNamespaceTables_forX_ShouldReturnTableNames( public void namespaceExists_forMysqlWithExistingNamespace_shouldReturnTrue() throws Exception { namespaceExists_forXWithExistingNamespace_ShouldReturnTrue( RdbEngine.MYSQL, - "SELECT 1 FROM `information_schema`.`schemata` WHERE `schema_name` = ?", - ""); + "SELECT 1 FROM `" + metadataSchemaName + "`.`namespaces` WHERE `namespace_name` = ?"); } @Test @@ -1295,34 +1515,33 @@ public void namespaceExists_forPostgresqlWithExistingNamespace_shouldReturnTrue( throws Exception { namespaceExists_forXWithExistingNamespace_ShouldReturnTrue( RdbEngine.POSTGRESQL, - "SELECT 1 FROM \"information_schema\".\"schemata\" WHERE \"schema_name\" = ?", - ""); + "SELECT 1 FROM \"" + metadataSchemaName + "\".\"namespaces\" WHERE \"namespace_name\" = ?"); } @Test public void namespaceExists_forSqlServerWithExistingNamespace_shouldReturnTrue() throws Exception { namespaceExists_forXWithExistingNamespace_ShouldReturnTrue( - RdbEngine.SQL_SERVER, "SELECT 1 FROM [sys].[schemas] WHERE [name] = ?", ""); + RdbEngine.SQL_SERVER, + "SELECT 1 FROM [" + metadataSchemaName + "].[namespaces] WHERE [namespace_name] = ?"); } @Test public void namespaceExists_forOracleWithExistingNamespace_shouldReturnTrue() throws Exception { namespaceExists_forXWithExistingNamespace_ShouldReturnTrue( - RdbEngine.ORACLE, "SELECT 1 FROM \"ALL_USERS\" WHERE \"USERNAME\" = ?", ""); + RdbEngine.ORACLE, + "SELECT 1 FROM \"" + metadataSchemaName + "\".\"namespaces\" WHERE \"namespace_name\" = ?"); } @Test public void namespaceExists_forSqliteWithExistingNamespace_shouldReturnTrue() throws Exception { namespaceExists_forXWithExistingNamespace_ShouldReturnTrue( RdbEngine.SQLITE, - "SELECT 1 FROM sqlite_master WHERE \"type\" = \"table\" AND \"tbl_name\" LIKE ?", - "$%"); + "SELECT 1 FROM \"" + metadataSchemaName + "$namespaces\" WHERE \"namespace_name\" = ?"); } private void namespaceExists_forXWithExistingNamespace_ShouldReturnTrue( - RdbEngine rdbEngine, String expectedSelectStatement, String namespacePlaceholderSuffix) - throws SQLException, ExecutionException { + RdbEngine rdbEngine, String expectedSelectStatement) throws SQLException, ExecutionException { // Arrange String namespace = "my_ns"; JdbcAdmin admin = createJdbcAdminFor(rdbEngine); @@ -1342,7 +1561,7 @@ private void namespaceExists_forXWithExistingNamespace_ShouldReturnTrue( verify(selectStatement).executeQuery(); verify(connection).prepareStatement(expectedSelectStatement); - verify(selectStatement).setString(1, namespace + namespacePlaceholderSuffix); + verify(selectStatement).setString(1, namespace); } @Test @@ -1351,11 +1570,11 @@ public void createIndex_ForColumnTypeWithoutRequiredAlterationForMysql_ShouldCre createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreateIndexProperly( RdbEngine.MYSQL, "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", "CREATE INDEX `index_my_ns_my_tbl_my_column` ON `my_ns`.`my_tbl` (`my_column`)", "UPDATE `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` SET `indexed`=true WHERE `full_table_name`='my_ns.my_tbl' AND `column_name`='my_column'"); } @@ -1366,11 +1585,11 @@ public void createIndex_ForColumnTypeWithoutRequiredAlterationForMysql_ShouldCre createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreateIndexProperly( RdbEngine.POSTGRESQL, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "CREATE INDEX \"index_my_ns_my_tbl_my_column\" ON \"my_ns\".\"my_tbl\" (\"my_column\")", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" SET \"indexed\"=true WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1381,11 +1600,11 @@ public void createIndex_ForColumnTypeWithoutRequiredAlterationForMysql_ShouldCre createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreateIndexProperly( RdbEngine.SQL_SERVER, "SELECT [column_name],[data_type],[key_type],[clustering_order],[indexed] FROM [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] WHERE [full_table_name]=? ORDER BY [ordinal_position] ASC", "CREATE INDEX [index_my_ns_my_tbl_my_column] ON [my_ns].[my_tbl] ([my_column])", "UPDATE [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] SET [indexed]=1 WHERE [full_table_name]='my_ns.my_tbl' AND [column_name]='my_column'"); } @@ -1396,11 +1615,11 @@ public void createIndex_ForColumnTypeWithoutRequiredAlterationForMysql_ShouldCre createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreateIndexProperly( RdbEngine.ORACLE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "CREATE INDEX \"index_my_ns_my_tbl_my_column\" ON \"my_ns\".\"my_tbl\" (\"my_column\")", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" SET \"indexed\"=1 WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1411,11 +1630,11 @@ public void createIndex_ForColumnTypeWithoutRequiredAlterationForMysql_ShouldCre createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreateIndexProperly( RdbEngine.SQLITE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "CREATE INDEX \"index_my_ns_my_tbl_my_column\" ON \"my_ns$my_tbl\" (\"my_column\")", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" SET \"indexed\"=TRUE WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1431,17 +1650,15 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate String indexColumn = "my_column"; JdbcAdmin admin = createJdbcAdminFor(rdbEngine); - PreparedStatement checkStatement = prepareStatementForNamespaceCheck(); PreparedStatement selectStatement = mock(PreparedStatement.class); ResultSet resultSet = mockResultSet( - Arrays.asList( - new GetColumnsResultSetMocker.Row( - "c1", DataType.BOOLEAN.toString(), "PARTITION", null, false), - new GetColumnsResultSetMocker.Row( - indexColumn, DataType.BOOLEAN.toString(), null, null, false))); + new SelectAllFromMetadataTableResultSetMocker.Row( + "c1", DataType.BOOLEAN.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + indexColumn, DataType.BOOLEAN.toString(), null, null, false)); when(selectStatement.executeQuery()).thenReturn(resultSet); - when(connection.prepareStatement(any())).thenReturn(checkStatement).thenReturn(selectStatement); + when(connection.prepareStatement(any())).thenReturn(selectStatement); Statement statement = mock(Statement.class); when(dataSource.getConnection()).thenReturn(connection); @@ -1468,12 +1685,12 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate createIndex_forColumnTypeWithRequiredAlterationForX_ShouldAlterColumnAndCreateIndexProperly( RdbEngine.MYSQL, "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", "ALTER TABLE `my_ns`.`my_tbl` MODIFY`my_column` VARCHAR(64)", "CREATE INDEX `index_my_ns_my_tbl_my_column` ON `my_ns`.`my_tbl` (`my_column`)", "UPDATE `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` SET `indexed`=true WHERE `full_table_name`='my_ns.my_tbl' AND `column_name`='my_column'"); } @@ -1484,12 +1701,12 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate createIndex_forColumnTypeWithRequiredAlterationForX_ShouldAlterColumnAndCreateIndexProperly( RdbEngine.POSTGRESQL, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN\"my_column\" TYPE VARCHAR(10485760)", "CREATE INDEX \"index_my_ns_my_tbl_my_column\" ON \"my_ns\".\"my_tbl\" (\"my_column\")", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" SET \"indexed\"=true WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1500,12 +1717,12 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate createIndex_forColumnTypeWithRequiredAlterationForX_ShouldAlterColumnAndCreateIndexProperly( RdbEngine.ORACLE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "ALTER TABLE \"my_ns\".\"my_tbl\" MODIFY ( \"my_column\" VARCHAR2(64) )", "CREATE INDEX \"index_my_ns_my_tbl_my_column\" ON \"my_ns\".\"my_tbl\" (\"my_column\")", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" SET \"indexed\"=1 WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1529,17 +1746,15 @@ private void createIndex_ForColumnTypeWithoutRequiredAlterationForX_ShouldCreate String indexColumn = "my_column"; JdbcAdmin admin = createJdbcAdminFor(rdbEngine); - PreparedStatement checkStatement = prepareStatementForNamespaceCheck(); PreparedStatement selectStatement = mock(PreparedStatement.class); ResultSet resultSet = mockResultSet( - Arrays.asList( - new GetColumnsResultSetMocker.Row( - "c1", DataType.BOOLEAN.toString(), "PARTITION", null, false), - new GetColumnsResultSetMocker.Row( - indexColumn, DataType.TEXT.toString(), null, null, false))); + new SelectAllFromMetadataTableResultSetMocker.Row( + "c1", DataType.BOOLEAN.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + indexColumn, DataType.TEXT.toString(), null, null, false)); when(selectStatement.executeQuery()).thenReturn(resultSet); - when(connection.prepareStatement(any())).thenReturn(checkStatement).thenReturn(selectStatement); + when(connection.prepareStatement(any())).thenReturn(selectStatement); Statement statement = mock(Statement.class); @@ -1567,11 +1782,11 @@ public void dropIndex_forColumnTypeWithoutRequiredAlterationForMysql_ShouldDropI dropIndex_forColumnTypeWithoutRequiredAlterationForX_ShouldDropIndexProperly( RdbEngine.MYSQL, "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", "DROP INDEX `index_my_ns_my_tbl_my_column` ON `my_ns`.`my_tbl`", "UPDATE `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` SET `indexed`=false WHERE `full_table_name`='my_ns.my_tbl' AND `column_name`='my_column'"); } @@ -1582,11 +1797,11 @@ public void dropIndex_forColumnTypeWithoutRequiredAlterationForMysql_ShouldDropI dropIndex_forColumnTypeWithoutRequiredAlterationForX_ShouldDropIndexProperly( RdbEngine.POSTGRESQL, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"my_ns\".\"index_my_ns_my_tbl_my_column\"", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" SET \"indexed\"=false WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1596,11 +1811,11 @@ public void dropIndex_forColumnTypeWithoutRequiredAlterationForServer_ShouldDrop dropIndex_forColumnTypeWithoutRequiredAlterationForX_ShouldDropIndexProperly( RdbEngine.SQL_SERVER, "SELECT [column_name],[data_type],[key_type],[clustering_order],[indexed] FROM [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] WHERE [full_table_name]=? ORDER BY [ordinal_position] ASC", "DROP INDEX [index_my_ns_my_tbl_my_column] ON [my_ns].[my_tbl]", "UPDATE [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] SET [indexed]=0 WHERE [full_table_name]='my_ns.my_tbl' AND [column_name]='my_column'"); } @@ -1610,11 +1825,11 @@ public void dropIndex_forColumnTypeWithoutRequiredAlterationForOracle_ShouldDrop dropIndex_forColumnTypeWithoutRequiredAlterationForX_ShouldDropIndexProperly( RdbEngine.ORACLE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"index_my_ns_my_tbl_my_column\"", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" SET \"indexed\"=0 WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1624,11 +1839,11 @@ public void dropIndex_forColumnTypeWithoutRequiredAlterationForSqlite_ShouldDrop dropIndex_forColumnTypeWithoutRequiredAlterationForX_ShouldDropIndexProperly( RdbEngine.SQLITE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"index_my_ns_my_tbl_my_column\"", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" SET \"indexed\"=FALSE WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1643,17 +1858,15 @@ private void dropIndex_forColumnTypeWithoutRequiredAlterationForX_ShouldDropInde String table = "my_tbl"; String indexColumn = "my_column"; JdbcAdmin admin = createJdbcAdminFor(rdbEngine); - PreparedStatement checkStatement = prepareStatementForNamespaceCheck(); PreparedStatement selectStatement = mock(PreparedStatement.class); ResultSet resultSet = mockResultSet( - Arrays.asList( - new GetColumnsResultSetMocker.Row( - "c1", DataType.BOOLEAN.toString(), "PARTITION", null, false), - new GetColumnsResultSetMocker.Row( - indexColumn, DataType.BOOLEAN.toString(), null, null, false))); + new SelectAllFromMetadataTableResultSetMocker.Row( + "c1", DataType.BOOLEAN.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + indexColumn, DataType.BOOLEAN.toString(), null, null, false)); when(selectStatement.executeQuery()).thenReturn(resultSet); - when(connection.prepareStatement(any())).thenReturn(checkStatement).thenReturn(selectStatement); + when(connection.prepareStatement(any())).thenReturn(selectStatement); Statement statement = mock(Statement.class); @@ -1680,12 +1893,12 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForMysql_ShouldDropInde dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexProperly( RdbEngine.MYSQL, "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", "DROP INDEX `index_my_ns_my_tbl_my_column` ON `my_ns`.`my_tbl`", "ALTER TABLE `my_ns`.`my_tbl` MODIFY`my_column` LONGTEXT", "UPDATE `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` SET `indexed`=false WHERE `full_table_name`='my_ns.my_tbl' AND `column_name`='my_column'"); } @@ -1695,12 +1908,12 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForPostgresql_ShouldDro dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexProperly( RdbEngine.POSTGRESQL, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"my_ns\".\"index_my_ns_my_tbl_my_column\"", "ALTER TABLE \"my_ns\".\"my_tbl\" ALTER COLUMN\"my_column\" TYPE TEXT", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" SET \"indexed\"=false WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1710,12 +1923,12 @@ public void dropIndex_forColumnTypeWithRequiredAlterationForOracle_ShouldDropInd dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexProperly( RdbEngine.ORACLE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "DROP INDEX \"index_my_ns_my_tbl_my_column\"", "ALTER TABLE \"my_ns\".\"my_tbl\" MODIFY ( \"my_column\" VARCHAR2(4000) )", "UPDATE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" SET \"indexed\"=0 WHERE \"full_table_name\"='my_ns.my_tbl' AND \"column_name\"='my_column'"); } @@ -1736,17 +1949,15 @@ private void dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexPr String table = "my_tbl"; String indexColumn = "my_column"; JdbcAdmin admin = createJdbcAdminFor(rdbEngine); - PreparedStatement checkStatement = prepareStatementForNamespaceCheck(); PreparedStatement selectStatement = mock(PreparedStatement.class); ResultSet resultSet = mockResultSet( - Arrays.asList( - new GetColumnsResultSetMocker.Row( - "c1", DataType.BOOLEAN.toString(), "PARTITION", null, false), - new GetColumnsResultSetMocker.Row( - indexColumn, DataType.TEXT.toString(), null, null, false))); + new SelectAllFromMetadataTableResultSetMocker.Row( + "c1", DataType.BOOLEAN.toString(), "PARTITION", null, false), + new SelectAllFromMetadataTableResultSetMocker.Row( + indexColumn, DataType.TEXT.toString(), null, null, false)); when(selectStatement.executeQuery()).thenReturn(resultSet); - when(connection.prepareStatement(any())).thenReturn(checkStatement).thenReturn(selectStatement); + when(connection.prepareStatement(any())).thenReturn(selectStatement); Statement statement = mock(Statement.class); @@ -1775,13 +1986,13 @@ private void dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexPr repairTable_WithMissingMetadataTableForX_shouldCreateMetadataTableAndAddMetadataForTable( RdbEngine.MYSQL, "SELECT 1 FROM `my_ns`.`foo_table` LIMIT 1", - "SELECT 1 FROM `" + tableMetadataSchemaName + "`.`metadata` LIMIT 1", - "CREATE SCHEMA IF NOT EXISTS `" + tableMetadataSchemaName + "`", + "SELECT 1 FROM `" + metadataSchemaName + "`.`metadata` LIMIT 1", + "CREATE SCHEMA IF NOT EXISTS `" + metadataSchemaName + "`", "CREATE TABLE IF NOT EXISTS `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata`(`full_table_name` VARCHAR(128),`column_name` VARCHAR(128),`data_type` VARCHAR(20) NOT NULL,`key_type` VARCHAR(20),`clustering_order` VARCHAR(10),`indexed` BOOLEAN NOT NULL,`ordinal_position` INTEGER NOT NULL,PRIMARY KEY (`full_table_name`, `column_name`))", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,false,1)"); } @@ -1792,14 +2003,14 @@ private void dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexPr repairTable_WithMissingMetadataTableForX_shouldCreateMetadataTableAndAddMetadataForTable( RdbEngine.ORACLE, "SELECT 1 FROM \"my_ns\".\"foo_table\" FETCH FIRST 1 ROWS ONLY", - "SELECT 1 FROM \"" + tableMetadataSchemaName + "\".\"metadata\" FETCH FIRST 1 ROWS ONLY", - "CREATE USER \"" + tableMetadataSchemaName + "\" IDENTIFIED BY \"oracle\"", - "ALTER USER \"" + tableMetadataSchemaName + "\" quota unlimited on USERS", + "SELECT 1 FROM \"" + metadataSchemaName + "\".\"metadata\" FETCH FIRST 1 ROWS ONLY", + "CREATE USER \"" + metadataSchemaName + "\" IDENTIFIED BY \"oracle\"", + "ALTER USER \"" + metadataSchemaName + "\" quota unlimited on USERS", "CREATE TABLE \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\"(\"full_table_name\" VARCHAR2(128),\"column_name\" VARCHAR2(128),\"data_type\" VARCHAR2(20) NOT NULL,\"key_type\" VARCHAR2(20),\"clustering_order\" VARCHAR2(10),\"indexed\" NUMBER(1) NOT NULL,\"ordinal_position\" INTEGER NOT NULL,PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,0,1)"); } @@ -1810,13 +2021,13 @@ private void dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexPr repairTable_WithMissingMetadataTableForX_shouldCreateMetadataTableAndAddMetadataForTable( RdbEngine.POSTGRESQL, "SELECT 1 FROM \"my_ns\".\"foo_table\" LIMIT 1", - "SELECT 1 FROM \"" + tableMetadataSchemaName + "\".\"metadata\" LIMIT 1", - "CREATE SCHEMA IF NOT EXISTS \"" + tableMetadataSchemaName + "\"", + "SELECT 1 FROM \"" + metadataSchemaName + "\".\"metadata\" LIMIT 1", + "CREATE SCHEMA IF NOT EXISTS \"" + metadataSchemaName + "\"", "CREATE TABLE IF NOT EXISTS \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\"(\"full_table_name\" VARCHAR(128),\"column_name\" VARCHAR(128),\"data_type\" VARCHAR(20) NOT NULL,\"key_type\" VARCHAR(20),\"clustering_order\" VARCHAR(10),\"indexed\" BOOLEAN NOT NULL,\"ordinal_position\" INTEGER NOT NULL,PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,false,1)"); } @@ -1827,13 +2038,13 @@ private void dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexPr repairTable_WithMissingMetadataTableForX_shouldCreateMetadataTableAndAddMetadataForTable( RdbEngine.SQL_SERVER, "SELECT TOP 1 1 FROM [my_ns].[foo_table]", - "SELECT TOP 1 1 FROM [" + tableMetadataSchemaName + "].[metadata]", - "CREATE SCHEMA [" + tableMetadataSchemaName + "]", + "SELECT TOP 1 1 FROM [" + metadataSchemaName + "].[metadata]", + "CREATE SCHEMA [" + metadataSchemaName + "]", "CREATE TABLE [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata]([full_table_name] VARCHAR(128),[column_name] VARCHAR(128),[data_type] VARCHAR(20) NOT NULL,[key_type] VARCHAR(20),[clustering_order] VARCHAR(10),[indexed] BIT NOT NULL,[ordinal_position] INTEGER NOT NULL,PRIMARY KEY ([full_table_name], [column_name]))", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,0,1)"); } @@ -1844,12 +2055,12 @@ private void dropIndex_forColumnTypeWithRequiredAlterationForX_ShouldDropIndexPr repairTable_WithMissingMetadataTableForX_shouldCreateMetadataTableAndAddMetadataForTable( RdbEngine.SQLITE, "SELECT 1 FROM \"my_ns$foo_table\" LIMIT 1", - "SELECT 1 FROM \"" + tableMetadataSchemaName + "$metadata\" LIMIT 1", + "SELECT 1 FROM \"" + metadataSchemaName + "$metadata\" LIMIT 1", "CREATE TABLE IF NOT EXISTS \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\"(\"full_table_name\" TEXT,\"column_name\" TEXT,\"data_type\" TEXT NOT NULL,\"key_type\" TEXT,\"clustering_order\" TEXT,\"indexed\" BOOLEAN NOT NULL,\"ordinal_position\" INTEGER NOT NULL,PRIMARY KEY (\"full_table_name\", \"column_name\"))", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,FALSE,1)"); } @@ -1896,12 +2107,12 @@ public void repairTable_ExistingMetadataTableForMysql_shouldDeleteThenAddMetadat repairTable_ExistingMetadataTableForX_shouldDeleteThenAddMetadataForTable( RdbEngine.MYSQL, "SELECT 1 FROM `my_ns`.`foo_table` LIMIT 1", - "SELECT 1 FROM `" + tableMetadataSchemaName + "`.`metadata` LIMIT 1", + "SELECT 1 FROM `" + metadataSchemaName + "`.`metadata` LIMIT 1", "DELETE FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name` = 'my_ns.foo_table'", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,false,1)"); } @@ -1911,27 +2122,27 @@ public void repairTable_ExistingMetadataTableForOracle_shouldDeleteThenAddMetada repairTable_ExistingMetadataTableForX_shouldDeleteThenAddMetadataForTable( RdbEngine.ORACLE, "SELECT 1 FROM \"my_ns\".\"foo_table\" FETCH FIRST 1 ROWS ONLY", - "SELECT 1 FROM \"" + tableMetadataSchemaName + "\".\"metadata\" FETCH FIRST 1 ROWS ONLY", + "SELECT 1 FROM \"" + metadataSchemaName + "\".\"metadata\" FETCH FIRST 1 ROWS ONLY", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,0,1)"); } @Test - public void repairTable_ExistingMetadataTableForPosgresql_shouldDeleteThenAddMetadataForTable() + public void repairTable_ExistingMetadataTableForPostgresql_shouldDeleteThenAddMetadataForTable() throws SQLException, ExecutionException { repairTable_ExistingMetadataTableForX_shouldDeleteThenAddMetadataForTable( RdbEngine.POSTGRESQL, "SELECT 1 FROM \"my_ns\".\"foo_table\" LIMIT 1", - "SELECT 1 FROM \"" + tableMetadataSchemaName + "\".\"metadata\" LIMIT 1", + "SELECT 1 FROM \"" + metadataSchemaName + "\".\"metadata\" LIMIT 1", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,false,1)"); } @@ -1941,12 +2152,12 @@ public void repairTable_ExistingMetadataTableForSqlServer_shouldDeleteThenAddMet repairTable_ExistingMetadataTableForX_shouldDeleteThenAddMetadataForTable( RdbEngine.SQL_SERVER, "SELECT TOP 1 1 FROM [my_ns].[foo_table]", - "SELECT TOP 1 1 FROM [" + tableMetadataSchemaName + "].[metadata]", + "SELECT TOP 1 1 FROM [" + metadataSchemaName + "].[metadata]", "DELETE FROM [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] WHERE [full_table_name] = 'my_ns.foo_table'", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,0,1)"); } @@ -1956,12 +2167,12 @@ public void repairTable_ExistingMetadataTableForSqlite_shouldDeleteThenAddMetada repairTable_ExistingMetadataTableForX_shouldDeleteThenAddMetadataForTable( RdbEngine.SQLITE, "SELECT 1 FROM \"my_ns$foo_table\" LIMIT 1", - "SELECT 1 FROM \"" + tableMetadataSchemaName + "$metadata\" LIMIT 1", + "SELECT 1 FROM \"" + metadataSchemaName + "$metadata\" LIMIT 1", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('my_ns.foo_table','c1','TEXT','PARTITION',NULL,FALSE,1)"); } @@ -2066,17 +2277,15 @@ public void addNewColumnToTable_ForMysql_ShouldWorkProperly() addNewColumnToTable_ForX_ShouldWorkProperly( RdbEngine.MYSQL, "SELECT `column_name`,`data_type`,`key_type`,`clustering_order`,`indexed` FROM `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` WHERE `full_table_name`=? ORDER BY `ordinal_position` ASC", "ALTER TABLE `ns`.`table` ADD `c2` INT", - "DELETE FROM `" - + tableMetadataSchemaName - + "`.`metadata` WHERE `full_table_name` = 'ns.table'", + "DELETE FROM `" + metadataSchemaName + "`.`metadata` WHERE `full_table_name` = 'ns.table'", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('ns.table','c1','TEXT','PARTITION',NULL,false,1)", "INSERT INTO `" - + tableMetadataSchemaName + + metadataSchemaName + "`.`metadata` VALUES ('ns.table','c2','INT',NULL,NULL,false,2)"); } @@ -2086,37 +2295,37 @@ public void addNewColumnToTable_ForOracle_ShouldWorkProperly() addNewColumnToTable_ForX_ShouldWorkProperly( RdbEngine.ORACLE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "ALTER TABLE \"ns\".\"table\" ADD \"c2\" INT", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" = 'ns.table'", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('ns.table','c1','TEXT','PARTITION',NULL,0,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('ns.table','c2','INT',NULL,NULL,0,2)"); } @Test - public void addNewColumnToTable_ForPostgrsql_ShouldWorkProperly() + public void addNewColumnToTable_ForPostgresql_ShouldWorkProperly() throws SQLException, ExecutionException { addNewColumnToTable_ForX_ShouldWorkProperly( RdbEngine.POSTGRESQL, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "ALTER TABLE \"ns\".\"table\" ADD \"c2\" INT", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" WHERE \"full_table_name\" = 'ns.table'", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('ns.table','c1','TEXT','PARTITION',NULL,false,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "\".\"metadata\" VALUES ('ns.table','c2','INT',NULL,NULL,false,2)"); } @@ -2126,17 +2335,15 @@ public void addNewColumnToTable_ForSqlServer_ShouldWorkProperly() addNewColumnToTable_ForX_ShouldWorkProperly( RdbEngine.SQL_SERVER, "SELECT [column_name],[data_type],[key_type],[clustering_order],[indexed] FROM [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] WHERE [full_table_name]=? ORDER BY [ordinal_position] ASC", "ALTER TABLE [ns].[table] ADD [c2] INT", - "DELETE FROM [" - + tableMetadataSchemaName - + "].[metadata] WHERE [full_table_name] = 'ns.table'", + "DELETE FROM [" + metadataSchemaName + "].[metadata] WHERE [full_table_name] = 'ns.table'", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('ns.table','c1','TEXT','PARTITION',NULL,0,1)", "INSERT INTO [" - + tableMetadataSchemaName + + metadataSchemaName + "].[metadata] VALUES ('ns.table','c2','INT',NULL,NULL,0,2)"); } @@ -2146,17 +2353,17 @@ public void addNewColumnToTable_ForSqlite_ShouldWorkProperly() addNewColumnToTable_ForX_ShouldWorkProperly( RdbEngine.SQLITE, "SELECT \"column_name\",\"data_type\",\"key_type\",\"clustering_order\",\"indexed\" FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\"=? ORDER BY \"ordinal_position\" ASC", "ALTER TABLE \"ns$table\" ADD \"c2\" INT", "DELETE FROM \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" WHERE \"full_table_name\" = 'ns.table'", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('ns.table','c1','TEXT','PARTITION',NULL,FALSE,1)", "INSERT INTO \"" - + tableMetadataSchemaName + + metadataSchemaName + "$metadata\" VALUES ('ns.table','c2','INT',NULL,NULL,FALSE,2)"); } @@ -2169,15 +2376,14 @@ private void addNewColumnToTable_ForX_ShouldWorkProperly( String currentColumn = "c1"; String newColumn = "c2"; - PreparedStatement checkStatement = prepareStatementForNamespaceCheck(); PreparedStatement selectStatement = mock(PreparedStatement.class); ResultSet resultSet = mockResultSet( - Collections.singletonList( - new Row(currentColumn, DataType.TEXT.toString(), "PARTITION", null, false))); + new SelectAllFromMetadataTableResultSetMocker.Row( + currentColumn, DataType.TEXT.toString(), "PARTITION", null, false)); when(selectStatement.executeQuery()).thenReturn(resultSet); - when(connection.prepareStatement(any())).thenReturn(checkStatement).thenReturn(selectStatement); + when(connection.prepareStatement(any())).thenReturn(selectStatement); List expectedStatements = new ArrayList<>(); for (int i = 0; i < expectedSqlStatements.length; i++) { Statement expectedStatement = mock(Statement.class); @@ -2202,6 +2408,70 @@ private void addNewColumnToTable_ForX_ShouldWorkProperly( } } + @Test + public void getNamespaceNames_forMysql_ShouldReturnNamespaceNames() throws Exception { + getNamespaceNames_forX_ShouldReturnNamespaceNames( + RdbEngine.MYSQL, "SELECT * FROM `" + metadataSchemaName + "`.`namespaces`"); + } + + @Test + public void getNamespaceNames_forPostgresql_ShouldReturnNamespaceNames() throws Exception { + getNamespaceNames_forX_ShouldReturnNamespaceNames( + RdbEngine.POSTGRESQL, "SELECT * FROM \"" + metadataSchemaName + "\".\"namespaces\""); + } + + @Test + public void getNamespaceNames_forSqlServer_ShouldReturnNamespaceNames() throws Exception { + getNamespaceNames_forX_ShouldReturnNamespaceNames( + RdbEngine.SQL_SERVER, "SELECT * FROM [" + metadataSchemaName + "].[namespaces]"); + } + + @Test + public void getNamespaceNames_forOracle_ShouldReturnNamespaceNames() throws Exception { + getNamespaceNames_forX_ShouldReturnNamespaceNames( + RdbEngine.ORACLE, "SELECT * FROM \"" + metadataSchemaName + "\".\"namespaces\""); + } + + @Test + public void getNamespaceNames_forSqlLite_ShouldReturnNamespaceNames() throws Exception { + getNamespaceNames_forX_ShouldReturnNamespaceNames( + RdbEngine.SQLITE, "SELECT * FROM \"" + metadataSchemaName + "$" + "namespaces\""); + } + + private void getNamespaceNames_forX_ShouldReturnNamespaceNames( + RdbEngine rdbEngine, String expectedSelectStatement) throws Exception { + // Arrange + String namespace1 = "ns1"; + String namespace2 = "ns2"; + ResultSet resultSet = + mockResultSet( + new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row(namespace1), + new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row(namespace2)); + PreparedStatement mockPreparedStatement = mock(PreparedStatement.class); + when(dataSource.getConnection()).thenReturn(connection); + when(connection.prepareStatement(anyString())).thenReturn(mockPreparedStatement); + when(mockPreparedStatement.executeQuery()).thenReturn(resultSet); + + JdbcAdmin admin = createJdbcAdminFor(rdbEngine); + + // Act + Set actualNamespaceNames = admin.getNamespaceNames(); + + // Assert + verify(connection).prepareStatement(expectedSelectStatement); + verify(mockPreparedStatement).executeQuery(); + assertThat(actualNamespaceNames).containsOnly(namespace1, namespace2); + } + + private List prepareMockStatements(int count) { + List statements = new ArrayList<>(); + for (int i = 0; i < count; i++) { + Statement statement = mock(Statement.class); + statements.add(statement); + } + return statements; + } + @Test public void getImportTableMetadata_ForX_ShouldWorkProperly() throws SQLException, ExecutionException { @@ -2539,21 +2809,8 @@ public void importTable_ForSQLite_ShouldThrowUnsupportedOperationException() { assertThat(thrown).isInstanceOf(UnsupportedOperationException.class); } - private PreparedStatement prepareStatementForNamespaceCheck() throws SQLException { - return prepareStatementForNamespaceCheck(true); - } - - private PreparedStatement prepareStatementForNamespaceCheck(boolean exists) throws SQLException { - PreparedStatement statement = mock(PreparedStatement.class); - ResultSet results = mock(ResultSet.class); - doNothing().when(statement).setString(anyInt(), anyString()); - when(statement.executeQuery()).thenReturn(results); - when(results.next()).thenReturn(exists); - return statement; - } - private String prepareSqlForMetadataTableCheck(RdbEngine rdbEngine) { - return prepareSqlForTableCheck(rdbEngine, tableMetadataSchemaName, "metadata"); + return prepareSqlForTableCheck(rdbEngine, metadataSchemaName, "metadata"); } private String prepareSqlForTableCheck(RdbEngine rdbEngine, String namespace, String table) { @@ -2597,17 +2854,17 @@ private List prepareSqlForCreateSchemaStatements(RdbEngine rdbEngine) { statements.add( "CREATE SCHEMA " + (rdbEngine.equals(RdbEngine.SQL_SERVER) ? "" : "IF NOT EXISTS ") - + rdbEngineStrategy.enclose(tableMetadataSchemaName)); + + rdbEngineStrategy.enclose(metadataSchemaName)); break; case ORACLE: statements.add( "CREATE USER " - + rdbEngineStrategy.enclose(tableMetadataSchemaName) + + rdbEngineStrategy.enclose(metadataSchemaName) + " IDENTIFIED BY " + rdbEngineStrategy.enclose("oracle")); statements.add( "ALTER USER " - + rdbEngineStrategy.enclose(tableMetadataSchemaName) + + rdbEngineStrategy.enclose(metadataSchemaName) + " quota unlimited on USERS"); break; default: @@ -2624,7 +2881,7 @@ private String prepareSqlForCreateMetadataTable(RdbEngine rdbEngine) { sql.append("IF NOT EXISTS "); } - sql.append(rdbEngineStrategy.encloseFullTableName(tableMetadataSchemaName, "metadata")) + sql.append(rdbEngineStrategy.encloseFullTableName(metadataSchemaName, "metadata")) .append("(") .append(rdbEngineStrategy.enclose("full_table_name")) .append(" ") @@ -2701,7 +2958,7 @@ private String prepareSqlForInsertMetadata( Integer.toString(ordinal)); return "INSERT INTO " - + rdbEngineStrategy.encloseFullTableName(tableMetadataSchemaName, "metadata") + + rdbEngineStrategy.encloseFullTableName(metadataSchemaName, "metadata") + " VALUES (" + String.join(",", values) + ")"; @@ -2722,14 +2979,15 @@ private String getBooleanString(RdbEngine rdbEngine, boolean value) { private RdbEngineStrategy getRdbEngineStrategy(RdbEngine rdbEngine) { return RDB_ENGINES.getOrDefault(rdbEngine, RdbEngineFactory.create("jdbc:mysql:")); } + // Utility class used to mock ResultSet for a "select * from" query on the metadata table + static class SelectAllFromMetadataTableResultSetMocker + implements org.mockito.stubbing.Answer { - // Utility class used to mock ResultSet for getTableMetadata test - static class GetColumnsResultSetMocker implements org.mockito.stubbing.Answer { - - final List rows; + final List rows; int row = -1; - public GetColumnsResultSetMocker(List rows) { + public SelectAllFromMetadataTableResultSetMocker( + List rows) { this.rows = rows; } @@ -2739,7 +2997,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { if (row >= rows.size()) { return false; } - GetColumnsResultSetMocker.Row currentRow = rows.get(row); + SelectAllFromMetadataTableResultSetMocker.Row currentRow = rows.get(row); ResultSet mock = (ResultSet) invocation.getMock(); when(mock.getString(JdbcAdmin.METADATA_COL_COLUMN_NAME)).thenReturn(currentRow.columnName); when(mock.getString(JdbcAdmin.METADATA_COL_DATA_TYPE)).thenReturn(currentRow.dataType); @@ -2773,13 +3031,16 @@ public Row( } } - // Utility class used to mock ResultSet for getTablesNames test - static class GetTablesNamesResultSetMocker implements org.mockito.stubbing.Answer { + // Utility class used to mock ResultSet for a "select full_table_name from" query on the metadata + // table + static class SelectFullTableNameFromMetadataTableResultSetMocker + implements org.mockito.stubbing.Answer { - final List rows; + final List rows; int row = -1; - public GetTablesNamesResultSetMocker(List rows) { + public SelectFullTableNameFromMetadataTableResultSetMocker( + List rows) { this.rows = rows; } @@ -2789,7 +3050,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { if (row >= rows.size()) { return false; } - GetTablesNamesResultSetMocker.Row currentRow = rows.get(row); + SelectFullTableNameFromMetadataTableResultSetMocker.Row currentRow = rows.get(row); ResultSet mock = (ResultSet) invocation.getMock(); when(mock.getString(JdbcAdmin.METADATA_COL_FULL_TABLE_NAME)) .thenReturn(currentRow.fullTableName); @@ -2805,4 +3066,40 @@ public Row(String fullTableName) { } } } + + // Utility class used to mock ResultSet for a "select namespace_name from" query on the namespace + // table + static class SelectNamespaceNameFromNamespaceTableResultSetMocker + implements org.mockito.stubbing.Answer { + + final List rows; + int row = -1; + + public SelectNamespaceNameFromNamespaceTableResultSetMocker( + List rows) { + this.rows = rows; + } + + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + row++; + if (row >= rows.size()) { + return false; + } + SelectNamespaceNameFromNamespaceTableResultSetMocker.Row currentRow = rows.get(row); + ResultSet mock = (ResultSet) invocation.getMock(); + when(mock.getString(JdbcAdmin.NAMESPACE_COL_NAMESPACE_NAME)) + .thenReturn(currentRow.namespaceName); + return true; + } + + static class Row { + + final String namespaceName; + + public Row(String namespaceName) { + this.namespaceName = namespaceName; + } + } + } } diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminWithTableMetadataSchemaConfigTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminWithMetadataSchemaConfigTest.java similarity index 67% rename from core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminWithTableMetadataSchemaConfigTest.java rename to core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminWithMetadataSchemaConfigTest.java index 27f3f031f1..4ea805d0b4 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminWithTableMetadataSchemaConfigTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminWithMetadataSchemaConfigTest.java @@ -2,7 +2,7 @@ import java.util.Optional; -public class JdbcAdminWithTableMetadataSchemaConfigTest extends JdbcAdminTestBase { +public class JdbcAdminWithMetadataSchemaConfigTest extends JdbcAdminTestBase { @Override Optional getTableMetadataSchemaConfig() { diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcConfigTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcConfigTest.java index 769c485584..78c258dae6 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcConfigTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcConfigTest.java @@ -13,7 +13,7 @@ public class JdbcConfigTest { private static final String ANY_USERNAME = "root"; private static final String ANY_PASSWORD = "mysql"; private static final String JDBC_STORAGE = "jdbc"; - private static final String ANY_TABLE_METADATA_SCHEMA = "any_schema"; + private static final String ANY_METADATA_SCHEMA = "any_schema"; @Test public void constructor_AllPropertiesGiven_ShouldLoadProperly() { @@ -29,7 +29,7 @@ public void constructor_AllPropertiesGiven_ShouldLoadProperly() { props.setProperty(JdbcConfig.PREPARED_STATEMENTS_POOL_ENABLED, "true"); props.setProperty(JdbcConfig.PREPARED_STATEMENTS_POOL_MAX_OPEN, "300"); props.setProperty(JdbcConfig.ISOLATION_LEVEL, Isolation.SERIALIZABLE.name()); - props.setProperty(JdbcConfig.TABLE_METADATA_SCHEMA, ANY_TABLE_METADATA_SCHEMA); + props.setProperty(JdbcConfig.METADATA_SCHEMA, ANY_METADATA_SCHEMA); props.setProperty(JdbcConfig.TABLE_METADATA_CONNECTION_POOL_MIN_IDLE, "100"); props.setProperty(JdbcConfig.TABLE_METADATA_CONNECTION_POOL_MAX_IDLE, "200"); props.setProperty(JdbcConfig.TABLE_METADATA_CONNECTION_POOL_MAX_TOTAL, "300"); @@ -53,8 +53,8 @@ public void constructor_AllPropertiesGiven_ShouldLoadProperly() { assertThat(config.getPreparedStatementsPoolMaxOpen()).isEqualTo(300); assertThat(config.getIsolation()).isPresent(); assertThat(config.getIsolation().get()).isEqualTo(Isolation.SERIALIZABLE); - assertThat(config.getTableMetadataSchema()).isPresent(); - assertThat(config.getTableMetadataSchema().get()).isEqualTo(ANY_TABLE_METADATA_SCHEMA); + assertThat(config.getMetadataSchema()).isPresent(); + assertThat(config.getMetadataSchema().get()).isEqualTo(ANY_METADATA_SCHEMA); assertThat(config.getTableMetadataConnectionPoolMinIdle()).isEqualTo(100); assertThat(config.getTableMetadataConnectionPoolMaxIdle()).isEqualTo(200); assertThat(config.getTableMetadataConnectionPoolMaxTotal()).isEqualTo(300); @@ -93,7 +93,7 @@ public void constructor_AllPropertiesGiven_ShouldLoadProperly() { assertThat(config.getPreparedStatementsPoolMaxOpen()) .isEqualTo(JdbcConfig.DEFAULT_PREPARED_STATEMENTS_POOL_MAX_OPEN); assertThat(config.getIsolation()).isNotPresent(); - assertThat(config.getTableMetadataSchema()).isNotPresent(); + assertThat(config.getMetadataSchema()).isNotPresent(); assertThat(config.getTableMetadataConnectionPoolMinIdle()) .isEqualTo(JdbcConfig.DEFAULT_TABLE_METADATA_CONNECTION_POOL_MIN_IDLE); assertThat(config.getTableMetadataConnectionPoolMaxIdle()) @@ -218,4 +218,44 @@ public void constructor_PropertiesWithWrongStorage_ShouldThrowIllegalArgumentExc assertThatThrownBy(() -> new JdbcConfig(new DatabaseConfig(props))) .isInstanceOf(IllegalArgumentException.class); } + + @Test + public void + constructor_WithTableMetadataSchemaAndMetadataSchemaGiven_ShouldThrowIllegalArgumentException() { + // Arrange + Properties props = new Properties(); + props.setProperty(DatabaseConfig.CONTACT_POINTS, ANY_JDBC_URL); + props.setProperty(DatabaseConfig.USERNAME, ANY_USERNAME); + props.setProperty(DatabaseConfig.PASSWORD, ANY_PASSWORD); + props.setProperty(DatabaseConfig.STORAGE, JDBC_STORAGE); + props.setProperty(JdbcConfig.TABLE_METADATA_SCHEMA, "aaa"); + props.setProperty(JdbcConfig.METADATA_SCHEMA, "bbb"); + + // Act Assert + assertThatThrownBy(() -> new JdbcConfig(new DatabaseConfig(props))) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructor_PropertiesWithTableMetadataSchemaGiven_ShouldLoadProperly() { + // Arrange + Properties props = new Properties(); + props.setProperty(DatabaseConfig.CONTACT_POINTS, ANY_JDBC_URL); + props.setProperty(DatabaseConfig.USERNAME, ANY_USERNAME); + props.setProperty(DatabaseConfig.PASSWORD, ANY_PASSWORD); + props.setProperty(DatabaseConfig.STORAGE, JDBC_STORAGE); + props.setProperty(JdbcConfig.TABLE_METADATA_SCHEMA, ANY_METADATA_SCHEMA); + + // Act + JdbcConfig config = new JdbcConfig(new DatabaseConfig(props)); + + // Assert + assertThat(config.getJdbcUrl()).isEqualTo(ANY_JDBC_URL); + assertThat(config.getUsername().isPresent()).isTrue(); + assertThat(config.getUsername().get()).isEqualTo(ANY_USERNAME); + assertThat(config.getPassword().isPresent()).isTrue(); + assertThat(config.getPassword().get()).isEqualTo(ANY_PASSWORD); + assertThat(config.getMetadataSchema()).isPresent(); + assertThat(config.getMetadataSchema().get()).isEqualTo(ANY_METADATA_SCHEMA); + } } diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/RdbEngineSqliteTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/RdbEngineSqliteTest.java index 2ee72314d9..a9f71fa386 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/RdbEngineSqliteTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/RdbEngineSqliteTest.java @@ -121,12 +121,12 @@ void isConflictError_False() { } @Test - void isValidTableName_True() { - assertTrue(rdbEngine.isValidTableName("a_b")); + void isValidNamespaceOrTableName_True() { + assertTrue(rdbEngine.isValidNamespaceOrTableName("a_b")); } @Test - void isValidTableName_False_WhenContainsNamespaceSeparator() { - assertFalse(rdbEngine.isValidTableName("a$b")); + void isValidNamespaceOrTableName_False_WhenContainsNamespaceSeparator() { + assertFalse(rdbEngine.isValidNamespaceOrTableName("a$b")); } } diff --git a/core/src/test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTest.java b/core/src/test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTest.java index d3417dd25f..15413dec7c 100644 --- a/core/src/test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/multistorage/MultiStorageAdminTest.java @@ -1,14 +1,19 @@ package com.scalar.db.storage.multistorage; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.scalar.db.api.DistributedStorageAdmin; import com.scalar.db.api.TableMetadata; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; @@ -488,4 +493,81 @@ public void addNewColumnToTable_ForTable1InNamespace2_ShouldCallAddNewColumnOfAd // Assert verify(admin2).addNewColumnToTable(namespace, table, column, dataType); } + + @Test + public void + getNamespaceNames_WithExistingNamespacesNotInMapping_ShouldReturnExistingNamespacesInMappingAndFromDefaultAdmin() + throws ExecutionException { + // Arrange + Map namespaceAdminMap = new HashMap<>(); + namespaceAdminMap.put("ns1", admin1); + namespaceAdminMap.put("ns2", admin2); + namespaceAdminMap.put("ns3", admin2); + DistributedStorageAdmin defaultAdmin = admin3; + multiStorageAdmin = + new MultiStorageAdmin(Collections.emptyMap(), namespaceAdminMap, defaultAdmin); + + when(admin1.getNamespaceNames()).thenReturn(ImmutableSet.of("ns1", "ns2")); + when(admin2.getNamespaceNames()).thenReturn(ImmutableSet.of("ns3")); + when(admin3.getNamespaceNames()).thenReturn(ImmutableSet.of("ns4", "ns5")); + + // Act + Set actualNamespaces = multiStorageAdmin.getNamespaceNames(); + + // Assert + verify(admin1).getNamespaceNames(); + verify(admin2).getNamespaceNames(); + verify(admin3).getNamespaceNames(); + assertThat(actualNamespaces).containsOnly("ns1", "ns3", "ns4", "ns5"); + } + + @Test + public void getNamespaceNames_WithNamespaceInMappingButNotExisting_ShouldReturnEmptySet() + throws ExecutionException { + // Arrange + Map namespaceAdminMap = new HashMap<>(); + namespaceAdminMap.put("ns1", admin1); + namespaceAdminMap.put("ns2", admin2); + DistributedStorageAdmin defaultAdmin = admin3; + multiStorageAdmin = + new MultiStorageAdmin(Collections.emptyMap(), namespaceAdminMap, defaultAdmin); + + when(admin1.getNamespaceNames()).thenReturn(Collections.emptySet()); + when(admin2.getNamespaceNames()).thenReturn(Collections.emptySet()); + when(admin3.getNamespaceNames()).thenReturn(Collections.emptySet()); + + // Act + Set actualNamespaces = multiStorageAdmin.getNamespaceNames(); + + // Assert + verify(admin1).getNamespaceNames(); + verify(admin2).getNamespaceNames(); + verify(admin3).getNamespaceNames(); + assertThat(actualNamespaces).isEmpty(); + } + + @Test + public void getNamespaceNames_WithExistingNamespaceButNotInMapping_ShouldReturnEmptySet() + throws ExecutionException { + // Arrange + Map namespaceAdminMap = new HashMap<>(); + namespaceAdminMap.put("ns1", admin1); + namespaceAdminMap.put("ns2", admin2); + DistributedStorageAdmin defaultAdmin = admin3; + multiStorageAdmin = + new MultiStorageAdmin(Collections.emptyMap(), namespaceAdminMap, defaultAdmin); + + when(admin1.getNamespaceNames()).thenReturn(ImmutableSet.of("ns2")); + when(admin2.getNamespaceNames()).thenReturn(Collections.emptySet()); + when(admin3.getNamespaceNames()).thenReturn(Collections.emptySet()); + + // Act + Set actualNamespaces = multiStorageAdmin.getNamespaceNames(); + + // Assert + verify(admin1).getNamespaceNames(); + verify(admin2).getNamespaceNames(); + verify(admin3).getNamespaceNames(); + assertThat(actualNamespaces).isEmpty(); + } } diff --git a/core/src/test/java/com/scalar/db/storage/rpc/GrpcAdminTest.java b/core/src/test/java/com/scalar/db/storage/rpc/GrpcAdminTest.java index 89d9dc0b27..405d98a609 100644 --- a/core/src/test/java/com/scalar/db/storage/rpc/GrpcAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/rpc/GrpcAdminTest.java @@ -1,7 +1,7 @@ package com.scalar.db.storage.rpc; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; @@ -340,15 +340,14 @@ public void unsupportedOperations_ShouldThrowUnsupportedException() { String table = "tbl"; String column = "col"; - // Act - Throwable thrown1 = catchThrowable(() -> admin.getImportTableMetadata(namespace, table)); - Throwable thrown2 = - catchThrowable(() -> admin.addRawColumnToTable(namespace, table, column, DataType.INT)); - Throwable thrown3 = catchThrowable(() -> admin.importTable(namespace, table)); - - // Assert - assertThat(thrown1).isInstanceOf(UnsupportedOperationException.class); - assertThat(thrown2).isInstanceOf(UnsupportedOperationException.class); - assertThat(thrown3).isInstanceOf(UnsupportedOperationException.class); + // Act Assert + assertThatThrownBy(() -> admin.getImportTableMetadata(namespace, table)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> admin.addRawColumnToTable(namespace, table, column, DataType.INT)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> admin.importTable(namespace, table)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> admin.getNamespaceNames()) + .isInstanceOf(UnsupportedOperationException.class); } } diff --git a/core/src/test/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminTestBase.java b/core/src/test/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminTestBase.java index 4bb3111d93..1633e150b0 100644 --- a/core/src/test/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminTestBase.java +++ b/core/src/test/java/com/scalar/db/transaction/consensuscommit/ConsensusCommitAdminTestBase.java @@ -657,4 +657,18 @@ public void importTable_WithTableAlreadyExists_ShouldThrowIllegalArgumentExcepti // Assert assertThat(thrown).isInstanceOf(IllegalArgumentException.class); } + + @Test + public void getNamespacesNames_ShouldCallJdbcAdminProperly() throws ExecutionException { + // Arrange + Set namespaces = ImmutableSet.of("n1", "n2", coordinatorNamespaceName); + when(distributedStorageAdmin.getNamespaceNames()).thenReturn(namespaces); + + // Act + Set actualNamespaces = admin.getNamespaceNames(); + + // Assert + verify(distributedStorageAdmin).getNamespaceNames(); + assertThat(actualNamespaces).containsOnly("n1", "n2"); + } } diff --git a/core/src/test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminTest.java b/core/src/test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminTest.java index cd6088b5a3..1d320eeb14 100644 --- a/core/src/test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminTest.java +++ b/core/src/test/java/com/scalar/db/transaction/jdbc/JdbcTransactionAdminTest.java @@ -240,4 +240,18 @@ public void importTable_ShouldCallJdbcAdminProperly() throws ExecutionException // Assert verify(jdbcAdmin).importTable(namespace, table); } + + @Test + public void getNamespaceNames_ShouldCallJdbcAdminProperly() throws ExecutionException { + // Arrange + Set namespaceNames = ImmutableSet.of("n1", "n2"); + when(jdbcAdmin.getNamespaceNames()).thenReturn(namespaceNames); + + // Act + Set actualNamespaces = admin.getNamespaceNames(); + + // Assert + verify(jdbcAdmin).getNamespaceNames(); + assertThat(actualNamespaces).containsOnly("n1", "n2"); + } } diff --git a/core/src/test/java/com/scalar/db/transaction/rpc/GrpcTransactionAdminTest.java b/core/src/test/java/com/scalar/db/transaction/rpc/GrpcTransactionAdminTest.java index b5bd84caa9..e46f701ce1 100644 --- a/core/src/test/java/com/scalar/db/transaction/rpc/GrpcTransactionAdminTest.java +++ b/core/src/test/java/com/scalar/db/transaction/rpc/GrpcTransactionAdminTest.java @@ -1,6 +1,7 @@ package com.scalar.db.transaction.rpc; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; @@ -411,4 +412,17 @@ public void addNewColumnToTable_CalledWithProperArguments_StubShouldBeCalledProp .setColumnType(com.scalar.db.rpc.DataType.DATA_TYPE_TEXT) .build()); } + + @Test + public void unsupportedOperations_ShouldThrowUnsupportedException() { + // Arrange + String namespace = "sample_ns"; + String table = "tbl"; + + // Act Assert + assertThatThrownBy(() -> admin.importTable(namespace, table)) + .isInstanceOf(UnsupportedOperationException.class); + assertThatThrownBy(() -> admin.getNamespaceNames()) + .isInstanceOf(UnsupportedOperationException.class); + } } diff --git a/docs/api-guide.md b/docs/api-guide.md index 6f8d97fd3f..ec433e5e71 100644 --- a/docs/api-guide.md +++ b/docs/api-guide.md @@ -203,6 +203,23 @@ boolean ifExists = true; admin.dropNamespace("ns", ifExists); ``` +### Get the namespaces + +You can get the existing namespaces as follows: + +```java +Set namespaces = admin.getNamespaceNames(); +``` + +### Get the tables of a namespace + +You can get the tables of a namespace as follows: + +```java +// Get the tables of the namespace "ns" +Set tables = admin.getNamespaceTableNames("ns"); +``` + ### Get a table metadata You can get a table metadata as follows: diff --git a/docs/configurations.md b/docs/configurations.md index 74c9b8793e..123360f13a 100644 --- a/docs/configurations.md +++ b/docs/configurations.md @@ -49,34 +49,35 @@ The following describes the configurations available for each storage. - For Cassandra, the following configurations are available: -| Name | Description | Default | -|----------------------------|-----------------------------------------|---------| -| `scalar.db.storage` | `cassandra` must be specified. | - | -| `scalar.db.contact_points` | Comma-separated contact points. | | -| `scalar.db.contact_port` | Port number for all the contact points. | | -| `scalar.db.username` | Username to access the database. | | -| `scalar.db.password` | Password to access the database. | | +| Name | Description | Default | +|-----------------------------------------|-----------------------------------------------------------------------|------------| +| `scalar.db.storage` | `cassandra` must be specified. | - | +| `scalar.db.contact_points` | Comma-separated contact points. | | +| `scalar.db.contact_port` | Port number for all the contact points. | | +| `scalar.db.username` | Username to access the database. | | +| `scalar.db.password` | Password to access the database. | | +| `scalar.db.cassandra.metadata.keyspace` | Keyspace name for the namespace and table metadata used for ScalarDB. | `scalardb` | - For Cosmos DB for NoSQL, the following configurations are available: -| Name | Description | Default | -|--------------------------------------------|----------------------------------------------------------------------------------------------------------|------------| -| `scalar.db.storage` | `cosmos` must be specified. | - | -| `scalar.db.contact_points` | Azure Cosmos DB for NoSQL endpoint with which ScalarDB should communicate. | | -| `scalar.db.password` | Either a master or read-only key used to perform authentication for accessing Azure Cosmos DB for NoSQL. | | -| `scalar.db.cosmos.table_metadata.database` | Database name for the table metadata used for ScalarDB. | `scalardb` | +| Name | Description | Default | +|--------------------------------------|----------------------------------------------------------------------------------------------------------|------------| +| `scalar.db.storage` | `cosmos` must be specified. | - | +| `scalar.db.contact_points` | Azure Cosmos DB for NoSQL endpoint with which ScalarDB should communicate. | | +| `scalar.db.password` | Either a master or read-only key used to perform authentication for accessing Azure Cosmos DB for NoSQL. | | +| `scalar.db.cosmos.metadata.database` | Database name for the namespace and table metadata used for ScalarDB. | `scalardb` | - For DynamoDB, the following configurations are available: -| Name | Description | Default | -|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| -| `scalar.db.storage` | `dynamo` must be specified. | - | -| `scalar.db.contact_points` | AWS region with which ScalarDB should communicate (e.g., `us-east-1`). | | -| `scalar.db.username` | AWS access key used to identify the user interacting with AWS. | | -| `scalar.db.password` | AWS secret access key used to authenticate the user interacting with AWS. | | -| `scalar.db.dynamo.endpoint_override` | Amazon DynamoDB endpoint with which ScalarDB should communicate. This is primarily used for testing with a local instance instead of an AWS service. | | -| `scalar.db.dynamo.table_metadata.namespace` | Namespace name for the table metadata used for ScalarDB. | `scalardb` | -| `scalar.db.dynamo.namespace.prefix` | Prefix for the user namespaces and metadata namespace names. Since AWS requires having unique tables names in a single AWS region, this is useful if you want to use multiple ScalarDB environments (development, production, etc.) in a single AWS region. | | +| Name | Description | Default | +|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------| +| `scalar.db.storage` | `dynamo` must be specified. | - | +| `scalar.db.contact_points` | AWS region with which ScalarDB should communicate (e.g., `us-east-1`). | | +| `scalar.db.username` | AWS access key used to identify the user interacting with AWS. | | +| `scalar.db.password` | AWS secret access key used to authenticate the user interacting with AWS. | | +| `scalar.db.dynamo.endpoint_override` | Amazon DynamoDB endpoint with which ScalarDB should communicate. This is primarily used for testing with a local instance instead of an AWS service. | | +| `scalar.db.dynamo.metadata.namespace` | Namespace name for the namespace and table metadata used for ScalarDB. | `scalardb` | +| `scalar.db.dynamo.namespace.prefix` | Prefix for the user namespaces and metadata namespace names. Since AWS requires having unique tables names in a single AWS region, this is useful if you want to use multiple ScalarDB environments (development, production, etc.) in a single AWS region. | | - For JDBC databases, the following configurations are available: @@ -92,7 +93,7 @@ The following describes the configurations available for each storage. | `scalar.db.jdbc.prepared_statements_pool.enabled` | Setting this property to `true` enables prepared statement pooling. | `false` | | `scalar.db.jdbc.prepared_statements_pool.max_open` | Maximum number of open statements that can be allocated from the statement pool at the same time, or negative for no limit. | `-1` | | `scalar.db.jdbc.isolation_level` | Isolation level for JDBC. `READ_UNCOMMITTED`, `READ_COMMITTED`, `REPEATABLE_READ`, or `SERIALIZABLE` can be specified. | Underlying-database specific | -| `scalar.db.jdbc.table_metadata.schema` | Schema name for the table metadata used for ScalarDB. | `scalardb` | +| `scalar.db.jdbc.metadata.schema` | Schema name for the namespace and table metadata used for ScalarDB. | `scalardb` | | `scalar.db.jdbc.table_metadata.connection_pool.min_idle` | Minimum number of idle connections in the connection pool for the table metadata. | `5` | | `scalar.db.jdbc.table_metadata.connection_pool.max_idle` | Maximum number of connections that can remain idle in the connection pool for the table metadata. | `10` | | `scalar.db.jdbc.table_metadata.connection_pool.max_total` | Maximum total number of idle and borrowed connections that can be active at the same time for the connection pool for the table metadata. Use a negative value for no limit. | `25` | diff --git a/docs/storage-abstraction.md b/docs/storage-abstraction.md index dd9bf3483e..8624b3beae 100644 --- a/docs/storage-abstraction.md +++ b/docs/storage-abstraction.md @@ -348,6 +348,23 @@ boolean ifExists = true; admin.dropNamespace("ns", ifExists); ``` +### Get the namespaces + +You can get the existing namespaces as follows: + +```java +Set namespaces = admin.getNamespaceNames(); +``` + +### Get the tables of a namespace + +You can get the tables of a namespace as follows: + +```java +// Get the tables of the namespace "ns" +Set tables = admin.getNamespaceTableNames("ns"); +``` + #### Get a table metadata You can get a table metadata as follows: diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java index 66fa153573..8ae757b631 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java @@ -79,6 +79,7 @@ protected abstract Map createExistingDatabaseWithAllDataT @Test public void importTable_ShouldWorkProperly() throws Exception { // Arrange + admin.createNamespace(getNamespace(), getCreationOptions()); tables.putAll(createExistingDatabaseWithAllDataTypes()); // Act Assert @@ -95,7 +96,11 @@ public void importTable_ShouldWorkProperly() throws Exception { } @Test - public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() { + public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + // Arrange + admin.createNamespace(getNamespace(), getCreationOptions()); + // Act Assert assertThatThrownBy(() -> admin.importTable(getNamespace(), "unsupported_db")) .isInstanceOf(UnsupportedOperationException.class); diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java index 5e79be6945..08e38c61f6 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminIntegrationTestBase.java @@ -208,7 +208,7 @@ public void createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperl // Arrange // Act - admin.createNamespace(namespace3); + admin.createNamespace(namespace3, getCreationOptions()); // Assert assertThat(admin.namespaceExists(namespace3)).isTrue(); @@ -222,7 +222,7 @@ public void createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentExcep // Arrange // Act Assert - assertThatThrownBy(() -> admin.createNamespace(namespace1)) + assertThatThrownBy(() -> admin.createNamespace(namespace1, getCreationOptions())) .isInstanceOf(IllegalArgumentException.class); } @@ -231,7 +231,8 @@ public void createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyEx // Arrange // Act Assert - assertThatCode(() -> admin.createNamespace(namespace1, true)).doesNotThrowAnyException(); + assertThatCode(() -> admin.createNamespace(namespace1, true, getCreationOptions())) + .doesNotThrowAnyException(); } @Test @@ -239,7 +240,7 @@ public void dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly() throws ExecutionException { try { // Arrange - admin.createNamespace(namespace3); + admin.createNamespace(namespace3, getCreationOptions()); // Act admin.dropNamespace(namespace3); @@ -265,8 +266,8 @@ public void dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentExcepti throws ExecutionException { try { // Arrange - admin.createNamespace(namespace3); - admin.createTable(namespace3, TABLE1, TABLE_METADATA); + admin.createNamespace(namespace3, getCreationOptions()); + admin.createTable(namespace3, TABLE1, TABLE_METADATA, getCreationOptions()); // Act Assert assertThatThrownBy(() -> admin.dropNamespace(namespace3)) @@ -307,7 +308,8 @@ public void createTable_ForExistingTable_ShouldThrowIllegalArgumentException() { // Arrange // Act Assert - assertThatThrownBy(() -> admin.createTable(namespace1, TABLE1, TABLE_METADATA)) + assertThatThrownBy( + () -> admin.createTable(namespace1, TABLE1, TABLE_METADATA, getCreationOptions())) .isInstanceOf(IllegalArgumentException.class); } @@ -316,7 +318,8 @@ public void createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentExcept // Arrange // Act Assert - assertThatThrownBy(() -> admin.createTable(namespace3, TABLE1, TABLE_METADATA)) + assertThatThrownBy( + () -> admin.createTable(namespace3, TABLE1, TABLE_METADATA, getCreationOptions())) .isInstanceOf(IllegalArgumentException.class); } @@ -325,7 +328,8 @@ public void createTable_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyExcept // Arrange // Act Assert - assertThatCode(() -> admin.createTable(namespace1, TABLE1, TABLE_METADATA, true)) + assertThatCode( + () -> admin.createTable(namespace1, TABLE1, TABLE_METADATA, true, getCreationOptions())) .doesNotThrowAnyException(); } @@ -730,6 +734,17 @@ public void addNewColumnToTable_AddColumnForEachExistingDataType_ShouldAddNewCol } } + @Test + public void getNamespaceNames_ShouldReturnCreatedNamespaces() throws ExecutionException { + // Arrange + + // Act + Set namespaces = admin.getNamespaceNames(); + + // Assert + assertThat(namespaces).containsOnly(namespace1, namespace2); + } + @Test public void addNewColumnToTable_ForNonExistingTable_ShouldThrowIllegalArgumentException() { // Arrange diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminImportTableIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminImportTableIntegrationTestBase.java index 8106ae1f4c..c6b059c527 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminImportTableIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminImportTableIntegrationTestBase.java @@ -79,6 +79,7 @@ protected abstract Map createExistingDatabaseWithAllDataT @Test public void importTable_ShouldWorkProperly() throws Exception { // Arrange + admin.createNamespace(getNamespace(), getCreationOptions()); tables.putAll(createExistingDatabaseWithAllDataTypes()); // Act Assert @@ -95,7 +96,11 @@ public void importTable_ShouldWorkProperly() throws Exception { } @Test - public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() { + public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException() + throws ExecutionException { + // Arrange + admin.createNamespace(getNamespace(), getCreationOptions()); + // Act Assert assertThatThrownBy(() -> admin.importTable(getNamespace(), "unsupported_db")) .isInstanceOf(UnsupportedOperationException.class); diff --git a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java index 89a68be004..e2b73b9791 100644 --- a/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminIntegrationTestBase.java @@ -211,7 +211,7 @@ public void createNamespace_ForNonExistingNamespace_ShouldCreateNamespaceProperl // Arrange // Act - admin.createNamespace(namespace3); + admin.createNamespace(namespace3, getCreationOptions()); // Assert assertThat(admin.namespaceExists(namespace3)).isTrue(); @@ -225,7 +225,7 @@ public void createNamespace_ForExistingNamespace_ShouldThrowIllegalArgumentExcep // Arrange // Act Assert - assertThatThrownBy(() -> admin.createNamespace(namespace1)) + assertThatThrownBy(() -> admin.createNamespace(namespace1, getCreationOptions())) .isInstanceOf(IllegalArgumentException.class); } @@ -234,7 +234,8 @@ public void createNamespace_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyEx // Arrange // Act Assert - assertThatCode(() -> admin.createNamespace(namespace1, true)).doesNotThrowAnyException(); + assertThatCode(() -> admin.createNamespace(namespace1, true, getCreationOptions())) + .doesNotThrowAnyException(); } @Test @@ -242,7 +243,7 @@ public void dropNamespace_ForNonExistingNamespace_ShouldDropNamespaceProperly() throws ExecutionException { try { // Arrange - admin.createNamespace(namespace3); + admin.createNamespace(namespace3, getCreationOptions()); // Act admin.dropNamespace(namespace3); @@ -268,8 +269,8 @@ public void dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentExcepti throws ExecutionException { try { // Arrange - admin.createNamespace(namespace3); - admin.createTable(namespace3, TABLE1, TABLE_METADATA); + admin.createNamespace(namespace3, getCreationOptions()); + admin.createTable(namespace3, TABLE1, TABLE_METADATA, getCreationOptions()); // Act Assert assertThatThrownBy(() -> admin.dropNamespace(namespace3)) @@ -310,7 +311,8 @@ public void createTable_ForExistingTable_ShouldThrowIllegalArgumentException() { // Arrange // Act Assert - assertThatThrownBy(() -> admin.createTable(namespace1, TABLE1, TABLE_METADATA)) + assertThatThrownBy( + () -> admin.createTable(namespace1, TABLE1, TABLE_METADATA, getCreationOptions())) .isInstanceOf(IllegalArgumentException.class); } @@ -319,7 +321,8 @@ public void createTable_ForNonExistingNamespace_ShouldThrowIllegalArgumentExcept // Arrange // Act Assert - assertThatThrownBy(() -> admin.createTable(namespace3, TABLE1, TABLE_METADATA)) + assertThatThrownBy( + () -> admin.createTable(namespace3, TABLE1, TABLE_METADATA, getCreationOptions())) .isInstanceOf(IllegalArgumentException.class); } @@ -328,7 +331,8 @@ public void createTable_IfNotExists_ForExistingNamespace_ShouldNotThrowAnyExcept // Arrange // Act Assert - assertThatCode(() -> admin.createTable(namespace1, TABLE1, TABLE_METADATA, true)) + assertThatCode( + () -> admin.createTable(namespace1, TABLE1, TABLE_METADATA, true, getCreationOptions())) .doesNotThrowAnyException(); } @@ -839,6 +843,17 @@ public void dropCoordinatorTables_IfExist_CoordinatorTablesDoNotExist_ShouldNotT } } + @Test + public void getNamespaceNames_ShouldReturnCreatedNamespaces() throws ExecutionException { + // Arrange + + // Act + Set namespaces = admin.getNamespaceNames(); + + // Assert + assertThat(namespaces).containsOnly(namespace1, namespace2); + } + protected boolean isIndexOnBooleanColumnSupported() { return true; } diff --git a/integration-test/src/main/java/com/scalar/db/schemaloader/SchemaLoaderImportIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/schemaloader/SchemaLoaderImportIntegrationTestBase.java index c8d9252cfc..20392a40bc 100644 --- a/integration-test/src/main/java/com/scalar/db/schemaloader/SchemaLoaderImportIntegrationTestBase.java +++ b/integration-test/src/main/java/com/scalar/db/schemaloader/SchemaLoaderImportIntegrationTestBase.java @@ -124,8 +124,6 @@ private void dropTablesIfExist() throws Exception { storageAdmin.dropNamespace(namespace2, true); } - protected abstract void createExistingDatabase(String namespace) throws Exception; - protected abstract void createImportableTable(String namespace, String table) throws Exception; protected abstract void createNonImportableTable(String namespace, String table) throws Exception; @@ -135,8 +133,8 @@ private void dropTablesIfExist() throws Exception { @Test public void importTables_ImportableTablesGiven_ShouldImportProperly() throws Exception { // Arrange - createExistingDatabase(namespace1); - createExistingDatabase(namespace2); + transactionAdmin.createNamespace(namespace1); + storageAdmin.createNamespace(namespace2); createImportableTable(namespace1, TABLE_1); createImportableTable(namespace2, TABLE_2); @@ -155,8 +153,8 @@ public void importTables_ImportableTablesGiven_ShouldImportProperly() throws Exc public void importTables_NonImportableTablesGiven_ShouldThrowIllegalArgumentException() throws Exception { // Arrange - createExistingDatabase(namespace1); - createExistingDatabase(namespace2); + transactionAdmin.createNamespace(namespace1); + storageAdmin.createNamespace(namespace2); createNonImportableTable(namespace1, TABLE_1); createNonImportableTable(namespace2, TABLE_2); diff --git a/server/src/integration-test/java/com/scalar/db/server/ConsensusCommitAdminIntegrationTestWithServer.java b/server/src/integration-test/java/com/scalar/db/server/ConsensusCommitAdminIntegrationTestWithServer.java index b728c30201..72adb29299 100644 --- a/server/src/integration-test/java/com/scalar/db/server/ConsensusCommitAdminIntegrationTestWithServer.java +++ b/server/src/integration-test/java/com/scalar/db/server/ConsensusCommitAdminIntegrationTestWithServer.java @@ -7,6 +7,7 @@ import com.scalar.db.transaction.consensuscommit.Coordinator; import java.util.Properties; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; @@ -80,6 +81,11 @@ public void afterAll() throws Exception { .getTableMetadata_WhenIncludeMetadataIsEnabled_ShouldReturnCorrectMetadataWithTransactionMetadataColumns(); } + @Override + @Test + @Disabled("Retrieving the namespace names is not supported in ScalarDB server") + public void getNamespaceNames_ShouldReturnCreatedNamespaces() {} + @SuppressWarnings("unused") private boolean isExternalServerUsed() { // An external server is used, so we don't have access to the configuration to connect to the diff --git a/server/src/integration-test/java/com/scalar/db/server/DistributedStorageAdminIntegrationTestWithServer.java b/server/src/integration-test/java/com/scalar/db/server/DistributedStorageAdminIntegrationTestWithServer.java index f8bb9148b6..fc42b8d6e8 100644 --- a/server/src/integration-test/java/com/scalar/db/server/DistributedStorageAdminIntegrationTestWithServer.java +++ b/server/src/integration-test/java/com/scalar/db/server/DistributedStorageAdminIntegrationTestWithServer.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.util.Properties; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; public class DistributedStorageAdminIntegrationTestWithServer extends DistributedStorageAdminIntegrationTestBase { @@ -24,6 +26,11 @@ protected Properties getProperties(String testName) { return ServerEnv.getClient1Properties(testName); } + @Override + @Test + @Disabled("Retrieving the namespace names is not supported in ScalarDB server") + public void getNamespaceNames_ShouldReturnCreatedNamespaces() {} + @AfterAll @Override public void afterAll() throws Exception { diff --git a/server/src/integration-test/java/com/scalar/db/server/ServerAdminTestUtils.java b/server/src/integration-test/java/com/scalar/db/server/ServerAdminTestUtils.java index f135de948b..ce343e059b 100644 --- a/server/src/integration-test/java/com/scalar/db/server/ServerAdminTestUtils.java +++ b/server/src/integration-test/java/com/scalar/db/server/ServerAdminTestUtils.java @@ -7,7 +7,6 @@ import com.scalar.db.storage.jdbc.JdbcConfig; import com.scalar.db.storage.jdbc.JdbcUtils; import com.scalar.db.storage.jdbc.RdbEngineFactory; -import com.scalar.db.storage.jdbc.RdbEngineOracle; import com.scalar.db.storage.jdbc.RdbEngineStrategy; import com.scalar.db.util.AdminTestUtils; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -27,7 +26,7 @@ public class ServerAdminTestUtils extends AdminTestUtils { public ServerAdminTestUtils(Properties jdbcStorageProperties) { super(jdbcStorageProperties); config = new JdbcConfig(new DatabaseConfig(jdbcStorageProperties)); - metadataNamespace = config.getTableMetadataSchema().orElse(JdbcAdmin.METADATA_SCHEMA); + metadataNamespace = config.getMetadataSchema().orElse(JdbcAdmin.METADATA_SCHEMA); metadataTable = JdbcAdmin.METADATA_TABLE; rdbEngine = RdbEngineFactory.create(config); } @@ -35,14 +34,6 @@ public ServerAdminTestUtils(Properties jdbcStorageProperties) { @Override public void dropMetadataTable() throws SQLException { execute("DROP TABLE " + rdbEngine.encloseFullTableName(metadataNamespace, metadataTable)); - - String dropNamespaceStatement; - if (rdbEngine instanceof RdbEngineOracle) { - dropNamespaceStatement = "DROP USER " + rdbEngine.enclose(metadataNamespace); - } else { - dropNamespaceStatement = "DROP SCHEMA " + rdbEngine.enclose(metadataNamespace); - } - execute(dropNamespaceStatement); } @Override