From 012b7bc1d1d73aab4ed657ffd809cb9199578379 Mon Sep 17 00:00:00 2001 From: Kodai Doki <52027276+KodaiD@users.noreply.github.com> Date: Thu, 30 Oct 2025 07:39:53 +0000 Subject: [PATCH 1/2] Empty commit [skip ci] From eff5a896c434bfeabaa537b24fe79e5aad654748 Mon Sep 17 00:00:00 2001 From: Kodai Doki <52027276+KodaiD@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:39:28 +0900 Subject: [PATCH 2/2] Resolve conflicts --- ...raAdminCaseSensitivityIntegrationTest.java | 6 +++ .../CassandraAdminIntegrationTest.java | 6 +++ .../cassandra/CassandraAdminTestUtils.java | 16 +++++++ ...osAdminCaseSensitivityIntegrationTest.java | 6 +++ .../cosmos/CosmosAdminIntegrationTest.java | 6 +++ .../storage/cosmos/CosmosAdminTestUtils.java | 14 ++++++ ...moAdminCaseSensitivityIntegrationTest.java | 12 +++++ .../dynamo/DynamoAdminIntegrationTest.java | 12 +++++ .../storage/dynamo/DynamoAdminTestUtils.java | 25 ++++++++++ ...bcAdminCaseSensitivityIntegrationTest.java | 12 +++++ .../jdbc/JdbcAdminIntegrationTest.java | 12 +++++ .../db/storage/jdbc/JdbcAdminTestUtils.java | 22 +++++++++ .../MultiStorageAdminTestUtils.java | 48 +++++++++++++++++++ .../com/scalar/db/common/error/CoreError.java | 6 +++ .../scalar/db/storage/cosmos/CosmosAdmin.java | 14 ++++++ .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 26 +++++++++- .../db/storage/jdbc/RdbEngineMysql.java | 5 ++ .../db/storage/jdbc/RdbEngineOracle.java | 5 ++ .../db/storage/jdbc/RdbEnginePostgresql.java | 5 ++ .../db/storage/jdbc/RdbEngineSqlServer.java | 5 ++ .../db/storage/jdbc/RdbEngineSqlite.java | 6 +++ .../db/storage/jdbc/RdbEngineStrategy.java | 2 + .../storage/cosmos/CosmosAdminTestBase.java | 34 +++++++++++++ .../db/storage/jdbc/JdbcAdminTestBase.java | 5 ++ ...ibutedStorageAdminIntegrationTestBase.java | 26 ++++++++++ .../com/scalar/db/util/AdminTestUtils.java | 18 +++++++ 26 files changed, 353 insertions(+), 1 deletion(-) diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java index 1dcc29cca4..8807d3585b 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminCaseSensitivityIntegrationTest.java @@ -2,6 +2,7 @@ import com.scalar.db.api.DistributedStorageAdminCaseSensitivityIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.AdminTestUtils; import java.util.Properties; public class CassandraAdminCaseSensitivityIntegrationTest @@ -17,4 +18,9 @@ protected String getSystemNamespaceName(Properties properties) { .getSystemNamespaceName() .orElse(DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME); } + + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new CassandraAdminTestUtils(getProperties(testName)); + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java index 6fb74f3d34..f9a11666df 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminIntegrationTest.java @@ -2,6 +2,7 @@ import com.scalar.db.api.DistributedStorageAdminIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.AdminTestUtils; import java.util.Properties; public class CassandraAdminIntegrationTest extends DistributedStorageAdminIntegrationTestBase { @@ -16,4 +17,9 @@ protected String getSystemNamespaceName(Properties properties) { .getSystemNamespaceName() .orElse(DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME); } + + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new CassandraAdminTestUtils(getProperties(testName)); + } } diff --git a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminTestUtils.java b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminTestUtils.java index 24c1b2ad8f..e5cae073ef 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminTestUtils.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cassandra/CassandraAdminTestUtils.java @@ -1,5 +1,8 @@ package com.scalar.db.storage.cassandra; +import static com.datastax.driver.core.Metadata.quoteIfNecessary; + +import com.datastax.driver.core.schemabuilder.SchemaBuilder; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.util.AdminTestUtils; import java.util.Properties; @@ -28,6 +31,11 @@ public void corruptMetadata(String namespace, String table) { // Do nothing } + @Override + public void deleteMetadata(String namespace, String table) throws Exception { + // Do nothing + } + @Override public boolean namespaceExists(String namespace) { return clusterManager.getSession().getCluster().getMetadata().getKeyspace(namespace) != null; @@ -38,6 +46,14 @@ public boolean tableExists(String namespace, String table) { return clusterManager.getMetadata(namespace, table) != null; } + @Override + public void dropTable(String namespace, String table) { + String dropTableQuery = + SchemaBuilder.dropTable(quoteIfNecessary(namespace), quoteIfNecessary(table)) + .getQueryString(); + clusterManager.getSession().execute(dropTableQuery); + } + @Override public void close() { clusterManager.close(); diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java index 0ea6fca2c7..f24d633a39 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminCaseSensitivityIntegrationTest.java @@ -2,6 +2,7 @@ import com.scalar.db.api.DistributedStorageAdminCaseSensitivityIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.AdminTestUtils; import java.util.Map; import java.util.Properties; @@ -18,6 +19,11 @@ protected Map getCreationOptions() { return CosmosEnv.getCreationOptions(); } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new CosmosAdminTestUtils(getProperties(testName)); + } + @Override protected String getSystemNamespaceName(Properties properties) { return new CosmosConfig(new DatabaseConfig(properties)) diff --git a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java index 14bd5ae415..ebe48cf1b6 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/cosmos/CosmosAdminIntegrationTest.java @@ -2,6 +2,7 @@ import com.scalar.db.api.DistributedStorageAdminIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.AdminTestUtils; import java.util.Map; import java.util.Properties; @@ -17,6 +18,11 @@ protected Map getCreationOptions() { return CosmosEnv.getCreationOptions(); } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new CosmosAdminTestUtils(getProperties(testName)); + } + @Override protected String getSystemNamespaceName(Properties properties) { return new CosmosConfig(new DatabaseConfig(properties)) 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 c4e62f960a..767ee3f22b 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 @@ -76,6 +76,15 @@ public void corruptMetadata(String namespace, String table) { container.upsertItem(corruptedMetadata); } + @Override + public void deleteMetadata(String namespace, String table) { + String fullTableName = getFullTableName(namespace, table); + CosmosContainer container = + client.getDatabase(metadataDatabase).getContainer(CosmosAdmin.METADATA_CONTAINER); + container.deleteItem( + fullTableName, new PartitionKey(fullTableName), new CosmosItemRequestOptions()); + } + /** * Retrieve the stored procedure for the given table * @@ -117,6 +126,11 @@ public boolean tableExists(String namespace, String table) { return true; } + @Override + public void dropTable(String namespace, String table) { + client.getDatabase(namespace).getContainer(table).delete(); + } + @Override public void close() { client.close(); diff --git a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java index 2a6826d856..255cce3fa7 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/dynamo/DynamoAdminCaseSensitivityIntegrationTest.java @@ -2,6 +2,7 @@ import com.scalar.db.api.DistributedStorageAdminCaseSensitivityIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.AdminTestUtils; import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Disabled; @@ -25,6 +26,11 @@ protected boolean isIndexOnBooleanColumnSupported() { return false; } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new DynamoAdminTestUtils(getProperties(testName)); + } + @Override protected String getSystemNamespaceName(Properties properties) { return new DynamoConfig(new DatabaseConfig(properties)) @@ -35,6 +41,12 @@ protected String getSystemNamespaceName(Properties properties) { // 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 + dropNamespace_ForNamespaceWithNonScalarDBManagedTables_ShouldThrowIllegalArgumentException() {} + @Disabled @Test @Override 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 eba3ca2880..63f33d365a 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 @@ -2,6 +2,7 @@ import com.scalar.db.api.DistributedStorageAdminIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; +import com.scalar.db.util.AdminTestUtils; import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.Disabled; @@ -24,6 +25,11 @@ protected boolean isIndexOnBooleanColumnSupported() { return false; } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new DynamoAdminTestUtils(getProperties(testName)); + } + @Override protected String getSystemNamespaceName(Properties properties) { return new DynamoConfig(new DatabaseConfig(properties)) @@ -34,6 +40,12 @@ protected String getSystemNamespaceName(Properties properties) { // 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 + dropNamespace_ForNamespaceWithNonScalarDBManagedTables_ShouldThrowIllegalArgumentException() {} + @Disabled @Test @Override 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 74e80302fb..32d376ee84 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 @@ -139,6 +139,31 @@ public void corruptMetadata(String namespace, String table) { .build()); } + @Override + public void deleteMetadata(String namespace, String table) { + String fullTableName = + getFullTableName(Namespace.of(namespacePrefix, namespace).prefixed(), table); + Map keyToDelete = new HashMap<>(); + keyToDelete.put("table", AttributeValue.builder().s(fullTableName).build()); + + client.deleteItem( + DeleteItemRequest.builder() + .tableName(getFullTableName(metadataNamespace, DynamoAdmin.METADATA_TABLE)) + .key(keyToDelete) + .build()); + } + + @Override + public void dropTable(String nonPrefixedNamespace, String table) { + String namespace = Namespace.of(namespacePrefix, nonPrefixedNamespace).prefixed(); + client.deleteTable( + DeleteTableRequest.builder().tableName(getFullTableName(namespace, table)).build()); + if (!waitForTableDeletion(namespace, table)) { + throw new RuntimeException( + String.format("Deleting the %s table timed out", getFullTableName(namespace, table))); + } + } + @Override public boolean namespaceExists(String namespace) throws Exception { // Dynamo has no concept of namespace diff --git a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java index 7d19aed743..aae5b5a466 100644 --- a/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java +++ b/core/src/integration-test/java/com/scalar/db/storage/jdbc/JdbcAdminCaseSensitivityIntegrationTest.java @@ -3,6 +3,7 @@ import com.scalar.db.api.DistributedStorageAdminCaseSensitivityIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.util.AdminTestUtils; import java.util.Properties; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; @@ -22,6 +23,11 @@ protected String getSystemNamespaceName(Properties properties) { .orElse(DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME); } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new JdbcAdminTestUtils(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. @@ -30,6 +36,12 @@ private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @Test + @Override + @DisabledIf("isSqlite") + public void + dropNamespace_ForNamespaceWithNonScalarDBManagedTables_ShouldThrowIllegalArgumentException() {} + @Test @Override @DisabledIf("isSqlite") 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 9a57dd1a5a..0343ca7dc0 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 @@ -3,6 +3,7 @@ import com.scalar.db.api.DistributedStorageAdminIntegrationTestBase; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; +import com.scalar.db.util.AdminTestUtils; import java.util.Properties; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledIf; @@ -21,6 +22,11 @@ protected String getSystemNamespaceName(Properties properties) { .orElse(DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME); } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new JdbcAdminTestUtils(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. @@ -29,6 +35,12 @@ private boolean isSqlite() { return JdbcEnv.isSqlite(); } + @Test + @Override + @DisabledIf("isSqlite") + public void + dropNamespace_ForNamespaceWithNonScalarDBManagedTables_ShouldThrowIllegalArgumentException() {} + @Test @Override @DisabledIf("isSqlite") 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 25f7e63a79..743e735a54 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 @@ -56,6 +56,22 @@ public void corruptMetadata(String namespace, String table) throws Exception { execute(insertCorruptedMetadataStatement); } + @Override + public void deleteMetadata(String namespace, String table) throws Exception { + String deleteMetadataStatement = + "DELETE FROM " + + rdbEngine.encloseFullTableName(metadataSchema, JdbcAdmin.METADATA_TABLE) + + " WHERE " + + rdbEngine.enclose(JdbcAdmin.METADATA_COL_FULL_TABLE_NAME) + + " = ?"; + try (Connection connection = dataSource.getConnection(); + PreparedStatement preparedStatement = + connection.prepareStatement(deleteMetadataStatement)) { + preparedStatement.setString(1, getFullTableName(namespace, table)); + preparedStatement.executeUpdate(); + } + } + private void execute(String sql) throws SQLException { try (Connection connection = dataSource.getConnection()) { JdbcAdmin.execute(connection, sql); @@ -110,6 +126,12 @@ public boolean tableExists(String namespace, String table) throws Exception { } } + @Override + public void dropTable(String namespace, String table) throws Exception { + String dropTableStatement = "DROP TABLE " + rdbEngine.encloseFullTableName(namespace, table); + execute(dropTableStatement); + } + @Override public void close() throws SQLException { dataSource.close(); 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 3b4d05beb7..20dcf7b6c4 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 @@ -1,7 +1,9 @@ package com.scalar.db.storage.multistorage; +import static com.datastax.driver.core.Metadata.quoteIfNecessary; import static com.scalar.db.util.ScalarDbUtils.getFullTableName; +import com.datastax.driver.core.schemabuilder.SchemaBuilder; import com.scalar.db.config.DatabaseConfig; import com.scalar.db.storage.cassandra.ClusterManager; import com.scalar.db.storage.jdbc.JdbcAdmin; @@ -87,6 +89,25 @@ public void corruptMetadata(String namespace, String table) throws Exception { execute(insertCorruptedMetadataStatement); } + @Override + public void deleteMetadata(String namespace, String table) throws Exception { + // Do nothing for Cassandra + + // for JDBC + String deleteMetadataStatement = + "DELETE FROM " + + rdbEngine.encloseFullTableName(jdbcMetadataSchema, JdbcAdmin.METADATA_TABLE) + + " WHERE " + + rdbEngine.enclose(JdbcAdmin.METADATA_COL_FULL_TABLE_NAME) + + " = ?"; + try (Connection connection = dataSource.getConnection(); + PreparedStatement preparedStatement = + connection.prepareStatement(deleteMetadataStatement)) { + preparedStatement.setString(1, getFullTableName(namespace, table)); + preparedStatement.executeUpdate(); + } + } + @Override public boolean namespaceExists(String namespace) throws SQLException { boolean existsOnCassandra = namespaceExistsOnCassandra(namespace); @@ -175,6 +196,33 @@ private void execute(String sql) throws SQLException { } } + @Override + public void dropTable(String namespace, String table) throws Exception { + boolean existsOnCassandra = tableExistsOnCassandra(namespace, table); + boolean existsOnJdbc = tableExistsOnJdbc(namespace, table); + + if (existsOnCassandra && existsOnJdbc) { + throw new IllegalStateException( + String.format( + "The %s table should not exist on both storages", + getFullTableName(namespace, table))); + } else if (!(existsOnCassandra || existsOnJdbc)) { + throw new IllegalStateException( + String.format( + "The %s table does not exist on both storages", getFullTableName(namespace, table))); + } + + if (existsOnCassandra) { + String dropTableQuery = + SchemaBuilder.dropTable(quoteIfNecessary(namespace), quoteIfNecessary(table)) + .getQueryString(); + clusterManager.getSession().execute(dropTableQuery); + } else { + String dropTableStatement = "DROP TABLE " + rdbEngine.encloseFullTableName(namespace, table); + execute(dropTableStatement); + } + } + @Override public void close() throws SQLException { clusterManager.close(); diff --git a/core/src/main/java/com/scalar/db/common/error/CoreError.java b/core/src/main/java/com/scalar/db/common/error/CoreError.java index d61e72b45e..bfb736b703 100644 --- a/core/src/main/java/com/scalar/db/common/error/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/error/CoreError.java @@ -853,6 +853,12 @@ public enum CoreError implements ScalarDbError { Category.USER_ERROR, "0203", "Delimiter must not be null", "", ""), DATA_LOADER_CONFIG_FILE_PATH_BLANK( Category.USER_ERROR, "0204", "Config file path must not be blank", "", ""), + NAMESPACE_WITH_NON_SCALARDB_TABLES_CANNOT_BE_DROPPED( + Category.USER_ERROR, + "0249", + "The namespace has non-ScalarDB tables and cannot be dropped. Namespace: %s; Tables in the namespace: %s", + "", + ""), // // Errors for the concurrency error category 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 5afb616ae7..c1c86f49cc 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 @@ -314,7 +314,15 @@ private void deleteTableMetadata(String namespace, String table) throws Executio @Override public void dropNamespace(String namespace) throws ExecutionException { try { + Set remainingTables = getRawTableNames(namespace); + if (!remainingTables.isEmpty()) { + throw new IllegalArgumentException( + CoreError.NAMESPACE_WITH_NON_SCALARDB_TABLES_CANNOT_BE_DROPPED.buildMessage( + namespace, remainingTables)); + } client.getDatabase(namespace).delete(); + } catch (IllegalArgumentException e) { + throw e; } catch (RuntimeException e) { throw new ExecutionException("Deleting the database failed", e); } @@ -614,4 +622,10 @@ private boolean metadataContainerExists() { } return true; } + + private Set getRawTableNames(String namespace) { + return client.getDatabase(namespace).readAllContainers().stream() + .map(CosmosContainerProperties::getId) + .collect(Collectors.toSet()); + } } 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 b0ddbabd98..9c81faa3e2 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 @@ -42,7 +42,7 @@ public class JdbcAdmin implements DistributedStorageAdmin { public static final String METADATA_TABLE = "metadata"; - @VisibleForTesting static final String METADATA_COL_FULL_TABLE_NAME = "full_table_name"; + @VisibleForTesting public static final String METADATA_COL_FULL_TABLE_NAME = "full_table_name"; @VisibleForTesting static final String METADATA_COL_COLUMN_NAME = "column_name"; @VisibleForTesting static final String METADATA_COL_DATA_TYPE = "data_type"; @VisibleForTesting static final String METADATA_COL_KEY_TYPE = "key_type"; @@ -371,6 +371,12 @@ private void deleteMetadataSchema(Connection connection) throws SQLException { @Override public void dropNamespace(String namespace) throws ExecutionException { try (Connection connection = dataSource.getConnection()) { + Set remainingTables = getInternalTableNames(connection, namespace); + if (!remainingTables.isEmpty()) { + throw new IllegalArgumentException( + CoreError.NAMESPACE_WITH_NON_SCALARDB_TABLES_CANNOT_BE_DROPPED.buildMessage( + namespace, remainingTables)); + } execute(connection, rdbEngine.dropNamespaceSql(namespace)); } catch (SQLException e) { rdbEngine.dropNamespaceTranslateSQLException(e, namespace); @@ -562,6 +568,24 @@ public Set getNamespaceTableNames(String namespace) throws ExecutionExce } } + private Set getInternalTableNames(Connection connection, String namespace) + throws SQLException { + String sql = rdbEngine.getTableNamesInNamespaceSql(); + if (Strings.isNullOrEmpty(sql)) { + return Collections.emptySet(); + } + Set tableNames = new HashSet<>(); + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.setString(1, namespace); + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + tableNames.add(resultSet.getString(1)); + } + } + } + return tableNames; + } + @Override public boolean namespaceExists(String namespace) throws ExecutionException { if (metadataSchema.equals(namespace)) { 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 be1f5747af..69425358ab 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 @@ -374,4 +374,9 @@ public String getSchemaName(String namespace) { // method is used for filtering. return namespace; } + + @Override + public String getTableNamesInNamespaceSql() { + return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?"; + } } 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 23d917656d..147a2e60d7 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 @@ -352,4 +352,9 @@ public String getEscape(LikeExpression likeExpression) { String escape = likeExpression.getEscape(); return escape.isEmpty() ? null : escape; } + + @Override + public String getTableNamesInNamespaceSql() { + return "SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = ?"; + } } 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 cec8d52906..2f1edca01d 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 @@ -313,4 +313,9 @@ public String computeBooleanValue(boolean value) { public Driver getDriver() { return new org.postgresql.Driver(); } + + @Override + public String getTableNamesInNamespaceSql() { + return "SELECT table_name FROM information_schema.tables WHERE table_schema = ?"; + } } 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 b52b96967e..48233dbf15 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 @@ -319,4 +319,9 @@ public String getEscape(LikeExpression likeExpression) { String escape = likeExpression.getEscape(); return escape.isEmpty() ? "\\" : escape; } + + @Override + public String getTableNamesInNamespaceSql() { + return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = ?"; + } } 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 5932653cef..c806ee66d0 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 @@ -278,4 +278,10 @@ public String getEscape(LikeExpression likeExpression) { String escape = likeExpression.getEscape(); return escape.isEmpty() ? null : escape; } + + @Override + public String getTableNamesInNamespaceSql() { + // Do nothing. Namespace is just a table prefix in the SQLite implementation. + return null; + } } 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 0f980de598..148c6ebb47 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 @@ -140,4 +140,6 @@ default String getPattern(LikeExpression likeExpression) { default @Nullable String getSchemaName(String namespace) { return namespace; } + + String getTableNamesInNamespaceSql(); } 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 d35c85c829..bb1d01881c 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 @@ -2,6 +2,7 @@ import static com.scalar.db.util.ScalarDbUtils.getFullTableName; 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; @@ -469,6 +470,12 @@ public void dropNamespace_WithExistingDatabase_ShouldDropDatabase() throws Execu String namespace = "ns"; when(client.getDatabase(any())).thenReturn(database); + @SuppressWarnings("unchecked") + CosmosPagedIterable emptyContainerIterable = + mock(CosmosPagedIterable.class); + when(emptyContainerIterable.stream()).thenReturn(Stream.empty()); + when(database.readAllContainers()).thenReturn(emptyContainerIterable); + // Act admin.dropNamespace(namespace); @@ -476,6 +483,33 @@ public void dropNamespace_WithExistingDatabase_ShouldDropDatabase() throws Execu verify(database).delete(); } + @Test + public void dropNamespace_WithNonScalarDBTableLeft_ShouldThrowIllegalArgumentException() { + // Arrange + String namespace = "ns"; + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + when(client.getDatabase(namespace)).thenReturn(database); + when(client.getDatabase(metadataDatabaseName)).thenReturn(metadataDatabase); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); + + @SuppressWarnings("unchecked") + CosmosPagedIterable containerPagedIterable = + mock(CosmosPagedIterable.class); + when(containerPagedIterable.stream()) + .thenReturn(Stream.of(mock(CosmosContainerProperties.class))); + when(database.readAllContainers()).thenReturn(containerPagedIterable); + + @SuppressWarnings("unchecked") + CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); + when(namespacesContainer.queryItems(anyString(), any(), any())).thenReturn(pagedIterable); + when(pagedIterable.stream()).thenReturn(Stream.empty()); + + // Act Assert + assertThatCode(() -> admin.dropNamespace(namespace)) + .isInstanceOf(IllegalArgumentException.class); + } + @Test public void truncateTable_WithExistingRecords_ShouldDeleteAllRecords() throws ExecutionException { // Arrange 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 dfb6ce747a..95f81b52cb 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 @@ -1312,9 +1312,14 @@ private void dropSchema_forX_shouldDropSchema( Connection connection = mock(Connection.class); Statement dropSchemaStatement = mock(Statement.class); + PreparedStatement getTableNamesPrepStmt = mock(PreparedStatement.class); + ResultSet emptyResultSet = mock(ResultSet.class); when(dataSource.getConnection()).thenReturn(connection); when(connection.createStatement()).thenReturn(dropSchemaStatement); + when(connection.prepareStatement(any())).thenReturn(getTableNamesPrepStmt); + when(emptyResultSet.next()).thenReturn(false); + when(getTableNamesPrepStmt.executeQuery()).thenReturn(emptyResultSet); // Act admin.dropNamespace(namespace); 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 335e727395..381341268d 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 @@ -8,6 +8,7 @@ import com.scalar.db.io.DataType; import com.scalar.db.io.Key; import com.scalar.db.service.StorageFactory; +import com.scalar.db.util.AdminTestUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -187,6 +188,8 @@ protected Map getCreationOptions() { return Collections.emptyMap(); } + protected abstract AdminTestUtils getAdminTestUtils(String testName); + @AfterAll public void afterAll() throws Exception { try { @@ -365,6 +368,29 @@ public void dropNamespace_ForNonEmptyNamespace_ShouldThrowIllegalArgumentExcepti } } + @Test + public void + dropNamespace_ForNamespaceWithNonScalarDBManagedTables_ShouldThrowIllegalArgumentException() + throws Exception { + AdminTestUtils adminTestUtils = getAdminTestUtils(getTestName()); + String nonManagedTable = "non_managed_table"; + try { + // Arrange + admin.createNamespace(namespace3, getCreationOptions()); + admin.createTable(namespace3, nonManagedTable, getTableMetadata(), getCreationOptions()); + adminTestUtils.deleteMetadata(namespace3, nonManagedTable); + + // Act Assert + assertThatThrownBy(() -> admin.dropNamespace(namespace3)) + .isInstanceOf(IllegalArgumentException.class); + } finally { + adminTestUtils.dropTable(namespace3, nonManagedTable); + admin.dropNamespace(namespace3, true); + + adminTestUtils.close(); + } + } + @Test public void dropNamespace_IfExists_ForNonExistingNamespace_ShouldNotThrowAnyException() { // Arrange diff --git a/integration-test/src/main/java/com/scalar/db/util/AdminTestUtils.java b/integration-test/src/main/java/com/scalar/db/util/AdminTestUtils.java index f548040d54..a3bf9a62aa 100644 --- a/integration-test/src/main/java/com/scalar/db/util/AdminTestUtils.java +++ b/integration-test/src/main/java/com/scalar/db/util/AdminTestUtils.java @@ -42,6 +42,15 @@ public AdminTestUtils(Properties coordinatorStorageProperties) { */ public abstract void corruptMetadata(String namespace, String table) throws Exception; + /** + * Deletes the metadata for the specified table. + * + * @param namespace the namespace + * @param table the table name + * @throws Exception if an error occurs + */ + public abstract void deleteMetadata(String namespace, String table) throws Exception; + /** * Returns whether the table metadata for the coordinator tables are present or not. * @@ -95,6 +104,15 @@ public boolean areTableMetadataForCoordinatorTablesPresent() throws Exception { */ public abstract boolean tableExists(String namespace, String table) throws Exception; + /** + * Drops the table in the underlying storage. + * + * @param namespace a namespace + * @param table a table + * @throws Exception if an errors occurs + */ + public abstract void dropTable(String namespace, String table) throws Exception; + /** * Closes connections to the storage *