Skip to content

Commit 169e4b6

Browse files
committed
Fix to avoid deleting non ScalarDB tables when dropping namespaces
1 parent 2eb60eb commit 169e4b6

File tree

10 files changed

+84
-4
lines changed

10 files changed

+84
-4
lines changed

core/src/main/java/com/scalar/db/storage/jdbc/JdbcAdmin.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,11 @@ private void deleteMetadataSchema(Connection connection) throws SQLException {
481481
@Override
482482
public void dropNamespace(String namespace) throws ExecutionException {
483483
try (Connection connection = dataSource.getConnection()) {
484+
Set<String> remainingTables = getNamespaceTableNamesInternal(connection, namespace);
485+
if (!remainingTables.isEmpty()) {
486+
throw new IllegalStateException(
487+
CoreError.NAMESPACE_NOT_EMPTY.buildMessage(namespace, remainingTables));
488+
}
484489
execute(connection, rdbEngine.dropNamespaceSql(namespace));
485490
deleteFromNamespacesTable(connection, namespace);
486491
deleteNamespacesTableAndMetadataSchemaIfEmpty(connection);
@@ -703,6 +708,20 @@ public Set<String> getNamespaceTableNames(String namespace) throws ExecutionExce
703708
}
704709
}
705710

711+
private Set<String> getNamespaceTableNamesInternal(Connection connection, String namespace)
712+
throws SQLException {
713+
String sql = rdbEngine.getTableNamesInNamespaceSql(namespace);
714+
Set<String> tableNames = new HashSet<>();
715+
try (PreparedStatement statement = connection.prepareStatement(sql)) {
716+
try (ResultSet resultSet = statement.executeQuery()) {
717+
while (resultSet.next()) {
718+
tableNames.add(resultSet.getString(1));
719+
}
720+
}
721+
}
722+
return tableNames;
723+
}
724+
706725
@Override
707726
public boolean namespaceExists(String namespace) throws ExecutionException {
708727
String selectQuery =

core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineDb2.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,4 +583,10 @@ public void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
583583
.buildMessage(orderingOnBlobColumn.get()));
584584
}
585585
}
586+
587+
@Override
588+
public String getTableNamesInNamespaceSql(String namespace) {
589+
return String.format(
590+
"SELECT TABNAME FROM SYSCAT.TABLES WHERE TABSCHEMA = '%s' AND TYPE = 'T'", namespace);
591+
}
586592
}

core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineMysql.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,4 +504,11 @@ public void setConnectionToReadOnly(Connection connection, boolean readOnly) thr
504504
// Observed performance degradation when using read-only connections in MySQL. So we do not
505505
// set the read-only mode for MySQL connections.
506506
}
507+
508+
@Override
509+
public String getTableNamesInNamespaceSql(String namespace) {
510+
return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '"
511+
+ namespace
512+
+ "'";
513+
}
507514
}

core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineOracle.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,4 +451,9 @@ public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) {
451451
from.toString(), to.toString()));
452452
}
453453
}
454+
455+
@Override
456+
public String getTableNamesInNamespaceSql(String namespace) {
457+
return String.format("SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = '%s'", namespace);
458+
}
454459
}

core/src/main/java/com/scalar/db/storage/jdbc/RdbEnginePostgresql.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,4 +395,11 @@ public String tryAddIfNotExistsToCreateIndexSql(String createIndexSql) {
395395
getTimeTypeStrategy() {
396396
return timeTypeEngine;
397397
}
398+
399+
@Override
400+
public String getTableNamesInNamespaceSql(String namespace) {
401+
return "SELECT table_name FROM information_schema.tables WHERE table_schema = '"
402+
+ namespace
403+
+ "'";
404+
}
398405
}

core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlServer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,4 +431,11 @@ public Map<String, String> getConnectionProperties(JdbcConfig config) {
431431
getTimeTypeStrategy() {
432432
return timeTypeEngine;
433433
}
434+
435+
@Override
436+
public String getTableNamesInNamespaceSql(String namespace) {
437+
return "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '"
438+
+ namespace
439+
+ "'";
440+
}
434441
}

core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineSqlite.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,4 +367,12 @@ public void throwIfAlterColumnTypeNotSupported(DataType from, DataType to) {
367367
public void setConnectionToReadOnly(Connection connection, boolean readOnly) {
368368
// Do nothing. SQLite does not support read-only mode.
369369
}
370+
371+
@Override
372+
public String getTableNamesInNamespaceSql(String namespace) {
373+
return "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '"
374+
+ namespace
375+
+ NAMESPACE_SEPARATOR
376+
+ "%'";
377+
}
370378
}

core/src/main/java/com/scalar/db/storage/jdbc/RdbEngineStrategy.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,4 +305,6 @@ default void setConnectionToReadOnly(Connection connection, boolean readOnly)
305305
*/
306306
default void throwIfCrossPartitionScanOrderingOnBlobColumnNotSupported(
307307
ScanAll scanAll, TableMetadata metadata) {}
308+
309+
String getTableNamesInNamespaceSql(String namespace);
308310
}

core/src/test/java/com/scalar/db/storage/jdbc/JdbcAdminTest.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,6 +1957,7 @@ public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception {
19571957
JdbcAdmin admin = createJdbcAdminFor(RdbEngine.SQLITE);
19581958

19591959
Connection connection = mock(Connection.class);
1960+
PreparedStatement getTableNamesPrepStmt = mock(PreparedStatement.class);
19601961
PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class);
19611962
Statement selectAllFromNamespaceTablePrepStmt = mock(Statement.class);
19621963
Statement selectAllFromMetadataTablePrepStmt = mock(Statement.class);
@@ -1967,7 +1968,13 @@ public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception {
19671968
selectAllFromNamespaceTablePrepStmt,
19681969
selectAllFromMetadataTablePrepStmt,
19691970
dropNamespaceTableStmt);
1970-
when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTablePrepStmt);
1971+
// Mock for getNamespaceTableNamesInternal() check - returns empty ResultSet (namespace is
1972+
// empty)
1973+
ResultSet emptyResultSet = mock(ResultSet.class);
1974+
when(emptyResultSet.next()).thenReturn(false);
1975+
when(getTableNamesPrepStmt.executeQuery()).thenReturn(emptyResultSet);
1976+
when(connection.prepareStatement(anyString()))
1977+
.thenReturn(getTableNamesPrepStmt, deleteFromNamespaceTablePrepStmt);
19711978
when(dataSource.getConnection()).thenReturn(connection);
19721979
// Only the metadata schema is left
19731980
ResultSet resultSet1 =
@@ -2005,6 +2012,7 @@ private void dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNa
20052012

20062013
Connection connection = mock(Connection.class);
20072014
Statement dropNamespaceStmt = mock(Statement.class);
2015+
PreparedStatement isNamespaceEmptyStatementMock = mock(PreparedStatement.class);
20082016
PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class);
20092017
Statement selectAllFromNamespaceTablePrepStmt = mock(Statement.class);
20102018
Statement selectAllFromMetadataTablePrepStmt = mock(Statement.class);
@@ -2018,7 +2026,12 @@ private void dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNa
20182026
selectAllFromMetadataTablePrepStmt,
20192027
dropNamespaceTableStmt,
20202028
dropMetadataSchemaStmt);
2021-
when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTablePrepStmt);
2029+
// Mock for isNamespaceEmpty() check - returns empty ResultSet (namespace is empty)
2030+
ResultSet emptyResultSet = mock(ResultSet.class);
2031+
when(emptyResultSet.next()).thenReturn(false);
2032+
when(isNamespaceEmptyStatementMock.executeQuery()).thenReturn(emptyResultSet);
2033+
when(connection.prepareStatement(anyString()))
2034+
.thenReturn(isNamespaceEmptyStatementMock, deleteFromNamespaceTablePrepStmt);
20222035
when(dataSource.getConnection()).thenReturn(connection);
20232036
// Only the metadata schema is left
20242037
ResultSet resultSet =
@@ -2116,6 +2129,7 @@ private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace(
21162129

21172130
Connection connection = mock(Connection.class);
21182131
Statement dropNamespaceStatementMock = mock(Statement.class);
2132+
PreparedStatement isNamespaceEmptyStatementMock = mock(PreparedStatement.class);
21192133
PreparedStatement deleteFromNamespaceTableMock = mock(PreparedStatement.class);
21202134
Statement selectNamespaceStatementMock = mock(Statement.class);
21212135
if (rdbEngine != RdbEngine.SQLITE) {
@@ -2124,7 +2138,12 @@ private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace(
21242138
} else {
21252139
when(connection.createStatement()).thenReturn(selectNamespaceStatementMock);
21262140
}
2127-
when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTableMock);
2141+
// Mock for isNamespaceEmpty() check - returns empty ResultSet (namespace is empty)
2142+
ResultSet emptyResultSet = mock(ResultSet.class);
2143+
when(emptyResultSet.next()).thenReturn(false);
2144+
when(isNamespaceEmptyStatementMock.executeQuery()).thenReturn(emptyResultSet);
2145+
when(connection.prepareStatement(anyString()))
2146+
.thenReturn(isNamespaceEmptyStatementMock, deleteFromNamespaceTableMock);
21282147
when(dataSource.getConnection()).thenReturn(connection);
21292148
// Namespaces table contains other namespaces
21302149
ResultSet resultSet =

integration-test/src/main/java/com/scalar/db/api/DistributedStorageAdminImportTableIntegrationTestBase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ public void dropNamespace_ShouldNotDropNonScalarDBTables() throws Exception {
249249

250250
// Act
251251
assertThatCode(() -> admin.dropNamespace(getNamespace()))
252-
.isInstanceOf(ExecutionException.class);
252+
.isInstanceOf(IllegalStateException.class);
253253

254254
// Assert
255255
assertThat(admin.namespaceExists(getNamespace())).isTrue();

0 commit comments

Comments
 (0)