From 572788207e06106a0e9ff2f6ecab012cb7d75fb5 Mon Sep 17 00:00:00 2001 From: Kodai Doki <52027276+KodaiD@users.noreply.github.com> Date: Thu, 30 Oct 2025 07:39:48 +0000 Subject: [PATCH 1/2] Empty commit [skip ci] From a29ddd053d8dcc2ed04dd7d1a5eb90170317d57e 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 | 68 +++++++++++++++++++ ...ibutedStorageAdminIntegrationTestBase.java | 26 +++++++ .../com/scalar/db/util/AdminTestUtils.java | 18 +++++ 26 files changed, 416 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 8def2e329b..a1005f6bd4 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 @@ -18,6 +19,11 @@ protected String getSystemNamespaceName(Properties properties) { .orElse(DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME); } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new CassandraAdminTestUtils(getProperties(testName)); + } + @Override protected boolean isTimestampTypeSupported() { return false; 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 bba53f36bc..48bc092817 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 { @@ -17,6 +18,11 @@ protected String getSystemNamespaceName(Properties properties) { .orElse(DatabaseConfig.DEFAULT_SYSTEM_NAMESPACE_NAME); } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new CassandraAdminTestUtils(getProperties(testName)); + } + @Override protected boolean isTimestampTypeSupported() { return false; 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 1d3da1a539..4329d3d838 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 @@ -901,6 +901,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, + "0205", + "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 6d1f5b7c76..759c5ec4f4 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); } @@ -627,4 +635,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 8142b2f30d..33727e71e1 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); @@ -569,6 +575,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 5807414af6..4da8e9a78c 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 @@ -435,4 +435,9 @@ public TimestampTZColumn parseTimestampTZColumn(ResultSet resultSet, String colu getTimeTypeStrategy() { return timeTypeEngine; } + + @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 a8952ff9d8..42bd4b92d2 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 @@ -409,4 +409,9 @@ public String getEscape(LikeExpression likeExpression) { getTimeTypeStrategy() { return timeTypeEngine; } + + @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 b05c858ffb..d572143f08 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 @@ -364,4 +364,9 @@ public Driver getDriver() { getTimeTypeStrategy() { return timeTypeEngine; } + + @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 d54cf03d4e..0f8ad73c97 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 @@ -375,4 +375,9 @@ public Map getConnectionProperties() { getTimeTypeStrategy() { return timeTypeEngine; } + + @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 555cecefdc..baf810ad26 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 @@ -334,4 +334,10 @@ public TimestampTZColumn parseTimestampTZColumn(ResultSet resultSet, String colu public RdbEngineTimeTypeStrategy getTimeTypeStrategy() { return timeTypeEngine; } + + @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 7ea0b80dc8..eb34ca719f 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 @@ -211,4 +211,6 @@ default Map getConnectionProperties() { } RdbEngineTimeTypeStrategy getTimeTypeStrategy(); + + 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 b626f3fbbf..fe2d571b44 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; @@ -512,6 +513,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); @@ -519,6 +526,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 27f22687aa..e1d1f5d035 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,6 +10,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowable; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -1394,9 +1395,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); @@ -1405,6 +1411,68 @@ private void dropSchema_forX_shouldDropSchema( verify(dropSchemaStatement).execute(expectedDropSchemaStatement); } + @Test + public void dropNamespace_WithNonScalarDBTableLeftForMysql_ShouldThrowIllegalArgumentException() + throws Exception { + dropNamespace_WithNonScalarDBTableLeftForX_ShouldThrowIllegalArgumentException(RdbEngine.MYSQL); + } + + @Test + public void + dropNamespace_WithNonScalarDBTableLeftForPostgresql_ShouldThrowIllegalArgumentException() + throws Exception { + dropNamespace_WithNonScalarDBTableLeftForX_ShouldThrowIllegalArgumentException( + RdbEngine.POSTGRESQL); + } + + @Test + public void + dropNamespace_WithNonScalarDBTableLeftForSqlServer_ShouldThrowIllegalArgumentException() + throws Exception { + dropNamespace_WithNonScalarDBTableLeftForX_ShouldThrowIllegalArgumentException( + RdbEngine.SQL_SERVER); + } + + @Test + public void dropNamespace_WithNonScalarDBTableLeftForOracle_ShouldThrowIllegalArgumentException() + throws Exception { + dropNamespace_WithNonScalarDBTableLeftForX_ShouldThrowIllegalArgumentException( + RdbEngine.ORACLE); + } + + @Test + public void dropNamespace_WithNonScalarDBTableLeftForSqlite_ShouldThrowIllegalArgumentException() + throws Exception { + // Do nothing. SQLite does not have a concept of namespaces. + } + + private void dropNamespace_WithNonScalarDBTableLeftForX_ShouldThrowIllegalArgumentException( + RdbEngine rdbEngine) throws Exception { + // Arrange + String namespace = "my_ns"; + JdbcAdmin admin = createJdbcAdminFor(rdbEngine); + + Connection connection = mock(Connection.class); + Statement dropNamespaceStatementMock = mock(Statement.class); + Statement selectNamespaceStatementMock = mock(Statement.class); + PreparedStatement getTableNamesPrepStmt = mock(PreparedStatement.class); + when(connection.createStatement()) + .thenReturn(dropNamespaceStatementMock, selectNamespaceStatementMock); + ResultSet emptyResultSet = mock(ResultSet.class); + when(emptyResultSet.next()).thenReturn(true).thenReturn(false); + when(getTableNamesPrepStmt.executeQuery()).thenReturn(emptyResultSet); + when(connection.prepareStatement(any())).thenReturn(getTableNamesPrepStmt); + when(dataSource.getConnection()).thenReturn(connection); + // Namespaces table does not contain other namespaces + ResultSet resultSet = mock(ResultSet.class); + when(resultSet.next()).thenReturn(false); + when(selectNamespaceStatementMock.executeQuery(anyString())).thenReturn(resultSet); + + // Act Assert + assertThatCode(() -> admin.dropNamespace(namespace)) + .isInstanceOf(IllegalArgumentException.class); + } + @Test public void getNamespaceTables_forMysql_ShouldReturnTableNames() throws Exception { getNamespaceTables_forX_ShouldReturnTableNames( 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 0531144764..80834f1576 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.time.LocalDate; @@ -213,6 +214,8 @@ protected Map getCreationOptions() { return Collections.emptyMap(); } + protected abstract AdminTestUtils getAdminTestUtils(String testName); + @AfterAll public void afterAll() throws Exception { try { @@ -415,6 +418,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 *