From 2eb60eba9aa0d252bb46410e57b9fd92106eb114 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Mon, 27 Oct 2025 15:54:42 +0900 Subject: [PATCH 01/12] Add integration tests --- .../JdbcAdminImportTableIntegrationTest.java | 6 +++ ...geAdminImportTableIntegrationTestBase.java | 43 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) 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 24dbca8db4..177a455f38 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 @@ -6,6 +6,7 @@ import com.scalar.db.api.TableMetadata; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; +import com.scalar.db.util.AdminTestUtils; import java.sql.SQLException; import java.util.Collections; import java.util.List; @@ -68,6 +69,11 @@ protected void dropNonImportableTable(String table) throws SQLException { testUtils.dropTable(getNamespace(), table); } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new JdbcAdminTestUtils(getProperties(testName)); + } + @SuppressWarnings("unused") private boolean isOracle() { return JdbcEnv.isOracle(); 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 e9eb568267..5fb3b6959a 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 @@ -1,12 +1,14 @@ package com.scalar.db.api; 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 com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.Column; import com.scalar.db.io.DataType; import com.scalar.db.service.StorageFactory; +import com.scalar.db.util.AdminTestUtils; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; @@ -59,7 +61,7 @@ private void dropTable() throws Exception { if (!testData.isImportableTable()) { dropNonImportableTable(testData.getTableName()); } else { - admin.dropTable(getNamespace(), testData.getTableName()); + admin.dropTable(getNamespace(), testData.getTableName(), true); } } if (!admin.namespaceExists(getNamespace())) { @@ -108,6 +110,8 @@ protected abstract List getFloatCompatibleColumnNamesOnExistingDatabase( protected abstract void dropNonImportableTable(String table) throws Exception; + protected abstract AdminTestUtils getAdminTestUtils(String testName); + @Test public void importTable_ShouldWorkProperly() throws Exception { // Arrange @@ -222,6 +226,43 @@ public void alterColumnType_WideningConversion_ForImportedTable_ShouldAlterPrope } } + @Test + public void dropNamespace_ShouldNotDropNonScalarDBTables() throws Exception { + AdminTestUtils adminTestUtils = getAdminTestUtils(TEST_NAME); + try { + // Arrange + testDataList.addAll(createExistingDatabaseWithAllDataTypes()); + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.importTable( + getNamespace(), + testData.getTableName(), + Collections.emptyMap(), + testData.getOverrideColumnsType()); + } + } + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.dropTable(getNamespace(), testData.getTableName()); + } + } + + // Act + assertThatCode(() -> admin.dropNamespace(getNamespace())) + .isInstanceOf(ExecutionException.class); + + // Assert + assertThat(admin.namespaceExists(getNamespace())).isTrue(); + for (TestData testData : testDataList) { + if (!testData.isImportableTable()) { + adminTestUtils.tableExists(getNamespace(), testData.getTableName()); + } + } + } finally { + adminTestUtils.close(); + } + } + private void importTable_ForImportableTable_ShouldImportProperly( String table, Map overrideColumnsType, TableMetadata metadata) throws ExecutionException { From 169e4b6d2cb2367e5dfe228cb73950383f8d9d04 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Mon, 27 Oct 2025 21:08:02 +0900 Subject: [PATCH 02/12] Fix to avoid deleting non ScalarDB tables when dropping namespaces --- .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 19 ++++++++++++++ .../scalar/db/storage/jdbc/RdbEngineDb2.java | 6 +++++ .../db/storage/jdbc/RdbEngineMysql.java | 7 ++++++ .../db/storage/jdbc/RdbEngineOracle.java | 5 ++++ .../db/storage/jdbc/RdbEnginePostgresql.java | 7 ++++++ .../db/storage/jdbc/RdbEngineSqlServer.java | 7 ++++++ .../db/storage/jdbc/RdbEngineSqlite.java | 8 ++++++ .../db/storage/jdbc/RdbEngineStrategy.java | 2 ++ .../scalar/db/storage/jdbc/JdbcAdminTest.java | 25 ++++++++++++++++--- ...geAdminImportTableIntegrationTestBase.java | 2 +- 10 files changed, 84 insertions(+), 4 deletions(-) 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 abdfa15354..64a046328b 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 @@ -481,6 +481,11 @@ private void deleteMetadataSchema(Connection connection) throws SQLException { @Override public void dropNamespace(String namespace) throws ExecutionException { try (Connection connection = dataSource.getConnection()) { + Set remainingTables = getNamespaceTableNamesInternal(connection, namespace); + if (!remainingTables.isEmpty()) { + throw new IllegalStateException( + CoreError.NAMESPACE_NOT_EMPTY.buildMessage(namespace, remainingTables)); + } execute(connection, rdbEngine.dropNamespaceSql(namespace)); deleteFromNamespacesTable(connection, namespace); deleteNamespacesTableAndMetadataSchemaIfEmpty(connection); @@ -703,6 +708,20 @@ public Set getNamespaceTableNames(String namespace) throws ExecutionExce } } + private Set getNamespaceTableNamesInternal(Connection connection, String namespace) + throws SQLException { + String sql = rdbEngine.getTableNamesInNamespaceSql(namespace); + Set tableNames = new HashSet<>(); + try (PreparedStatement statement = connection.prepareStatement(sql)) { + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + tableNames.add(resultSet.getString(1)); + } + } + } + return tableNames; + } + @Override public boolean namespaceExists(String namespace) throws ExecutionException { String selectQuery = diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java index 8d0ec467eb..6d1d194088 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java @@ -583,4 +583,10 @@ public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( .buildMessage(orderingOnBlobColumn.get())); } } + + @Override + public String getTableNamesInNamespaceSql(String namespace) { + return String.format( + "SELECT TABNAME FROM SYSCAT.TABLES WHERE TABSCHEMA = '%s' AND TYPE = 'T'", 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 fed20d8a81..37ad4ae4fa 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 @@ -504,4 +504,11 @@ public void setConnectionToReadOnly(Connection connection, boolean readOnly) thr // Observed performance degradation when using read-only connections in MySQL. So we do not // set the read-only mode for MySQL connections. } + + @Override + public String getTableNamesInNamespaceSql(String namespace) { + return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" + + namespace + + "'"; + } } 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 4bf9bcb5a6..c450450d02 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 @@ -451,4 +451,9 @@ public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { from.toString(), to.toString())); } } + + @Override + public String getTableNamesInNamespaceSql(String namespace) { + return String.format("SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = '%s'", namespace); + } } 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 c7339c8451..3020522e17 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 @@ -395,4 +395,11 @@ public String tryAddIfNotExistsToCreateIndexSql(String createIndexSql) { getTimeTypeStrategy() { return timeTypeEngine; } + + @Override + public String getTableNamesInNamespaceSql(String namespace) { + return "SELECT table_name FROM information_schema.tables WHERE table_schema = '" + + namespace + + "'"; + } } 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 9b489090e3..34b3d54cd4 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 @@ -431,4 +431,11 @@ public Map getConnectionProperties(JdbcConfig config) { getTimeTypeStrategy() { return timeTypeEngine; } + + @Override + public String getTableNamesInNamespaceSql(String namespace) { + return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" + + namespace + + "'"; + } } 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 cdeb2ce414..98331a04f3 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 @@ -367,4 +367,12 @@ public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { public void setConnectionToReadOnly(Connection connection, boolean readOnly) { // Do nothing. SQLite does not support read-only mode. } + + @Override + public String getTableNamesInNamespaceSql(String namespace) { + return "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '" + + namespace + + NAMESPACE_SEPARATOR + + "%'"; + } } 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 55ebc1937b..2bbd69b920 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 @@ -305,4 +305,6 @@ default void setConnectionToReadOnly(Connection connection, boolean readOnly) */ default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( ScanAll scanAll, TableMetadata metadata) {} + + String getTableNamesInNamespaceSql(String namespace); } diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java index 7b2528e2e2..de6da18d87 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java @@ -1957,6 +1957,7 @@ public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception { JdbcAdmin admin = createJdbcAdminFor(RdbEngine.SQLITE); Connection connection = mock(Connection.class); + PreparedStatement getTableNamesPrepStmt = mock(PreparedStatement.class); PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class); Statement selectAllFromNamespaceTablePrepStmt = mock(Statement.class); Statement selectAllFromMetadataTablePrepStmt = mock(Statement.class); @@ -1967,7 +1968,13 @@ public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception { selectAllFromNamespaceTablePrepStmt, selectAllFromMetadataTablePrepStmt, dropNamespaceTableStmt); - when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTablePrepStmt); + // Mock for getNamespaceTableNamesInternal() check - returns empty ResultSet (namespace is + // empty) + ResultSet emptyResultSet = mock(ResultSet.class); + when(emptyResultSet.next()).thenReturn(false); + when(getTableNamesPrepStmt.executeQuery()).thenReturn(emptyResultSet); + when(connection.prepareStatement(anyString())) + .thenReturn(getTableNamesPrepStmt, deleteFromNamespaceTablePrepStmt); when(dataSource.getConnection()).thenReturn(connection); // Only the metadata schema is left ResultSet resultSet1 = @@ -2005,6 +2012,7 @@ private void dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNa Connection connection = mock(Connection.class); Statement dropNamespaceStmt = mock(Statement.class); + PreparedStatement isNamespaceEmptyStatementMock = mock(PreparedStatement.class); PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class); Statement selectAllFromNamespaceTablePrepStmt = mock(Statement.class); Statement selectAllFromMetadataTablePrepStmt = mock(Statement.class); @@ -2018,7 +2026,12 @@ private void dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNa selectAllFromMetadataTablePrepStmt, dropNamespaceTableStmt, dropMetadataSchemaStmt); - when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTablePrepStmt); + // Mock for isNamespaceEmpty() check - returns empty ResultSet (namespace is empty) + ResultSet emptyResultSet = mock(ResultSet.class); + when(emptyResultSet.next()).thenReturn(false); + when(isNamespaceEmptyStatementMock.executeQuery()).thenReturn(emptyResultSet); + when(connection.prepareStatement(anyString())) + .thenReturn(isNamespaceEmptyStatementMock, deleteFromNamespaceTablePrepStmt); when(dataSource.getConnection()).thenReturn(connection); // Only the metadata schema is left ResultSet resultSet = @@ -2116,6 +2129,7 @@ private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( Connection connection = mock(Connection.class); Statement dropNamespaceStatementMock = mock(Statement.class); + PreparedStatement isNamespaceEmptyStatementMock = mock(PreparedStatement.class); PreparedStatement deleteFromNamespaceTableMock = mock(PreparedStatement.class); Statement selectNamespaceStatementMock = mock(Statement.class); if (rdbEngine != RdbEngine.SQLITE) { @@ -2124,7 +2138,12 @@ private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( } else { when(connection.createStatement()).thenReturn(selectNamespaceStatementMock); } - when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTableMock); + // Mock for isNamespaceEmpty() check - returns empty ResultSet (namespace is empty) + ResultSet emptyResultSet = mock(ResultSet.class); + when(emptyResultSet.next()).thenReturn(false); + when(isNamespaceEmptyStatementMock.executeQuery()).thenReturn(emptyResultSet); + when(connection.prepareStatement(anyString())) + .thenReturn(isNamespaceEmptyStatementMock, deleteFromNamespaceTableMock); when(dataSource.getConnection()).thenReturn(connection); // Namespaces table contains other namespaces ResultSet resultSet = 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 5fb3b6959a..0c7853dc89 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 @@ -249,7 +249,7 @@ public void dropNamespace_ShouldNotDropNonScalarDBTables() throws Exception { // Act assertThatCode(() -> admin.dropNamespace(getNamespace())) - .isInstanceOf(ExecutionException.class); + .isInstanceOf(IllegalStateException.class); // Assert assertThat(admin.namespaceExists(getNamespace())).isTrue(); From ea08896f48f2f26904897aca48c2d1b8e53b3353 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 28 Oct 2025 08:43:14 +0900 Subject: [PATCH 03/12] Disable the test for SQLite --- .../db/storage/jdbc/JdbcAdminImportTableIntegrationTest.java | 4 ++++ 1 file changed, 4 insertions(+) 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 177a455f38..788e377ba8 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 @@ -269,4 +269,8 @@ public void alterColumnType_Oracle_WideningConversion_ForImportedTable_ShouldAlt } } } + + @Test + @DisabledIf("isSqlite") + public void dropNamespace_ShouldNotDropNonScalarDBTables() {} } From 6d051e2909dfe46a91c007c5c537dac45e508b92 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 28 Oct 2025 09:09:51 +0900 Subject: [PATCH 04/12] Add integration tests for ConsensusCommitAdmin --- ...tTableIntegrationTestWithJdbcDatabase.java | 11 +++++ .../JdbcAdminImportTableIntegrationTest.java | 1 + ...onAdminImportTableIntegrationTestBase.java | 41 +++++++++++++++++++ 3 files changed, 53 insertions(+) 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 d589c140cc..bd6ad7357c 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 @@ -3,6 +3,7 @@ import com.scalar.db.api.DistributedStorageAdminImportTableIntegrationTestBase.TestData; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.transaction.consensuscommit.ConsensusCommitAdminImportTableIntegrationTestBase; +import com.scalar.db.util.AdminTestUtils; import java.sql.SQLException; import java.util.List; import java.util.Properties; @@ -53,6 +54,11 @@ protected void dropNonImportableTable(String table) throws SQLException { testUtils.dropTable(getNamespace(), table); } + @Override + protected AdminTestUtils getAdminTestUtils(String testName) { + return new JdbcAdminTestUtils(getProperties(testName)); + } + @SuppressWarnings("unused") private boolean isSqlite() { return JdbcEnv.isSqlite(); @@ -72,4 +78,9 @@ public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationEx throws ExecutionException { super.importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException(); } + + @Test + @Override + @DisabledIf("isSqlite") + public void dropNamespace_ShouldNotDropNonScalarDBTables() {} } 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 788e377ba8..4b0f54fae5 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 @@ -271,6 +271,7 @@ public void alterColumnType_Oracle_WideningConversion_ForImportedTable_ShouldAlt } @Test + @Override @DisabledIf("isSqlite") public void dropNamespace_ShouldNotDropNonScalarDBTables() {} } 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 25010b5be3..21a499f322 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 @@ -1,6 +1,7 @@ package com.scalar.db.api; 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 com.scalar.db.api.DistributedStorageAdminImportTableIntegrationTestBase.TestData; @@ -9,6 +10,7 @@ import com.scalar.db.io.Column; import com.scalar.db.io.DataType; import com.scalar.db.service.TransactionFactory; +import com.scalar.db.util.AdminTestUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -104,6 +106,8 @@ protected void afterAll() throws Exception {} protected abstract void dropNonImportableTable(String table) throws Exception; + protected abstract AdminTestUtils getAdminTestUtils(String testName); + @Test public void importTable_ShouldWorkProperly() throws Exception { // Arrange @@ -134,6 +138,43 @@ public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationEx .isInstanceOf(UnsupportedOperationException.class); } + @Test + public void dropNamespace_ShouldNotDropNonScalarDBTables() throws Exception { + AdminTestUtils adminTestUtils = getAdminTestUtils(TEST_NAME); + try { + // Arrange + testDataList.addAll(createExistingDatabaseWithAllDataTypes()); + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.importTable( + getNamespace(), + testData.getTableName(), + Collections.emptyMap(), + testData.getOverrideColumnsType()); + } + } + for (TestData testData : testDataList) { + if (testData.isImportableTable()) { + admin.dropTable(getNamespace(), testData.getTableName()); + } + } + + // Act + assertThatCode(() -> admin.dropNamespace(getNamespace())) + .isInstanceOf(IllegalStateException.class); + + // Assert + assertThat(admin.namespaceExists(getNamespace())).isTrue(); + for (TestData testData : testDataList) { + if (!testData.isImportableTable()) { + adminTestUtils.tableExists(getNamespace(), testData.getTableName()); + } + } + } finally { + adminTestUtils.close(); + } + } + private void importTable_ForImportableTable_ShouldImportProperly( String table, Map overrideColumnsType, TableMetadata metadata) throws ExecutionException { From 05b8493611d5b86439a448e3365ffe9eba5cf1f0 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 28 Oct 2025 11:29:38 +0900 Subject: [PATCH 05/12] Fix based on review --- .../main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java | 3 ++- .../main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java | 5 ++--- .../java/com/scalar/db/storage/jdbc/RdbEngineMysql.java | 6 ++---- .../java/com/scalar/db/storage/jdbc/RdbEngineOracle.java | 4 ++-- .../com/scalar/db/storage/jdbc/RdbEnginePostgresql.java | 6 ++---- .../com/scalar/db/storage/jdbc/RdbEngineSqlServer.java | 6 ++---- .../java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java | 7 ++----- .../java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java | 2 +- ...tributedStorageAdminImportTableIntegrationTestBase.java | 2 +- ...utedTransactionAdminImportTableIntegrationTestBase.java | 2 +- 10 files changed, 17 insertions(+), 26 deletions(-) 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 64a046328b..03784b7423 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 @@ -710,9 +710,10 @@ public Set getNamespaceTableNames(String namespace) throws ExecutionExce private Set getNamespaceTableNamesInternal(Connection connection, String namespace) throws SQLException { - String sql = rdbEngine.getTableNamesInNamespaceSql(namespace); + String sql = rdbEngine.getTableNamesInNamespaceSql(); 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)); diff --git a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java index 6d1d194088..f914228a85 100644 --- a/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java +++ b/core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java @@ -585,8 +585,7 @@ public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( } @Override - public String getTableNamesInNamespaceSql(String namespace) { - return String.format( - "SELECT TABNAME FROM SYSCAT.TABLES WHERE TABSCHEMA = '%s' AND TYPE = 'T'", namespace); + public String getTableNamesInNamespaceSql() { + return "SELECT TABNAME FROM SYSCAT.TABLES WHERE TABSCHEMA = ? AND TYPE = 'T'"; } } 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 37ad4ae4fa..74363ecc37 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 @@ -506,9 +506,7 @@ public void setConnectionToReadOnly(Connection connection, boolean readOnly) thr } @Override - public String getTableNamesInNamespaceSql(String namespace) { - return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" - + namespace - + "'"; + 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 c450450d02..352b68c9c5 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 @@ -453,7 +453,7 @@ public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) { } @Override - public String getTableNamesInNamespaceSql(String namespace) { - return String.format("SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = '%s'", namespace); + 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 3020522e17..0815be9def 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 @@ -397,9 +397,7 @@ public String tryAddIfNotExistsToCreateIndexSql(String createIndexSql) { } @Override - public String getTableNamesInNamespaceSql(String namespace) { - return "SELECT table_name FROM information_schema.tables WHERE table_schema = '" - + namespace - + "'"; + 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 34b3d54cd4..c833c25817 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 @@ -433,9 +433,7 @@ public Map getConnectionProperties(JdbcConfig config) { } @Override - public String getTableNamesInNamespaceSql(String namespace) { - return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '" - + namespace - + "'"; + 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 98331a04f3..f5d41ebaf5 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 @@ -369,10 +369,7 @@ public void setConnectionToReadOnly(Connection connection, boolean readOnly) { } @Override - public String getTableNamesInNamespaceSql(String namespace) { - return "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '" - + namespace - + NAMESPACE_SEPARATOR - + "%'"; + public String getTableNamesInNamespaceSql() { + return "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE ?"; } } 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 2bbd69b920..2a48cd21d1 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 @@ -306,5 +306,5 @@ default void setConnectionToReadOnly(Connection connection, boolean readOnly) default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported( ScanAll scanAll, TableMetadata metadata) {} - String getTableNamesInNamespaceSql(String namespace); + String getTableNamesInNamespaceSql(); } 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 0c7853dc89..f316354111 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 @@ -255,7 +255,7 @@ public void dropNamespace_ShouldNotDropNonScalarDBTables() throws Exception { assertThat(admin.namespaceExists(getNamespace())).isTrue(); for (TestData testData : testDataList) { if (!testData.isImportableTable()) { - adminTestUtils.tableExists(getNamespace(), testData.getTableName()); + assertThat(adminTestUtils.tableExists(getNamespace(), testData.getTableName())).isTrue(); } } } finally { 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 21a499f322..49062b0ba4 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 @@ -167,7 +167,7 @@ public void dropNamespace_ShouldNotDropNonScalarDBTables() throws Exception { assertThat(admin.namespaceExists(getNamespace())).isTrue(); for (TestData testData : testDataList) { if (!testData.isImportableTable()) { - adminTestUtils.tableExists(getNamespace(), testData.getTableName()); + assertThat(adminTestUtils.tableExists(getNamespace(), testData.getTableName())).isTrue(); } } } finally { From 96b13d3aa747559d7cfced22c4df8cf1c2e86a93 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 28 Oct 2025 17:54:21 +0900 Subject: [PATCH 06/12] Add an integration test case for all storages --- .../cassandra/CassandraAdminTestUtils.java | 5 +++ .../storage/cosmos/CosmosAdminTestUtils.java | 9 ++++ .../storage/dynamo/DynamoAdminTestUtils.java | 14 +++++++ ...tTableIntegrationTestWithJdbcDatabase.java | 11 ----- .../db/storage/jdbc/JdbcAdminTestUtils.java | 13 ++++++ .../MultiStorageAdminTestUtils.java | 16 ++++++++ .../java/com/scalar/db/common/CoreError.java | 6 +++ .../scalar/db/storage/cosmos/CosmosAdmin.java | 12 ++++++ .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 7 ++-- ...ibutedStorageAdminIntegrationTestBase.java | 23 +++++++++++ ...onAdminImportTableIntegrationTestBase.java | 41 ------------------- .../com/scalar/db/util/AdminTestUtils.java | 9 ++++ 12 files changed, 111 insertions(+), 55 deletions(-) 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 7e434aa50b..c2216ee808 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 @@ -57,6 +57,11 @@ public void corruptMetadata(String namespace, String table) { // Do nothing } + @Override + public void deleteMetadata(String namespace, String table) throws Exception { + // Do nothing + } + @Override public void dropNamespace(String namespace) { String dropKeyspaceQuery = 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 3f960f137b..0adb16eefc 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 @@ -86,6 +86,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.TABLE_METADATA_CONTAINER); + container.deleteItem( + fullTableName, new PartitionKey(fullTableName), new CosmosItemRequestOptions()); + } + /** * Retrieve the stored procedure for the given table * 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 dece9cd6d6..ad3fd80c98 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 @@ -171,6 +171,20 @@ 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(); 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 bd6ad7357c..d589c140cc 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 @@ -3,7 +3,6 @@ import com.scalar.db.api.DistributedStorageAdminImportTableIntegrationTestBase.TestData; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.transaction.consensuscommit.ConsensusCommitAdminImportTableIntegrationTestBase; -import com.scalar.db.util.AdminTestUtils; import java.sql.SQLException; import java.util.List; import java.util.Properties; @@ -54,11 +53,6 @@ protected void dropNonImportableTable(String table) throws SQLException { testUtils.dropTable(getNamespace(), table); } - @Override - protected AdminTestUtils getAdminTestUtils(String testName) { - return new JdbcAdminTestUtils(getProperties(testName)); - } - @SuppressWarnings("unused") private boolean isSqlite() { return JdbcEnv.isSqlite(); @@ -78,9 +72,4 @@ public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationEx throws ExecutionException { super.importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationException(); } - - @Test - @Override - @DisabledIf("isSqlite") - public void dropNamespace_ShouldNotDropNonScalarDBTables() {} } 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 c0a83577c2..e9a72016d8 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 @@ -64,6 +64,19 @@ 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) + + " = '" + + getFullTableName(namespace, table) + + "'"; + execute(deleteMetadataStatement); + } + private void execute(String sql) throws SQLException { try (Connection connection = dataSource.getConnection()) { JdbcAdmin.execute(connection, sql); 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 55e9f5a276..99881921b3 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 @@ -116,6 +116,22 @@ 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) + + " = '" + + getFullTableName(namespace, table) + + "'"; + execute(deleteMetadataStatement); + } + @Override public void dropNamespace(String namespace) throws SQLException { boolean existsOnCassandra = namespaceExistsOnCassandra(namespace); diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index 259b029be9..baab4357a8 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -808,6 +808,12 @@ public enum CoreError implements ScalarDbError { "Db2 does not support column type conversion from %s to %s", "", ""), + NAMESPACE_WITH_NON_SCALARDB_TABLES_CANNOT_BE_DROPPED( + Category.USER_ERROR, + "0241", + "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 98554dad02..fc480aa0e4 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 @@ -344,6 +344,12 @@ private void deleteTableMetadata(String namespace, String table) throws Executio @Override public void dropNamespace(String namespace) throws ExecutionException { try { + Set remainingTables = getNamespaceTableNamesInternal(namespace); + if (!remainingTables.isEmpty()) { + throw new IllegalArgumentException( + CoreError.NAMESPACE_WITH_NON_SCALARDB_TABLES_CANNOT_BE_DROPPED.buildMessage( + namespace, remainingTables)); + } client.getDatabase(namespace).delete(); getNamespacesContainer() .deleteItem(new CosmosNamespace(namespace), new CosmosItemRequestOptions()); @@ -785,4 +791,10 @@ private boolean containerExists(String databaseId, String containerId) throws Co public StorageInfo getStorageInfo(String namespace) { return STORAGE_INFO; } + + private Set getNamespaceTableNamesInternal(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 03784b7423..35b2f9011c 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 @@ -45,7 +45,7 @@ public class JdbcAdmin implements DistributedStorageAdmin { public static final String METADATA_TABLE = "metadata"; public static final String NAMESPACES_TABLE = "namespaces"; - @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"; @@ -483,8 +483,9 @@ public void dropNamespace(String namespace) throws ExecutionException { try (Connection connection = dataSource.getConnection()) { Set remainingTables = getNamespaceTableNamesInternal(connection, namespace); if (!remainingTables.isEmpty()) { - throw new IllegalStateException( - CoreError.NAMESPACE_NOT_EMPTY.buildMessage(namespace, remainingTables)); + throw new IllegalArgumentException( + CoreError.NAMESPACE_WITH_NON_SCALARDB_TABLES_CANNOT_BE_DROPPED.buildMessage( + namespace, remainingTables)); } execute(connection, rdbEngine.dropNamespaceSql(namespace)); deleteFromNamespacesTable(connection, 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 cc40db8d85..451746621b 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 @@ -423,6 +423,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/api/DistributedTransactionAdminImportTableIntegrationTestBase.java b/integration-test/src/main/java/com/scalar/db/api/DistributedTransactionAdminImportTableIntegrationTestBase.java index 49062b0ba4..25010b5be3 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 @@ -1,7 +1,6 @@ package com.scalar.db.api; 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 com.scalar.db.api.DistributedStorageAdminImportTableIntegrationTestBase.TestData; @@ -10,7 +9,6 @@ import com.scalar.db.io.Column; import com.scalar.db.io.DataType; import com.scalar.db.service.TransactionFactory; -import com.scalar.db.util.AdminTestUtils; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -106,8 +104,6 @@ protected void afterAll() throws Exception {} protected abstract void dropNonImportableTable(String table) throws Exception; - protected abstract AdminTestUtils getAdminTestUtils(String testName); - @Test public void importTable_ShouldWorkProperly() throws Exception { // Arrange @@ -138,43 +134,6 @@ public void importTable_ForUnsupportedDatabase_ShouldThrowUnsupportedOperationEx .isInstanceOf(UnsupportedOperationException.class); } - @Test - public void dropNamespace_ShouldNotDropNonScalarDBTables() throws Exception { - AdminTestUtils adminTestUtils = getAdminTestUtils(TEST_NAME); - try { - // Arrange - testDataList.addAll(createExistingDatabaseWithAllDataTypes()); - for (TestData testData : testDataList) { - if (testData.isImportableTable()) { - admin.importTable( - getNamespace(), - testData.getTableName(), - Collections.emptyMap(), - testData.getOverrideColumnsType()); - } - } - for (TestData testData : testDataList) { - if (testData.isImportableTable()) { - admin.dropTable(getNamespace(), testData.getTableName()); - } - } - - // Act - assertThatCode(() -> admin.dropNamespace(getNamespace())) - .isInstanceOf(IllegalStateException.class); - - // Assert - assertThat(admin.namespaceExists(getNamespace())).isTrue(); - for (TestData testData : testDataList) { - if (!testData.isImportableTable()) { - assertThat(adminTestUtils.tableExists(getNamespace(), testData.getTableName())).isTrue(); - } - } - } finally { - adminTestUtils.close(); - } - } - private void importTable_ForImportableTable_ShouldImportProperly( String table, Map overrideColumnsType, TableMetadata metadata) throws ExecutionException { 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 02ddc910f9..42ecb473a9 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 @@ -56,6 +56,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 and the table metadata for the coordinator tables are present or not. * From c68cf2a2f901fa94d3395dca47cd30edcee07877 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Tue, 28 Oct 2025 18:25:05 +0900 Subject: [PATCH 07/12] Fix --- ...bcAdminCaseSensitivityIntegrationTest.java | 6 +++++ .../jdbc/JdbcAdminIntegrationTest.java | 6 +++++ .../db/storage/jdbc/JdbcAdminTestUtils.java | 11 +++++---- .../MultiStorageAdminTestUtils.java | 11 +++++---- .../scalar/db/storage/cosmos/CosmosAdmin.java | 2 ++ .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 3 +++ .../db/storage/jdbc/RdbEngineSqlite.java | 3 ++- .../db/storage/cosmos/CosmosAdminTest.java | 22 ++++++++++++++---- .../scalar/db/storage/jdbc/JdbcAdminTest.java | 23 +++++++------------ 9 files changed, 59 insertions(+), 28 deletions(-) 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 9c983ad1c7..1dec6b8605 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 @@ -83,6 +83,12 @@ private boolean isWideningColumnTypeConversionNotFullySupported() { return JdbcTestUtils.isOracle(rdbEngine) || JdbcTestUtils.isSqlite(rdbEngine); } + @Test + @Override + @DisabledIf("isSqlite") + public void + dropNamespace_ForNamespaceWithNonScalarDBManagedTables_ShouldThrowIllegalArgumentException() {} + @Test @Override @DisabledIf("isDb2") 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 aef7b4c00f..d3e7b06e91 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 @@ -84,6 +84,12 @@ private boolean isWideningColumnTypeConversionNotFullySupported() { return JdbcTestUtils.isOracle(rdbEngine) || JdbcTestUtils.isSqlite(rdbEngine); } + @Test + @Override + @DisabledIf("isSqlite") + public void + dropNamespace_ForNamespaceWithNonScalarDBManagedTables_ShouldThrowIllegalArgumentException() {} + @Test @Override @DisabledIf("isDb2") 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 e9a72016d8..4afdb05a74 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 @@ -71,10 +71,13 @@ public void deleteMetadata(String namespace, String table) throws Exception { + rdbEngine.encloseFullTableName(metadataSchema, JdbcAdmin.METADATA_TABLE) + " WHERE " + rdbEngine.enclose(JdbcAdmin.METADATA_COL_FULL_TABLE_NAME) - + " = '" - + getFullTableName(namespace, table) - + "'"; - execute(deleteMetadataStatement); + + " = ?"; + 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 { 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 99881921b3..e1b1c9cac0 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 @@ -126,10 +126,13 @@ public void deleteMetadata(String namespace, String table) throws Exception { + rdbEngine.encloseFullTableName(jdbcMetadataSchema, JdbcAdmin.METADATA_TABLE) + " WHERE " + rdbEngine.enclose(JdbcAdmin.METADATA_COL_FULL_TABLE_NAME) - + " = '" - + getFullTableName(namespace, table) - + "'"; - execute(deleteMetadataStatement); + + " = ?"; + try (Connection connection = dataSource.getConnection(); + PreparedStatement preparedStatement = + connection.prepareStatement(deleteMetadataStatement)) { + preparedStatement.setString(1, getFullTableName(namespace, table)); + preparedStatement.executeUpdate(); + } } @Override 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 fc480aa0e4..c7d45ba69f 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 @@ -354,6 +354,8 @@ public void dropNamespace(String namespace) throws ExecutionException { getNamespacesContainer() .deleteItem(new CosmosNamespace(namespace), new CosmosItemRequestOptions()); deleteMetadataDatabaseIfEmpty(); + } catch (IllegalArgumentException e) { + throw e; } catch (RuntimeException e) { throw new ExecutionException(String.format("Deleting the %s database failed", namespace), e); } 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 35b2f9011c..f6cd085d5d 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 @@ -712,6 +712,9 @@ public Set getNamespaceTableNames(String namespace) throws ExecutionExce private Set getNamespaceTableNamesInternal(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); 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 f5d41ebaf5..895420d63f 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 @@ -370,6 +370,7 @@ public void setConnectionToReadOnly(Connection connection, boolean readOnly) { @Override public String getTableNamesInNamespaceSql() { - return "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE ?"; + // Do nothing. Namespace is just a table prefix in the SQLite implementation. + return null; } } diff --git a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java index 0e1477c2a6..2697ded84b 100644 --- a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java @@ -587,7 +587,8 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( // Arrange String namespace = "ns"; CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); - when(client.getDatabase(any())).thenReturn(database, metadataDatabase); + when(client.getDatabase(namespace)).thenReturn(database); + when(client.getDatabase(METADATA_DATABASE)).thenReturn(metadataDatabase); CosmosContainer namespacesContainer = mock(CosmosContainer.class); when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); @@ -597,6 +598,12 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( .thenReturn(pagedIterable); when(pagedIterable.stream()).thenReturn(Stream.of(new CosmosNamespace(METADATA_DATABASE))); + @SuppressWarnings("unchecked") + CosmosPagedIterable emptyContainerPagedIterable = + mock(CosmosPagedIterable.class); + when(emptyContainerPagedIterable.stream()).thenReturn(Stream.empty()); + when(database.readAllContainers()).thenReturn(emptyContainerPagedIterable); + @SuppressWarnings("unchecked") CosmosPagedIterable containerPagedIterable = mock(CosmosPagedIterable.class); @@ -613,7 +620,7 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( admin.dropNamespace(namespace); // Assert - verify(client).getDatabase(namespace); + verify(client, times(2)).getDatabase(namespace); verify(database).delete(); verify(client, times(3)).getDatabase(METADATA_DATABASE); verify(metadataDatabase, times(2)).getContainer(CosmosAdmin.NAMESPACES_CONTAINER); @@ -628,10 +635,17 @@ public void dropNamespace_WithExistingDatabaseAndSomeNamespacesLeft_ShouldDropDa // Arrange String namespace = "ns"; CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); - when(client.getDatabase(any())).thenReturn(database, metadataDatabase); + when(client.getDatabase(namespace)).thenReturn(database); + when(client.getDatabase(METADATA_DATABASE)).thenReturn(metadataDatabase); CosmosContainer namespacesContainer = mock(CosmosContainer.class); when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); + @SuppressWarnings("unchecked") + CosmosPagedIterable emptyContainerPagedIterable = + mock(CosmosPagedIterable.class); + when(emptyContainerPagedIterable.stream()).thenReturn(Stream.empty()); + when(database.readAllContainers()).thenReturn(emptyContainerPagedIterable); + @SuppressWarnings("unchecked") CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); when(namespacesContainer.queryItems(anyString(), any(), any())).thenReturn(pagedIterable); @@ -642,7 +656,7 @@ public void dropNamespace_WithExistingDatabaseAndSomeNamespacesLeft_ShouldDropDa admin.dropNamespace(namespace); // Assert - verify(client).getDatabase(namespace); + verify(client, times(2)).getDatabase(namespace); verify(database).delete(); verify(client, times(2)).getDatabase(METADATA_DATABASE); verify(metadataDatabase, times(2)).getContainer(CosmosAdmin.NAMESPACES_CONTAINER); diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java index de6da18d87..20d8760ef4 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java @@ -1957,7 +1957,6 @@ public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception { JdbcAdmin admin = createJdbcAdminFor(RdbEngine.SQLITE); Connection connection = mock(Connection.class); - PreparedStatement getTableNamesPrepStmt = mock(PreparedStatement.class); PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class); Statement selectAllFromNamespaceTablePrepStmt = mock(Statement.class); Statement selectAllFromMetadataTablePrepStmt = mock(Statement.class); @@ -1968,13 +1967,7 @@ public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception { selectAllFromNamespaceTablePrepStmt, selectAllFromMetadataTablePrepStmt, dropNamespaceTableStmt); - // Mock for getNamespaceTableNamesInternal() check - returns empty ResultSet (namespace is - // empty) - ResultSet emptyResultSet = mock(ResultSet.class); - when(emptyResultSet.next()).thenReturn(false); - when(getTableNamesPrepStmt.executeQuery()).thenReturn(emptyResultSet); - when(connection.prepareStatement(anyString())) - .thenReturn(getTableNamesPrepStmt, deleteFromNamespaceTablePrepStmt); + when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTablePrepStmt); when(dataSource.getConnection()).thenReturn(connection); // Only the metadata schema is left ResultSet resultSet1 = @@ -2129,21 +2122,21 @@ private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( Connection connection = mock(Connection.class); Statement dropNamespaceStatementMock = mock(Statement.class); - PreparedStatement isNamespaceEmptyStatementMock = mock(PreparedStatement.class); PreparedStatement deleteFromNamespaceTableMock = mock(PreparedStatement.class); Statement selectNamespaceStatementMock = mock(Statement.class); if (rdbEngine != RdbEngine.SQLITE) { + PreparedStatement getTableNamesPrepStmt = mock(PreparedStatement.class); when(connection.createStatement()) .thenReturn(dropNamespaceStatementMock, selectNamespaceStatementMock); + ResultSet emptyResultSet = mock(ResultSet.class); + when(emptyResultSet.next()).thenReturn(false); + when(getTableNamesPrepStmt.executeQuery()).thenReturn(emptyResultSet); + when(connection.prepareStatement(anyString())) + .thenReturn(getTableNamesPrepStmt, deleteFromNamespaceTableMock); } else { when(connection.createStatement()).thenReturn(selectNamespaceStatementMock); + when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTableMock); } - // Mock for isNamespaceEmpty() check - returns empty ResultSet (namespace is empty) - ResultSet emptyResultSet = mock(ResultSet.class); - when(emptyResultSet.next()).thenReturn(false); - when(isNamespaceEmptyStatementMock.executeQuery()).thenReturn(emptyResultSet); - when(connection.prepareStatement(anyString())) - .thenReturn(isNamespaceEmptyStatementMock, deleteFromNamespaceTableMock); when(dataSource.getConnection()).thenReturn(connection); // Namespaces table contains other namespaces ResultSet resultSet = From d34ea7aec0702ae3f53695d8a8b2b9bb486facb0 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Wed, 29 Oct 2025 09:31:30 +0900 Subject: [PATCH 08/12] Add missing error --- core/src/main/java/com/scalar/db/common/CoreError.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/com/scalar/db/common/CoreError.java b/core/src/main/java/com/scalar/db/common/CoreError.java index 710a2bd548..2ba2736377 100644 --- a/core/src/main/java/com/scalar/db/common/CoreError.java +++ b/core/src/main/java/com/scalar/db/common/CoreError.java @@ -1137,6 +1137,8 @@ public enum CoreError implements ScalarDbError { "Altering a column type failed. Table: %s; Column: %s; New column type: %s", "", ""), + JDBC_MYSQL_GETTING_CONNECTION_METADATA_FAILED( + Category.INTERNAL_ERROR, "0063", "Getting the MySQL JDBC connection metadata failed", "", ""), // // Errors for the unknown transaction status error category From c90108b1bfadaf36d60ecc99ae18d98ae2f171e2 Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Wed, 29 Oct 2025 09:48:33 +0900 Subject: [PATCH 09/12] Disable the test for DynamoDB --- core/integration.sqlite3 | Bin 0 -> 204800 bytes ...amoAdminCaseSensitivityIntegrationTest.java | 5 +++++ .../dynamo/DynamoAdminIntegrationTest.java | 5 +++++ 3 files changed, 10 insertions(+) create mode 100644 core/integration.sqlite3 diff --git a/core/integration.sqlite3 b/core/integration.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..0614f0c315996736b578fe4e85e029569ad29d98 GIT binary patch literal 204800 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 Date: Wed, 29 Oct 2025 23:46:21 +0900 Subject: [PATCH 10/12] Fix based on review --- core/integration.sqlite3 | Bin 204800 -> 0 bytes .../JdbcAdminImportTableIntegrationTest.java | 5 -- .../scalar/db/storage/cosmos/CosmosAdmin.java | 6 +- .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 4 +- .../db/storage/cosmos/CosmosAdminTest.java | 28 +++++++ .../scalar/db/storage/jdbc/JdbcAdminTest.java | 70 ++++++++++++++++++ 6 files changed, 103 insertions(+), 10 deletions(-) delete mode 100644 core/integration.sqlite3 diff --git a/core/integration.sqlite3 b/core/integration.sqlite3 deleted file mode 100644 index 0614f0c315996736b578fe4e85e029569ad29d98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 204800 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 remainingTables = getNamespaceTableNamesInternal(namespace); + Set remainingTables = getRawTableNames(namespace); if (!remainingTables.isEmpty()) { throw new IllegalArgumentException( CoreError.NAMESPACE_WITH_NON_SCALARDB_TABLES_CANNOT_BE_DROPPED.buildMessage( @@ -794,7 +794,7 @@ public StorageInfo getStorageInfo(String namespace) { return STORAGE_INFO; } - private Set getNamespaceTableNamesInternal(String namespace) { + 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 f6cd085d5d..9956fbcad1 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 @@ -481,7 +481,7 @@ private void deleteMetadataSchema(Connection connection) throws SQLException { @Override public void dropNamespace(String namespace) throws ExecutionException { try (Connection connection = dataSource.getConnection()) { - Set remainingTables = getNamespaceTableNamesInternal(connection, namespace); + Set remainingTables = getInternalTableNames(connection, namespace); if (!remainingTables.isEmpty()) { throw new IllegalArgumentException( CoreError.NAMESPACE_WITH_NON_SCALARDB_TABLES_CANNOT_BE_DROPPED.buildMessage( @@ -709,7 +709,7 @@ public Set getNamespaceTableNames(String namespace) throws ExecutionExce } } - private Set getNamespaceTableNamesInternal(Connection connection, String namespace) + private Set getInternalTableNames(Connection connection, String namespace) throws SQLException { String sql = rdbEngine.getTableNamesInNamespaceSql(); if (Strings.isNullOrEmpty(sql)) { diff --git a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java index 2697ded84b..2aa946a67a 100644 --- a/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/cosmos/CosmosAdminTest.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; @@ -665,6 +666,33 @@ public void dropNamespace_WithExistingDatabaseAndSomeNamespacesLeft_ShouldDropDa verify(metadataDatabase, never()).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(METADATA_DATABASE)).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/JdbcAdminTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java index 20d8760ef4..d2982ed601 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java @@ -8,6 +8,7 @@ import static com.scalar.db.storage.jdbc.JdbcAdmin.hasDifferentClusteringOrders; 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; @@ -2157,6 +2158,75 @@ private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( verify(selectNamespaceStatementMock).executeQuery(selectNamespaceStatement); } + @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_WithNonScalarDBTableLeftForDb2_ShouldThrowIllegalArgumentException() + throws Exception { + dropNamespace_WithNonScalarDBTableLeftForX_ShouldThrowIllegalArgumentException(RdbEngine.DB2); + } + + 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); + PreparedStatement deleteFromNamespaceTableMock = mock(PreparedStatement.class); + Statement selectNamespaceStatementMock = mock(Statement.class); + if (rdbEngine != RdbEngine.SQLITE) { + 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(anyString())) + .thenReturn(getTableNamesPrepStmt, deleteFromNamespaceTableMock); + } else { + when(connection.createStatement()).thenReturn(selectNamespaceStatementMock); + when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTableMock); + } + 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( From 6626c937aad3dc292d785cdd1e450df8f28ba30b Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Wed, 29 Oct 2025 23:55:03 +0900 Subject: [PATCH 11/12] Remove unnecessary override --- .../storage/jdbc/JdbcAdminImportTableIntegrationTest.java | 6 ------ 1 file changed, 6 deletions(-) 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 9a479aa51c..faab3ab1a1 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 @@ -6,7 +6,6 @@ import com.scalar.db.api.TableMetadata; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; -import com.scalar.db.util.AdminTestUtils; import java.sql.SQLException; import java.util.Collections; import java.util.List; @@ -69,11 +68,6 @@ protected void dropNonImportableTable(String table) throws SQLException { testUtils.dropTable(getNamespace(), table); } - @Override - protected AdminTestUtils getAdminTestUtils(String testName) { - return new JdbcAdminTestUtils(getProperties(testName)); - } - @SuppressWarnings("unused") private boolean isOracle() { return JdbcEnv.isOracle(); From 539bfa8386dffad119e9cf465ad64900648df89f Mon Sep 17 00:00:00 2001 From: Kodai Doki Date: Thu, 30 Oct 2025 12:40:18 +0900 Subject: [PATCH 12/12] Apply suggestions --- .../scalar/db/storage/cosmos/CosmosAdmin.java | 2 +- .../scalar/db/storage/jdbc/JdbcAdminTest.java | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) 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 70b801696c..c2c3f3eb0c 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 @@ -57,13 +57,13 @@ public class CosmosAdmin implements DistributedStorageAdmin { public static final String TABLE_METADATA_CONTAINER = "metadata"; public static final String NAMESPACES_CONTAINER = "namespaces"; - @VisibleForTesting public static final String STORED_PROCEDURE_FILE_NAME = "mutate.js"; private static final String ID = "id"; private static final String CONCATENATED_PARTITION_KEY = "concatenatedPartitionKey"; private static final String PARTITION_KEY_PATH = "/" + CONCATENATED_PARTITION_KEY; private static final String CLUSTERING_KEY_PATH_PREFIX = "/clusteringKey/"; private static final String SECONDARY_INDEX_KEY_PATH_PREFIX = "/values/"; private static final String EXCLUDED_PATH = "/*"; + @VisibleForTesting public static final String STORED_PROCEDURE_FILE_NAME = "mutate.js"; private static final String STORED_PROCEDURE_PATH = "cosmosdb_stored_procedure/" + STORED_PROCEDURE_FILE_NAME; private static final StorageInfo STORAGE_INFO = diff --git a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java index 7f0bdb0b8e..284947e7f9 100644 --- a/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java @@ -2187,6 +2187,12 @@ public void dropNamespace_WithNonScalarDBTableLeftForOracle_ShouldThrowIllegalAr RdbEngine.ORACLE); } + @Test + public void dropNamespace_WithNonScalarDBTableLeftForSqlite_ShouldThrowIllegalArgumentException() + throws Exception { + // Do nothing. SQLite does not have a concept of namespaces. + } + @Test public void dropNamespace_WithNonScalarDBTableLeftForDb2_ShouldThrowIllegalArgumentException() throws Exception { @@ -2203,19 +2209,14 @@ private void dropNamespace_WithNonScalarDBTableLeftForX_ShouldThrowIllegalArgume Statement dropNamespaceStatementMock = mock(Statement.class); PreparedStatement deleteFromNamespaceTableMock = mock(PreparedStatement.class); Statement selectNamespaceStatementMock = mock(Statement.class); - if (rdbEngine != RdbEngine.SQLITE) { - 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(anyString())) - .thenReturn(getTableNamesPrepStmt, deleteFromNamespaceTableMock); - } else { - when(connection.createStatement()).thenReturn(selectNamespaceStatementMock); - when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTableMock); - } + 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(anyString())) + .thenReturn(getTableNamesPrepStmt, deleteFromNamespaceTableMock); when(dataSource.getConnection()).thenReturn(connection); // Namespaces table does not contain other namespaces ResultSet resultSet = mock(ResultSet.class);