From c83c95d6cf027e55bdb75d35e5ccccdb6587ac08 Mon Sep 17 00:00:00 2001 From: Toshihiro Suzuki Date: Thu, 7 Dec 2023 04:16:19 +0900 Subject: [PATCH] Revisit namespaces table deletion logic --- .../db/storage/cassandra/CassandraAdmin.java | 37 +++- .../scalar/db/storage/cosmos/CosmosAdmin.java | 32 ++- .../scalar/db/storage/dynamo/DynamoAdmin.java | 70 ++++-- .../com/scalar/db/storage/jdbc/JdbcAdmin.java | 38 +++- .../storage/cassandra/CassandraAdminTest.java | 79 ++++--- .../db/storage/cosmos/CosmosAdminTest.java | 88 +++++--- .../storage/dynamo/DynamoAdminTestBase.java | 100 ++++----- .../scalar/db/storage/jdbc/JdbcAdminTest.java | 208 ++++++++---------- 8 files changed, 391 insertions(+), 261 deletions(-) diff --git a/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java b/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java index 6d35c553e2..8eb7687f14 100644 --- a/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/cassandra/CassandraAdmin.java @@ -25,6 +25,7 @@ import com.scalar.db.config.DatabaseConfig; import com.scalar.db.exception.storage.ExecutionException; import com.scalar.db.io.DataType; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; @@ -64,6 +65,7 @@ public void createTable( String namespace, String table, TableMetadata metadata, Map options) throws ExecutionException { try { + createNamespacesTableIfNotExists(); createTableInternal(namespace, table, metadata, false, options); createSecondaryIndexes(namespace, table, metadata.getSecondaryIndexNames(), false); } catch (RuntimeException e) { @@ -128,11 +130,13 @@ private void upsertIntoNamespacesTable(String keyspace) { @Override public void dropTable(String namespace, String table) throws ExecutionException { - String dropTableQuery = - SchemaBuilder.dropTable(quoteIfNecessary(namespace), quoteIfNecessary(table)) - .getQueryString(); try { + String dropTableQuery = + SchemaBuilder.dropTable(quoteIfNecessary(namespace), quoteIfNecessary(table)) + .getQueryString(); clusterManager.getSession().execute(dropTableQuery); + + dropMetadataKeyspaceIfEmpty(); } catch (RuntimeException e) { throw new ExecutionException( String.format("Dropping the %s table failed", getFullTableName(namespace, table)), e); @@ -144,7 +148,7 @@ public void dropNamespace(String namespace) throws ExecutionException { try { dropKeyspace(namespace); deleteFromNamespacesTable(namespace); - dropNamespacesTableIfEmpty(); + dropMetadataKeyspaceIfEmpty(); } catch (RuntimeException e) { throw new ExecutionException(String.format("Dropping the %s keyspace failed", namespace), e); } @@ -442,17 +446,32 @@ private void createNamespacesTableIfNotExists() { upsertIntoNamespacesTable(metadataKeyspace); } - private void dropNamespacesTableIfEmpty() { + private void dropMetadataKeyspaceIfEmpty() { String selectQuery = QueryBuilder.select(NAMESPACES_NAME_COL) .from(quoteIfNecessary(metadataKeyspace), quoteIfNecessary(NAMESPACES_TABLE)) .limit(2) .getQueryString(); - List rows = clusterManager.getSession().execute(selectQuery).all(); - if (rows.isEmpty() - || (rows.size() == 1 - && rows.get(0).getString(NAMESPACES_NAME_COL).equals(metadataKeyspace))) { + + boolean onlyMetadataNamespaceLeft = + rows.size() == 1 && rows.get(0).getString(NAMESPACES_NAME_COL).equals(metadataKeyspace); + if (!onlyMetadataNamespaceLeft) { + return; + } + + // Drop the metadata keyspace if there is only the namespaces table left + KeyspaceMetadata keyspace = + clusterManager + .getSession() + .getCluster() + .getMetadata() + .getKeyspace(quoteIfNecessary(metadataKeyspace)); + if (keyspace == null) { + return; + } + Collection tables = keyspace.getTables(); + if (tables.size() == 1 && tables.iterator().next().getName().equals(NAMESPACES_TABLE)) { dropKeyspace(metadataKeyspace); } } diff --git a/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java b/core/src/main/java/com/scalar/db/storage/cosmos/CosmosAdmin.java index aa3ee56373..977548afc8 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 @@ -38,6 +38,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -90,6 +92,7 @@ public void createTable( String namespace, String table, TableMetadata metadata, Map options) throws ExecutionException { try { + createMetadataDatabaseAndNamespaceContainerIfNotExists(); createTableInternal(namespace, table, metadata, false); } catch (IllegalArgumentException e) { throw e; @@ -303,6 +306,7 @@ public void dropTable(String namespace, String table) throws ExecutionException try { database.getContainer(table).delete(); deleteTableMetadata(namespace, table); + deleteMetadataDatabaseIfEmpty(); } catch (RuntimeException e) { throw new ExecutionException( String.format("Deleting the %s container failed", getFullTableName(namespace, table)), e); @@ -341,13 +345,13 @@ public void dropNamespace(String namespace) throws ExecutionException { client.getDatabase(namespace).delete(); getNamespacesContainer() .deleteItem(new CosmosNamespace(namespace), new CosmosItemRequestOptions()); - dropNamespacesTableIfEmpty(); + deleteMetadataDatabaseIfEmpty(); } catch (RuntimeException e) { throw new ExecutionException(String.format("Deleting the %s database failed", namespace), e); } } - private void dropNamespacesTableIfEmpty() { + private void deleteMetadataDatabaseIfEmpty() { Set namespaces = getNamespacesContainer() .queryItems( @@ -357,8 +361,28 @@ private void dropNamespacesTableIfEmpty() { .stream() .map(CosmosNamespace::getId) .collect(Collectors.toSet()); - if (namespaces.isEmpty() || (namespaces.size() == 1 && namespaces.contains(metadataDatabase))) { - client.getDatabase(metadataDatabase).delete(); + + boolean onlyMetadataNamespaceLeft = + namespaces.size() == 1 && namespaces.contains(metadataDatabase); + if (!onlyMetadataNamespaceLeft) { + return; + } + + // Delete the metadata database if there is only the namespaces container left + CosmosDatabase database = client.getDatabase(metadataDatabase); + Iterator iterator = database.readAllContainers().iterator(); + + Set containers = new HashSet<>(); + int count = 0; + while (iterator.hasNext()) { + containers.add(iterator.next().getId()); + // Only need to fetch the first two containers + if (count++ == 2) { + break; + } + } + if (containers.size() == 1 && containers.contains(NAMESPACES_CONTAINER)) { + database.delete(); } } diff --git a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java index 28f0ddafa3..b7d9b3d733 100644 --- a/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java +++ b/core/src/main/java/com/scalar/db/storage/dynamo/DynamoAdmin.java @@ -246,6 +246,8 @@ public void createTable( Map options) throws ExecutionException { try { + boolean noBackup = Boolean.parseBoolean(options.getOrDefault(NO_BACKUP, DEFAULT_NO_BACKUP)); + createNamespacesTableIfNotExists(noBackup); createTableInternal(nonPrefixedNamespace, table, metadata, false, options); } catch (ExecutionException e) { throw new ExecutionException( @@ -685,6 +687,7 @@ public void dropTable(String nonPrefixedNamespace, String table) throws Executio } waitForTableDeletion(namespace, table); deleteTableMetadata(namespace, table); + dropNamespacesTableIfEmpty(); } private void disableAutoScaling(Namespace namespace, String table) throws ExecutionException { @@ -795,26 +798,58 @@ public void dropNamespace(String nonPrefixedNamespace) throws ExecutionException Namespace namespace = Namespace.of(namespacePrefix, nonPrefixedNamespace); try { deleteFromNamespacesTable(namespace); - dropNamespacesTableIfEmpty(); } catch (Exception e) { throw new ExecutionException("Dropping the " + namespace + " namespace failed", e); } + dropNamespacesTableIfEmpty(); } private void dropNamespacesTableIfEmpty() throws ExecutionException { String namespaceTableFullName = ScalarDbUtils.getFullTableName(metadataNamespace, NAMESPACES_TABLE); - ScanResponse scanResponse = - client.scan(ScanRequest.builder().tableName(namespaceTableFullName).limit(2).build()); + + ScanResponse scanResponse; + try { + scanResponse = + client.scan(ScanRequest.builder().tableName(namespaceTableFullName).limit(2).build()); + } catch (Exception e) { + throw new ExecutionException("Scanning the namespaces table failed", e); + } Set namespaceNames = new HashSet<>(); for (Map namespace : scanResponse.items()) { - String prefixedNamespaceName = namespace.get(NAMESPACES_ATTR_NAME).s(); - namespaceNames.add(prefixedNamespaceName); + String namespaceName = namespace.get(NAMESPACES_ATTR_NAME).s(); + namespaceNames.add(namespaceName); + } + + boolean onlyMetadataNamespaceLeft = + namespaceNames.size() == 1 && namespaceNames.contains(metadataNamespace); + if (!onlyMetadataNamespaceLeft) { + return; + } + + // Delete the namespaces table if there is only the namespaces table left + Set tables = new HashSet<>(); + try { + String lastEvaluatedTableName = null; + do { + ListTablesRequest listTablesRequest = + ListTablesRequest.builder().exclusiveStartTableName(lastEvaluatedTableName).build(); + ListTablesResponse listTablesResponse = client.listTables(listTablesRequest); + lastEvaluatedTableName = listTablesResponse.lastEvaluatedTableName(); + List tableNames = listTablesResponse.tableNames(); + String prefix = metadataNamespace + "."; + for (String tableName : tableNames) { + if (tableName.startsWith(prefix)) { + tables.add(tableName.substring(prefix.length())); + } + } + } while (lastEvaluatedTableName != null); + } catch (Exception e) { + throw new ExecutionException("Listing tables failed", e); } - if (namespaceNames.isEmpty() - || (namespaceNames.size() == 1 && namespaceNames.contains(metadataNamespace))) { + if (tables.size() == 1 && tables.contains(NAMESPACES_TABLE)) { client.deleteTable(DeleteTableRequest.builder().tableName(namespaceTableFullName).build()); waitForTableDeletion(Namespace.of(metadataNamespace), NAMESPACES_TABLE); } @@ -1409,8 +1444,8 @@ private Set getNamespacesOfExistingTables() throws ExecutionException } private void createNamespacesTableIfNotExists(boolean noBackup) throws ExecutionException { - try { - if (!namespacesTableExists()) { + if (!namespacesTableExists()) { + try { List columnsToAttributeDefinitions = new ArrayList<>(); columnsToAttributeDefinitions.add( AttributeDefinition.builder() @@ -1432,17 +1467,18 @@ private void createNamespacesTableIfNotExists(boolean noBackup) throws Execution .build()) .tableName(ScalarDbUtils.getFullTableName(metadataNamespace, NAMESPACES_TABLE)) .build()); - waitForTableCreation(Namespace.of(metadataNamespace), NAMESPACES_TABLE); + } catch (Exception e) { + throw new ExecutionException("Creating the namespaces table failed", e); + } - // Insert the system namespace to the namespaces table - upsertIntoNamespacesTable(Namespace.of(metadataNamespace)); + waitForTableCreation(Namespace.of(metadataNamespace), NAMESPACES_TABLE); + + if (!noBackup) { + enableContinuousBackup(Namespace.of(metadataNamespace), NAMESPACES_TABLE); } - } catch (Exception e) { - throw new ExecutionException("Creating the namespaces table failed", e); - } - if (!noBackup) { - enableContinuousBackup(Namespace.of(metadataNamespace), NAMESPACES_TABLE); + // Insert the system namespace to the namespaces table + upsertIntoNamespacesTable(Namespace.of(metadataNamespace)); } } 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 b9ee3459e0..de5dec5acc 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 @@ -97,6 +97,7 @@ public void createTable( String namespace, String table, TableMetadata metadata, Map options) throws ExecutionException { try (Connection connection = dataSource.getConnection()) { + createNamespacesTableIfNotExists(connection); createTableInternal(connection, namespace, table, metadata, false); addTableMetadata(connection, namespace, table, metadata, true, false); } catch (SQLException e) { @@ -340,6 +341,7 @@ public void dropTable(String namespace, String table) throws ExecutionException try (Connection connection = dataSource.getConnection()) { dropTableInternal(connection, namespace, table); deleteTableMetadata(connection, namespace, table); + deleteNamespacesTableAndMetadataSchemaIfEmpty(connection); } catch (SQLException e) { throw new ExecutionException( "Dropping the " + getFullTableName(namespace, table) + " table failed", e); @@ -409,7 +411,7 @@ public void dropNamespace(String namespace) throws ExecutionException { try (Connection connection = dataSource.getConnection()) { execute(connection, rdbEngine.dropNamespaceSql(namespace)); deleteFromNamespacesTable(connection, namespace); - deleteNamespacesTableIfEmpty(connection); + deleteNamespacesTableAndMetadataSchemaIfEmpty(connection); } catch (SQLException e) { rdbEngine.dropNamespaceTranslateSQLException(e, namespace); } @@ -1008,20 +1010,22 @@ private void deleteFromNamespacesTable(Connection connection, String namespaceNa } } - private void deleteNamespacesTableIfEmpty(Connection connection) throws SQLException { - if (isNamespacesTableEmpty(connection)) { + private void deleteNamespacesTableAndMetadataSchemaIfEmpty(Connection connection) + throws SQLException { + if (areNamespacesTableAndMetadataSchemaEmpty(connection)) { deleteTable(connection, encloseFullTableName(metadataSchema, NAMESPACES_TABLE)); deleteMetadataSchema(connection); } } - private boolean isNamespacesTableEmpty(Connection connection) throws SQLException { + private boolean areNamespacesTableAndMetadataSchemaEmpty(Connection connection) + throws SQLException { String selectAllTables = "SELECT * FROM " + encloseFullTableName(metadataSchema, NAMESPACES_TABLE); Set namespaces = new HashSet<>(); - try (PreparedStatement preparedStatement = connection.prepareStatement(selectAllTables); - ResultSet results = preparedStatement.executeQuery()) { + try (Statement statement = connection.createStatement(); + ResultSet results = statement.executeQuery(selectAllTables)) { int count = 0; while (results.next()) { namespaces.add(results.getString(NAMESPACE_COL_NAMESPACE_NAME)); @@ -1032,7 +1036,27 @@ private boolean isNamespacesTableEmpty(Connection connection) throws SQLExceptio } } - return namespaces.isEmpty() || (namespaces.size() == 1 && namespaces.contains(metadataSchema)); + boolean onlyMetadataNamespaceLeft = + namespaces.size() == 1 && namespaces.contains(metadataSchema); + if (!onlyMetadataNamespaceLeft) { + return false; + } + + // Check if the metadata table exists. If it does not, the metadata schema is empty. + String sql = + rdbEngine.tableExistsInternalTableCheckSql( + encloseFullTableName(metadataSchema, METADATA_TABLE)); + try { + execute(connection, sql); + return false; + } catch (SQLException e) { + // An exception will be thrown if the table does not exist when executing the select + // query + if (rdbEngine.isUndefinedTableError(e)) { + return true; + } + throw e; + } } @Override diff --git a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java index 2b21f57b6d..bfb0da5d87 100644 --- a/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java +++ b/core/src/test/java/com/scalar/db/storage/cassandra/CassandraAdminTest.java @@ -252,6 +252,7 @@ private void verifyInsertIntoKeyspacesTableQuery(String keyspace) { .addSecondaryIndex("c2") .addSecondaryIndex("c4") .build(); + // Act cassandraAdmin.createTableInternal(namespace, table, tableMetadata, false, new HashMap<>()); @@ -418,12 +419,21 @@ public void dropTable_WithCorrectParameters_ShouldDropTable() throws ExecutionEx String namespace = "sample_ns"; String table = "sample_table"; + ResultSet selectQueryResult = mock(ResultSet.class); + Row row1 = mock(Row.class); + when(row1.getString("name")).thenReturn(METADATA_KEYSPACE); + Row row2 = mock(Row.class); + when(row2.getString("name")).thenReturn(namespace); + when(selectQueryResult.all()).thenReturn(Arrays.asList(row1, row2)); + when(cassandraSession.execute(anyString())).thenReturn(null, selectQueryResult); + // Act cassandraAdmin.dropTable(namespace, table); // Assert String dropTableStatement = SchemaBuilder.dropTable(namespace, table).getQueryString(); verify(cassandraSession).execute(dropTableStatement); + verifySelectTwoFromKeyspacesTableQuery(); } @Test @@ -432,6 +442,14 @@ public void dropTable_WithReservedKeywords_ShouldDropTable() throws ExecutionExc String namespace = "keyspace"; String table = "table"; + ResultSet selectQueryResult = mock(ResultSet.class); + Row row1 = mock(Row.class); + when(row1.getString("name")).thenReturn(METADATA_KEYSPACE); + Row row2 = mock(Row.class); + when(row2.getString("name")).thenReturn(namespace); + when(selectQueryResult.all()).thenReturn(Arrays.asList(row1, row2)); + when(cassandraSession.execute(anyString())).thenReturn(null, selectQueryResult); + // Act cassandraAdmin.dropTable(namespace, table); @@ -439,27 +457,7 @@ public void dropTable_WithReservedKeywords_ShouldDropTable() throws ExecutionExc String dropTableStatement = SchemaBuilder.dropTable(quote(namespace), quote(table)).getQueryString(); verify(cassandraSession).execute(dropTableStatement); - } - - @Test - public void - dropNamespace_WithCorrectParametersWithNoMoreKeyspacesLeft_ShouldDropKeyspaceAndMetadataKeyspace() - throws ExecutionException { - // Arrange - String namespace = "sample_ns"; - ResultSet selectQueryResult = mock(ResultSet.class); - when(selectQueryResult.all()).thenReturn(Collections.emptyList()); - when(cassandraSession.execute(anyString())).thenReturn(null, null, selectQueryResult, null); - - // Act - cassandraAdmin.dropNamespace(namespace); - - // Assert - String dropKeyspaceStatement = SchemaBuilder.dropKeyspace(namespace).getQueryString(); - verify(cassandraSession).execute(dropKeyspaceStatement); - verifyDeleteFromKeyspacesTableQuery(namespace); - verifySelectOneFromKeyspacesTableQuery(); - verifyDropMetadataKeyspaceQuery(); + verifySelectTwoFromKeyspacesTableQuery(); } @Test @@ -474,6 +472,15 @@ public void dropTable_WithReservedKeywords_ShouldDropTable() throws ExecutionExc when(selectQueryResult.all()).thenReturn(Collections.singletonList(row)); when(cassandraSession.execute(anyString())).thenReturn(null, null, selectQueryResult, null); + com.datastax.driver.core.TableMetadata tableMetadata = + mock(com.datastax.driver.core.TableMetadata.class); + when(tableMetadata.getName()).thenReturn(CassandraAdmin.NAMESPACES_TABLE); + + when(cassandraSession.getCluster()).thenReturn(cluster); + when(cluster.getMetadata()).thenReturn(metadata); + when(metadata.getKeyspace(any())).thenReturn(keyspaceMetadata); + when(keyspaceMetadata.getTables()).thenReturn(Collections.singletonList(tableMetadata)); + // Act cassandraAdmin.dropNamespace(namespace); @@ -481,7 +488,8 @@ public void dropTable_WithReservedKeywords_ShouldDropTable() throws ExecutionExc String dropKeyspaceStatement = SchemaBuilder.dropKeyspace(namespace).getQueryString(); verify(cassandraSession).execute(dropKeyspaceStatement); verifyDeleteFromKeyspacesTableQuery(namespace); - verifySelectOneFromKeyspacesTableQuery(); + verifySelectTwoFromKeyspacesTableQuery(); + verifyGetTablesInMetadataKeyspace(); verifyDropMetadataKeyspaceQuery(); } @@ -501,7 +509,7 @@ public void dropNamespace_WithCorrectParametersWithSomeKeyspacesLeft_ShouldOnlyD String dropKeyspaceStatement = SchemaBuilder.dropKeyspace(namespace).getQueryString(); verify(cassandraSession).execute(dropKeyspaceStatement); verifyDeleteFromKeyspacesTableQuery(namespace); - verifySelectOneFromKeyspacesTableQuery(); + verifySelectTwoFromKeyspacesTableQuery(); } @Test @@ -510,9 +518,20 @@ public void dropNamespace_WithReservedKeywordNamespace_ShouldDropKeyspace() // Arrange String namespace = "keyspace"; ResultSet selectQueryResult = mock(ResultSet.class); - when(selectQueryResult.one()).thenReturn(null); + Row row = mock(Row.class); + when(row.getString("name")).thenReturn(METADATA_KEYSPACE); + when(selectQueryResult.all()).thenReturn(Collections.singletonList(row)); when(cassandraSession.execute(anyString())).thenReturn(null, null, selectQueryResult, null); + com.datastax.driver.core.TableMetadata tableMetadata = + mock(com.datastax.driver.core.TableMetadata.class); + when(tableMetadata.getName()).thenReturn(CassandraAdmin.NAMESPACES_TABLE); + + when(cassandraSession.getCluster()).thenReturn(cluster); + when(cluster.getMetadata()).thenReturn(metadata); + when(metadata.getKeyspace(any())).thenReturn(keyspaceMetadata); + when(keyspaceMetadata.getTables()).thenReturn(Collections.singletonList(tableMetadata)); + // Act cassandraAdmin.dropNamespace(namespace); @@ -520,7 +539,8 @@ public void dropNamespace_WithReservedKeywordNamespace_ShouldDropKeyspace() String dropKeyspaceStatement = SchemaBuilder.dropKeyspace(quote(namespace)).getQueryString(); verify(cassandraSession).execute(dropKeyspaceStatement); verifyDeleteFromKeyspacesTableQuery(namespace); - verifySelectOneFromKeyspacesTableQuery(); + verifySelectTwoFromKeyspacesTableQuery(); + verifyGetTablesInMetadataKeyspace(); verifyDropMetadataKeyspaceQuery(); } @@ -535,7 +555,7 @@ private void verifyDeleteFromKeyspacesTableQuery(String keyspace) { verify(cassandraSession).execute(query); } - private void verifySelectOneFromKeyspacesTableQuery() { + private void verifySelectTwoFromKeyspacesTableQuery() { String query = QueryBuilder.select(CassandraAdmin.NAMESPACES_NAME_COL) .from( @@ -546,6 +566,13 @@ private void verifySelectOneFromKeyspacesTableQuery() { verify(cassandraSession).execute(query); } + private void verifyGetTablesInMetadataKeyspace() { + verify(cassandraSession).getCluster(); + verify(cluster).getMetadata(); + verify(metadata).getKeyspace(METADATA_KEYSPACE); + verify(keyspaceMetadata).getTables(); + } + private void verifyDropMetadataKeyspaceQuery() { String query = SchemaBuilder.dropKeyspace(quoteIfNecessary(METADATA_KEYSPACE)).getQueryString(); verify(cassandraSession).execute(query); 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 8828c8ed17..6f1de51fae 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 @@ -250,12 +250,15 @@ public void createTable_ShouldCreateContainer() throws ExecutionException { CosmosScripts cosmosScripts = Mockito.mock(CosmosScripts.class); when(container.getScripts()).thenReturn(cosmosScripts); - // for metadata table + // for metadata table and namespaces table CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); CosmosContainer metadataContainer = mock(CosmosContainer.class); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); when(client.getDatabase(METADATA_DATABASE)).thenReturn(metadataDatabase); when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); + when(metadataDatabase.getContainer(CosmosAdmin.NAMESPACES_CONTAINER)) + .thenReturn(namespacesContainer); // Act admin.createTable(namespace, table, metadata, Collections.emptyMap()); @@ -345,12 +348,15 @@ public void createTable_WithoutClusteringKeys_ShouldCreateContainerWithComposite CosmosScripts cosmosScripts = Mockito.mock(CosmosScripts.class); when(container.getScripts()).thenReturn(cosmosScripts); - // for metadata table + // for metadata table and namespaces table CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); CosmosContainer metadataContainer = mock(CosmosContainer.class); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); when(client.getDatabase(METADATA_DATABASE)).thenReturn(metadataDatabase); when(metadataDatabase.getContainer(CosmosAdmin.TABLE_METADATA_CONTAINER)) .thenReturn(metadataContainer); + when(metadataDatabase.getContainer(CosmosAdmin.NAMESPACES_CONTAINER)) + .thenReturn(namespacesContainer); // Act admin.createTable(namespace, table, metadata, Collections.emptyMap()); @@ -417,6 +423,13 @@ public void createTable_WithBlobClusteringKey_ShouldThrowIllegalArgumentExceptio .addColumn("c3", DataType.BOOLEAN) .build(); + // for namespaces table + CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(client.getDatabase(METADATA_DATABASE)).thenReturn(metadataDatabase); + when(metadataDatabase.getContainer(CosmosAdmin.NAMESPACES_CONTAINER)) + .thenReturn(namespacesContainer); + // Act Assert assertThatThrownBy(() -> admin.createTable(namespace, table, metadata)) .isInstanceOf(IllegalArgumentException.class); @@ -444,6 +457,18 @@ public void dropTable_WithNoMetadataLeft_ShouldDropContainerAndDeleteTableMetada .thenReturn(queryResults); when(queryResults.stream()).thenReturn(Stream.empty()); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(metadataDatabase.getContainer(CosmosAdmin.NAMESPACES_CONTAINER)) + .thenReturn(namespacesContainer); + + @SuppressWarnings("unchecked") + CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); + when(namespacesContainer.queryItems(anyString(), any(), any())) + .thenReturn(pagedIterable); + when(pagedIterable.stream()) + .thenReturn( + Stream.of(new CosmosNamespace(METADATA_DATABASE), new CosmosNamespace(namespace))); + // Act admin.dropTable(namespace, table); @@ -458,6 +483,7 @@ public void dropTable_WithNoMetadataLeft_ShouldDropContainerAndDeleteTableMetada .deleteItem( eq(fullTable), eq(new PartitionKey(fullTable)), refEq(new CosmosItemRequestOptions())); verify(metadataContainer).delete(); + verify(metadataDatabase, never()).delete(); } @Test @@ -482,6 +508,18 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( .thenReturn(queryResults); when(queryResults.stream()).thenReturn(Stream.of(new CosmosTableMetadata())); + CosmosContainer namespacesContainer = mock(CosmosContainer.class); + when(metadataDatabase.getContainer(CosmosAdmin.NAMESPACES_CONTAINER)) + .thenReturn(namespacesContainer); + + @SuppressWarnings("unchecked") + CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); + when(namespacesContainer.queryItems(anyString(), any(), any())) + .thenReturn(pagedIterable); + when(pagedIterable.stream()) + .thenReturn( + Stream.of(new CosmosNamespace(METADATA_DATABASE), new CosmosNamespace(namespace))); + // Act admin.dropTable(namespace, table); @@ -499,35 +537,6 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( verify(metadataDatabase, never()).delete(); } - @Test - public void - dropNamespace_WithExistingDatabaseAndNoMoreNamespaceLeft_ShouldDropDatabaseAndMetadataNamespace() - throws ExecutionException { - // Arrange - String namespace = "ns"; - CosmosDatabase metadataDatabase = mock(CosmosDatabase.class); - when(client.getDatabase(any())).thenReturn(database, metadataDatabase); - CosmosContainer namespacesContainer = mock(CosmosContainer.class); - when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); - - @SuppressWarnings("unchecked") - CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); - when(namespacesContainer.queryItems(anyString(), any(), any())).thenReturn(pagedIterable); - when(pagedIterable.stream()).thenReturn(Stream.empty()); - - // Act - admin.dropNamespace(namespace); - - // Assert - verify(client).getDatabase(namespace); - verify(database).delete(); - verify(client, times(3)).getDatabase(METADATA_DATABASE); - verify(metadataDatabase, times(2)).getContainer(CosmosAdmin.NAMESPACES_CONTAINER); - verify(namespacesContainer) - .deleteItem(eq(new CosmosNamespace(namespace)), refEq(new CosmosItemRequestOptions())); - verify(metadataDatabase).delete(); - } - @Test public void dropNamespace_WithExistingDatabaseAndOnlyMetadataNamespaceLeft_ShouldDropDatabaseAndMetadataNamespace() @@ -540,10 +549,23 @@ public void dropTable_WithMetadataLeft_ShouldDropContainerAndOnlyDeleteMetadata( when(metadataDatabase.getContainer(anyString())).thenReturn(namespacesContainer); @SuppressWarnings("unchecked") - CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); - when(namespacesContainer.queryItems(anyString(), any(), any())).thenReturn(pagedIterable); + CosmosPagedIterable pagedIterable = mock(CosmosPagedIterable.class); + when(namespacesContainer.queryItems(anyString(), any(), any())) + .thenReturn(pagedIterable); when(pagedIterable.stream()).thenReturn(Stream.of(new CosmosNamespace(METADATA_DATABASE))); + @SuppressWarnings("unchecked") + CosmosPagedIterable containerPagedIterable = + mock(CosmosPagedIterable.class); + @SuppressWarnings("unchecked") + Iterator iterator = mock(Iterator.class); + CosmosContainerProperties containerProperties = mock(CosmosContainerProperties.class); + when(containerProperties.getId()).thenReturn(CosmosAdmin.NAMESPACES_CONTAINER); + when(iterator.hasNext()).thenReturn(true, false); + when(iterator.next()).thenReturn(containerProperties); + when(containerPagedIterable.iterator()).thenReturn(iterator); + when(metadataDatabase.readAllContainers()).thenReturn(containerPagedIterable); + // Act admin.dropNamespace(namespace); diff --git a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java index 88a7ff5fed..86f78558a1 100644 --- a/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java +++ b/core/src/test/java/com/scalar/db/storage/dynamo/DynamoAdminTestBase.java @@ -256,47 +256,10 @@ public void dropNamespace_WithOtherNamespacesExisting_ShouldDropNamespace() .scan(ScanRequest.builder().tableName(getFullNamespaceTableName()).limit(2).build()); } - @Test - public void dropNamespace_WithNoNamespacesLeft_ShouldDropNamespaceAndNamespacesTable() - throws ExecutionException { - // Arrange - ListTablesResponse listTablesResponse = mock(ListTablesResponse.class); - when(client.listTables(any(ListTablesRequest.class))).thenReturn(listTablesResponse); - when(listTablesResponse.lastEvaluatedTableName()).thenReturn(null); - when(listTablesResponse.tableNames()).thenReturn(Collections.emptyList()); - ScanResponse scanResponse = mock(ScanResponse.class); - when(scanResponse.items()).thenReturn(Collections.emptyList()); - when(client.scan(any(ScanRequest.class))).thenReturn(scanResponse); - - // Act - admin.dropNamespace(NAMESPACE); - - // Assert - verify(client) - .deleteItem( - DeleteItemRequest.builder() - .tableName(getFullNamespaceTableName()) - .key( - ImmutableMap.of( - DynamoAdmin.NAMESPACES_ATTR_NAME, - AttributeValue.builder().s(getPrefixedNamespace()).build())) - .build()); - - verify(client) - .scan(ScanRequest.builder().tableName(getFullNamespaceTableName()).limit(2).build()); - verify(client) - .deleteTable(DeleteTableRequest.builder().tableName(getFullNamespaceTableName()).build()); - } - @Test public void dropNamespace_WithOnlyMetadataNamespacesLeft_ShouldDropNamespaceAndNamespacesTable() throws ExecutionException { // Arrange - ListTablesResponse listTablesResponse = mock(ListTablesResponse.class); - when(client.listTables(any(ListTablesRequest.class))).thenReturn(listTablesResponse); - when(listTablesResponse.lastEvaluatedTableName()).thenReturn(null); - when(listTablesResponse.tableNames()).thenReturn(Collections.emptyList()); - ScanResponse scanResponse = mock(ScanResponse.class); when(scanResponse.items()) .thenReturn( @@ -306,6 +269,15 @@ public void dropNamespace_WithOnlyMetadataNamespacesLeft_ShouldDropNamespaceAndN AttributeValue.builder().s(getPrefixedMetadataNamespace()).build()))); when(client.scan(any(ScanRequest.class))).thenReturn(scanResponse); + ListTablesResponse listTablesResponse = mock(ListTablesResponse.class); + when(client.listTables(any(ListTablesRequest.class))).thenReturn(listTablesResponse); + when(listTablesResponse.lastEvaluatedTableName()).thenReturn(null); + when(listTablesResponse.tableNames()) + .thenReturn( + Collections.singletonList( + getPrefixedMetadataNamespace() + "." + DynamoAdmin.NAMESPACES_TABLE)) + .thenReturn(Collections.emptyList()); + // Act admin.dropNamespace(NAMESPACE); @@ -361,6 +333,9 @@ public void createTable_WhenMetadataTableNotExist_ShouldCreateTableAndMetadataTa admin.createTable(NAMESPACE, TABLE, metadata); // Assert + verify(client) + .describeTable( + DescribeTableRequest.builder().tableName(getFullNamespaceTableName()).build()); ArgumentCaptor createTableRequestCaptor = ArgumentCaptor.forClass(CreateTableRequest.class); verify(client, times(2)).createTable(createTableRequestCaptor.capture()); @@ -547,6 +522,9 @@ public void createTable_WhenMetadataTableExists_ShouldCreateOnlyTable() admin.createTable(NAMESPACE, TABLE, metadata, options); // Assert + verify(client) + .describeTable( + DescribeTableRequest.builder().tableName(getFullNamespaceTableName()).build()); ArgumentCaptor createTableRequestCaptor = ArgumentCaptor.forClass(CreateTableRequest.class); verify(client).createTable(createTableRequestCaptor.capture()); @@ -759,7 +737,8 @@ public void dropTable_WithNoMetadataLeft_ShouldDropTableAndDeleteMetadata() // for the table metadata table ScanResponse scanResponse = mock(ScanResponse.class); when(scanResponse.count()).thenReturn(1); - when(client.scan(any(ScanRequest.class))).thenReturn(scanResponse); + when(client.scan(ScanRequest.builder().tableName(getFullMetadataTableName()).limit(1).build())) + .thenReturn(scanResponse); ListTablesResponse listTablesResponse = mock(ListTablesResponse.class); when(client.listTables(any(ListTablesRequest.class))).thenReturn(listTablesResponse); @@ -767,6 +746,20 @@ public void dropTable_WithNoMetadataLeft_ShouldDropTableAndDeleteMetadata() List tableList = Collections.emptyList(); when(listTablesResponse.tableNames()).thenReturn(tableList); + // for the namespaces table + ScanResponse scanResponseForNamespacesTable = mock(ScanResponse.class); + when(scanResponseForNamespacesTable.items()) + .thenReturn( + ImmutableList.of( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedMetadataNamespace()).build()), + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build()))); + when(client.scan(ScanRequest.builder().tableName(getFullNamespaceTableName()).limit(2).build())) + .thenReturn(scanResponseForNamespacesTable); + // Act admin.dropTable(NAMESPACE, TABLE); @@ -842,7 +835,8 @@ public void dropTable_WithMetadataLeft_ShouldDropTableAndDropMetadataTable() // for the table metadata table ScanResponse scanResponse = mock(ScanResponse.class); when(scanResponse.count()).thenReturn(0); - when(client.scan(any(ScanRequest.class))).thenReturn(scanResponse); + when(client.scan(ScanRequest.builder().tableName(getFullMetadataTableName()).limit(1).build())) + .thenReturn(scanResponse); ListTablesResponse listTablesResponse = mock(ListTablesResponse.class); when(client.listTables(any(ListTablesRequest.class))).thenReturn(listTablesResponse); @@ -850,6 +844,20 @@ public void dropTable_WithMetadataLeft_ShouldDropTableAndDropMetadataTable() List tableList = Collections.emptyList(); when(listTablesResponse.tableNames()).thenReturn(tableList); + // for the namespaces table + ScanResponse scanResponseForNamespacesTable = mock(ScanResponse.class); + when(scanResponseForNamespacesTable.items()) + .thenReturn( + ImmutableList.of( + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedMetadataNamespace()).build()), + ImmutableMap.of( + DynamoAdmin.NAMESPACES_ATTR_NAME, + AttributeValue.builder().s(getPrefixedNamespace()).build()))); + when(client.scan(ScanRequest.builder().tableName(getFullNamespaceTableName()).limit(2).build())) + .thenReturn(scanResponseForNamespacesTable); + // Act admin.dropTable(NAMESPACE, TABLE); @@ -1393,8 +1401,6 @@ public void addNewColumnToTable_ShouldWorkProperly() throws ExecutionException { public void createNamespace_WithExistingNamespacesTable_ShouldAddNamespace() throws ExecutionException { // Arrange - when(client.describeContinuousBackups(any(DescribeContinuousBackupsRequest.class))) - .thenReturn(backupIsEnabledResponse); // Act admin.createNamespace(NAMESPACE, Collections.emptyMap()); @@ -1403,11 +1409,6 @@ public void createNamespace_WithExistingNamespacesTable_ShouldAddNamespace() verify(client) .describeTable( DescribeTableRequest.builder().tableName(getFullNamespaceTableName()).build()); - verify(client) - .describeContinuousBackups( - DescribeContinuousBackupsRequest.builder() - .tableName(getFullNamespaceTableName()) - .build()); verify(client) .putItem( PutItemRequest.builder() @@ -1667,8 +1668,6 @@ public void unsupportedOperations_ShouldThrowUnsupportedException() { public void repairNamespace_WithExistingNamespacesTable_ShouldAddNamespace() throws ExecutionException { // Arrange - when(client.describeContinuousBackups(any(DescribeContinuousBackupsRequest.class))) - .thenReturn(backupIsEnabledResponse); // Act admin.repairNamespace(NAMESPACE, Collections.emptyMap()); @@ -1677,11 +1676,6 @@ public void repairNamespace_WithExistingNamespacesTable_ShouldAddNamespace() verify(client) .describeTable( DescribeTableRequest.builder().tableName(getFullNamespaceTableName()).build()); - verify(client) - .describeContinuousBackups( - DescribeContinuousBackupsRequest.builder() - .tableName(getFullNamespaceTableName()) - .build()); verify(client) .putItem( PutItemRequest.builder() 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 578f4aefe5..76bf372baf 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 @@ -42,7 +42,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -440,7 +439,10 @@ public void createTableInternal_ForSqlite_withInvalidTableName_ShouldThrowExecut // Act // Assert - assertThatThrownBy(() -> admin.createTable(namespace, table, metadata, new HashMap<>())) + assertThatThrownBy( + () -> + admin.createTableInternal( + mock(Connection.class), namespace, table, metadata, false)) .isInstanceOf(ExecutionException.class); } @@ -1318,7 +1320,8 @@ public void dropTable_forMysqlWithNoMoreMetadataAfterDeletion_shouldDropTableAnd + METADATA_SCHEMA + "`.`metadata` WHERE `full_table_name` = 'my_ns.foo_table'", "SELECT DISTINCT `full_table_name` FROM `" + METADATA_SCHEMA + "`.`metadata`", - "DROP TABLE `" + METADATA_SCHEMA + "`.`metadata`"); + "DROP TABLE `" + METADATA_SCHEMA + "`.`metadata`", + "SELECT * FROM `" + METADATA_SCHEMA + "`.`namespaces`"); } @Test @@ -1332,7 +1335,8 @@ public void dropTable_forMysqlWithNoMoreMetadataAfterDeletion_shouldDropTableAnd + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\"", - "DROP TABLE \"" + METADATA_SCHEMA + "\".\"metadata\""); + "DROP TABLE \"" + METADATA_SCHEMA + "\".\"metadata\"", + "SELECT * FROM \"" + METADATA_SCHEMA + "\".\"namespaces\""); } @Test @@ -1346,7 +1350,8 @@ public void dropTable_forMysqlWithNoMoreMetadataAfterDeletion_shouldDropTableAnd + METADATA_SCHEMA + "].[metadata] WHERE [full_table_name] = 'my_ns.foo_table'", "SELECT DISTINCT [full_table_name] FROM [" + METADATA_SCHEMA + "].[metadata]", - "DROP TABLE [" + METADATA_SCHEMA + "].[metadata]"); + "DROP TABLE [" + METADATA_SCHEMA + "].[metadata]", + "SELECT * FROM [" + METADATA_SCHEMA + "].[namespaces]"); } @Test @@ -1359,7 +1364,8 @@ public void dropTable_forOracleWithNoMoreMetadataAfterDeletion_shouldDropTableAn + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\"", - "DROP TABLE \"" + METADATA_SCHEMA + "\".\"metadata\""); + "DROP TABLE \"" + METADATA_SCHEMA + "\".\"metadata\"", + "SELECT * FROM \"" + METADATA_SCHEMA + "\".\"namespaces\""); } @Test @@ -1372,7 +1378,8 @@ public void dropTable_forSqliteWithNoMoreMetadataAfterDeletion_shouldDropTableAn + METADATA_SCHEMA + "$metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "$metadata\"", - "DROP TABLE \"" + METADATA_SCHEMA + "$metadata\""); + "DROP TABLE \"" + METADATA_SCHEMA + "$metadata\"", + "SELECT * FROM \"" + METADATA_SCHEMA + "$namespaces\""); } private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDeleteMetadata( @@ -1399,6 +1406,13 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel mockedStatements.subList(1, mockedStatements.size()).toArray(new Statement[0])); when(dataSource.getConnection()).thenReturn(connection); + ResultSet resultSetForSelectAllNamespacesTable = + mockResultSet( + new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row(METADATA_SCHEMA), + new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row(namespace)); + when(mockedStatements.get(mockedStatements.size() - 1).executeQuery(anyString())) + .thenReturn(resultSetForSelectAllNamespacesTable); + JdbcAdmin admin = createJdbcAdminFor(rdbEngine); // Act @@ -1424,7 +1438,8 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel "DELETE FROM `" + METADATA_SCHEMA + "`.`metadata` WHERE `full_table_name` = 'my_ns.foo_table'", - "SELECT DISTINCT `full_table_name` FROM `" + METADATA_SCHEMA + "`.`metadata`"); + "SELECT DISTINCT `full_table_name` FROM `" + METADATA_SCHEMA + "`.`metadata`", + "SELECT * FROM `" + METADATA_SCHEMA + "`.`namespaces`"); } @Test @@ -1437,7 +1452,8 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel "DELETE FROM \"" + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\"", + "SELECT * FROM \"" + METADATA_SCHEMA + "\".\"namespaces\""); } @Test @@ -1450,7 +1466,8 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel "DELETE FROM [" + METADATA_SCHEMA + "].[metadata] WHERE [full_table_name] = 'my_ns.foo_table'", - "SELECT DISTINCT [full_table_name] FROM [" + METADATA_SCHEMA + "].[metadata]"); + "SELECT DISTINCT [full_table_name] FROM [" + METADATA_SCHEMA + "].[metadata]", + "SELECT * FROM [" + METADATA_SCHEMA + "].[namespaces]"); } @Test @@ -1463,7 +1480,8 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel "DELETE FROM \"" + METADATA_SCHEMA + "\".\"metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "\".\"metadata\"", + "SELECT * FROM \"" + METADATA_SCHEMA + "\".\"namespaces\""); } @Test @@ -1476,7 +1494,8 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel "DELETE FROM \"" + METADATA_SCHEMA + "$metadata\" WHERE \"full_table_name\" = 'my_ns.foo_table'", - "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "$metadata\""); + "SELECT DISTINCT \"full_table_name\" FROM \"" + METADATA_SCHEMA + "$metadata\"", + "SELECT * FROM \"" + METADATA_SCHEMA + "$namespaces\""); } private void @@ -1504,6 +1523,13 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel mockedStatements.subList(1, mockedStatements.size()).toArray(new Statement[0])); when(dataSource.getConnection()).thenReturn(connection); + ResultSet resultSetForSelectAllNamespacesTable = + mockResultSet( + new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row(METADATA_SCHEMA), + new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row(namespace)); + when(mockedStatements.get(mockedStatements.size() - 1).executeQuery(anyString())) + .thenReturn(resultSetForSelectAllNamespacesTable); + JdbcAdmin admin = createJdbcAdminFor(rdbEngine); // Act @@ -1520,53 +1546,56 @@ private void dropTable_forXWithNoMoreMetadataAfterDeletion_shouldDropTableAndDel } @Test - public void - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForMysql_shouldDropSchemaAndNamespacesTable() - throws Exception { - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( + public void dropNamespace_WithOnlyNamespaceSchemaLeftForMysql_shouldDropSchemaAndNamespacesTable() + throws Exception { + dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( RdbEngine.MYSQL, "DROP SCHEMA `my_ns`", "DELETE FROM `" + METADATA_SCHEMA + "`.`namespaces` WHERE `namespace_name` = ?", "SELECT * FROM `" + METADATA_SCHEMA + "`.`namespaces`", + "SELECT 1 FROM `" + METADATA_SCHEMA + "`.`metadata` LIMIT 1", "DROP TABLE `" + METADATA_SCHEMA + "`.`namespaces`", "DROP SCHEMA `" + METADATA_SCHEMA + "`"); } @Test public void - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForPostgresql_shouldDropSchemaAndNamespacesTable() + dropNamespace_WithOnlyNamespaceSchemaLeftForPostgresql_shouldDropSchemaAndNamespacesTable() throws Exception { - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( + dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( RdbEngine.POSTGRESQL, "DROP SCHEMA \"my_ns\"", "DELETE FROM \"" + METADATA_SCHEMA + "\".\"namespaces\" WHERE \"namespace_name\" = ?", "SELECT * FROM \"" + METADATA_SCHEMA + "\".\"namespaces\"", + "SELECT 1 FROM \"" + METADATA_SCHEMA + "\".\"metadata\" LIMIT 1", "DROP TABLE \"" + METADATA_SCHEMA + "\".\"namespaces\"", "DROP SCHEMA \"" + METADATA_SCHEMA + "\""); } @Test public void - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForSqlServer_shouldDropSchemaAndNamespacesTable() + dropNamespace_WithOnlyNamespaceSchemaLeftForSqlServer_shouldDropSchemaAndNamespacesTable() throws Exception { - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( + dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( RdbEngine.SQL_SERVER, "DROP SCHEMA [my_ns]", "DELETE FROM [" + METADATA_SCHEMA + "].[namespaces] WHERE [namespace_name] = ?", "SELECT * FROM [" + METADATA_SCHEMA + "].[namespaces]", + "SELECT TOP 1 1 FROM [" + METADATA_SCHEMA + "].[metadata]", "DROP TABLE [" + METADATA_SCHEMA + "].[namespaces]", "DROP SCHEMA [" + METADATA_SCHEMA + "]"); } @Test public void - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForOracle_shouldDropSchemaAndNamespacesTable() + dropNamespace_WithOnlyNamespaceSchemaLeftForOracle_shouldDropSchemaAndNamespacesTable() throws Exception { - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( + dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( RdbEngine.ORACLE, "DROP USER \"my_ns\"", "DELETE FROM \"" + METADATA_SCHEMA + "\".\"namespaces\" WHERE \"namespace_name\" = ?", "SELECT * FROM \"" + METADATA_SCHEMA + "\".\"namespaces\"", + "SELECT 1 FROM \"" + METADATA_SCHEMA + "\".\"metadata\" FETCH FIRST 1 ROWS ONLY", "DROP TABLE \"" + METADATA_SCHEMA + "\".\"namespaces\"", "DROP USER \"" + METADATA_SCHEMA + "\""); } @@ -1578,6 +1607,8 @@ public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception { "DELETE FROM \"" + METADATA_SCHEMA + "$namespaces\" WHERE \"namespace_name\" = ?"; String selectAllFromNamespaceTableQuery = "SELECT * FROM \"" + METADATA_SCHEMA + "$namespaces\""; + String selectAllFromMetadataTableQuery = + "SELECT 1 FROM \"" + METADATA_SCHEMA + "$metadata\" LIMIT 1"; String dropNamespaceTableQuery = "DROP TABLE \"" + METADATA_SCHEMA + "$namespaces\""; String namespace = "my_ns"; @@ -1585,94 +1616,36 @@ public void dropNamespace_forSqlite_shouldDropNamespace() throws Exception { Connection connection = mock(Connection.class); PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class); - PreparedStatement selectAllFromNamespaceTablePrepStmt = mock(PreparedStatement.class); - Statement dropNamespaceTableStmt = mock(Statement.class); - when(dataSource.getConnection()).thenReturn(connection); - when(connection.createStatement()).thenReturn(dropNamespaceTableStmt); - when(connection.prepareStatement(anyString())) - .thenReturn(deleteFromNamespaceTablePrepStmt, selectAllFromNamespaceTablePrepStmt); - when(dataSource.getConnection()).thenReturn(connection); - // Namespaces table is empty - ResultSet resultSet = - mockResultSet(new SelectFullTableNameFromMetadataTableResultSetMocker.Row[0]); - when(selectAllFromNamespaceTablePrepStmt.executeQuery()).thenReturn(resultSet); - - // Act - admin.dropNamespace(namespace); - - // Assert - verify(deleteFromNamespaceTablePrepStmt).setString(1, namespace); - verify(connection).prepareStatement(deleteFromNamespaceTableQuery); - verify(connection).prepareStatement(selectAllFromNamespaceTableQuery); - verify(dropNamespaceTableStmt).execute(dropNamespaceTableQuery); - } - - private void - dropNamespace_WithNoSchemaLeftOrOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( - RdbEngine rdbEngine, - String dropNamespaceQuery, - String deleteFromNamespaceTableQuery, - String selectAllFromNamespaceTableQuery, - String dropNamespaceTableQuery, - String dropMetadataSchemaQuery) - throws Exception { - dropNamespace_WithNoSchemaLeftForX_shouldDropSchemaAndNamespacesTable( - rdbEngine, - dropNamespaceQuery, - deleteFromNamespaceTableQuery, - selectAllFromNamespaceTableQuery, - dropNamespaceTableQuery, - dropMetadataSchemaQuery); - dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( - rdbEngine, - dropNamespaceQuery, - deleteFromNamespaceTableQuery, - selectAllFromNamespaceTableQuery, - dropNamespaceTableQuery, - dropMetadataSchemaQuery); - } - - private void dropNamespace_WithNoSchemaLeftForX_shouldDropSchemaAndNamespacesTable( - RdbEngine rdbEngine, - String dropNamespaceQuery, - String deleteFromNamespaceTableQuery, - String selectAllFromNamespaceTableQuery, - String dropNamespaceTableQuery, - String dropMetadataSchemaQuery) - throws Exception { - // Arrange - String namespace = "my_ns"; - JdbcAdmin admin = createJdbcAdminFor(rdbEngine); - - Connection connection = mock(Connection.class); - Statement dropNamespaceStmt = mock(Statement.class); - PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class); - PreparedStatement selectAllFromNamespaceTablePrepStmt = mock(PreparedStatement.class); + Statement selectAllFromNamespaceTablePrepStmt = mock(Statement.class); + Statement selectAllFromMetadataTablePrepStmt = mock(Statement.class); Statement dropNamespaceTableStmt = mock(Statement.class); - Statement dropMetadataSchemaStmt = mock(Statement.class); when(dataSource.getConnection()).thenReturn(connection); when(connection.createStatement()) - .thenReturn(dropNamespaceStmt, dropNamespaceTableStmt, dropMetadataSchemaStmt); - when(connection.prepareStatement(anyString())) - .thenReturn(deleteFromNamespaceTablePrepStmt, selectAllFromNamespaceTablePrepStmt); + .thenReturn( + selectAllFromNamespaceTablePrepStmt, + selectAllFromMetadataTablePrepStmt, + dropNamespaceTableStmt); + when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTablePrepStmt); when(dataSource.getConnection()).thenReturn(connection); - // Namespaces table is empty - ResultSet resultSet = - mockResultSet(new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row[0]); - when(selectAllFromNamespaceTablePrepStmt.executeQuery()).thenReturn(resultSet); + // Only the metadata schema is left + ResultSet resultSet1 = + mockResultSet( + new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row(METADATA_SCHEMA)); + when(selectAllFromNamespaceTablePrepStmt.executeQuery(anyString())).thenReturn(resultSet1); + + SQLException sqlException = mock(SQLException.class); + mockUndefinedTableError(RdbEngine.SQLITE, sqlException); + when(selectAllFromMetadataTablePrepStmt.execute(anyString())).thenThrow(sqlException); // Act admin.dropNamespace(namespace); // Assert - verify(dropNamespaceStmt).execute(dropNamespaceQuery); verify(deleteFromNamespaceTablePrepStmt).setString(1, namespace); verify(connection).prepareStatement(deleteFromNamespaceTableQuery); - verify(deleteFromNamespaceTablePrepStmt).execute(); - verify(connection).prepareStatement(selectAllFromNamespaceTableQuery); - verify(selectAllFromNamespaceTablePrepStmt).executeQuery(); + verify(selectAllFromNamespaceTablePrepStmt).executeQuery(selectAllFromNamespaceTableQuery); + verify(selectAllFromMetadataTablePrepStmt).execute(selectAllFromMetadataTableQuery); verify(dropNamespaceTableStmt).execute(dropNamespaceTableQuery); - verify(dropMetadataSchemaStmt).execute(dropMetadataSchemaQuery); } private void dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNamespacesTable( @@ -1680,6 +1653,7 @@ private void dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNa String dropNamespaceQuery, String deleteFromNamespaceTableQuery, String selectAllFromNamespaceTableQuery, + String selectAllFromMetadataTableQuery, String dropNamespaceTableQuery, String dropMetadataSchemaQuery) throws Exception { @@ -1690,20 +1664,29 @@ private void dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNa Connection connection = mock(Connection.class); Statement dropNamespaceStmt = mock(Statement.class); PreparedStatement deleteFromNamespaceTablePrepStmt = mock(PreparedStatement.class); - PreparedStatement selectAllFromNamespaceTablePrepStmt = mock(PreparedStatement.class); + Statement selectAllFromNamespaceTablePrepStmt = mock(Statement.class); + Statement selectAllFromMetadataTablePrepStmt = mock(Statement.class); Statement dropNamespaceTableStmt = mock(Statement.class); Statement dropMetadataSchemaStmt = mock(Statement.class); when(dataSource.getConnection()).thenReturn(connection); when(connection.createStatement()) - .thenReturn(dropNamespaceStmt, dropNamespaceTableStmt, dropMetadataSchemaStmt); - when(connection.prepareStatement(anyString())) - .thenReturn(deleteFromNamespaceTablePrepStmt, selectAllFromNamespaceTablePrepStmt); + .thenReturn( + dropNamespaceStmt, + selectAllFromNamespaceTablePrepStmt, + selectAllFromMetadataTablePrepStmt, + dropNamespaceTableStmt, + dropMetadataSchemaStmt); + when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTablePrepStmt); when(dataSource.getConnection()).thenReturn(connection); // Only the metadata schema is left ResultSet resultSet = mockResultSet( new SelectNamespaceNameFromNamespaceTableResultSetMocker.Row(METADATA_SCHEMA)); - when(selectAllFromNamespaceTablePrepStmt.executeQuery()).thenReturn(resultSet); + when(selectAllFromNamespaceTablePrepStmt.executeQuery(anyString())).thenReturn(resultSet); + + SQLException sqlException = mock(SQLException.class); + mockUndefinedTableError(rdbEngine, sqlException); + when(selectAllFromMetadataTablePrepStmt.execute(anyString())).thenThrow(sqlException); // Act admin.dropNamespace(namespace); @@ -1713,8 +1696,8 @@ private void dropNamespace_WithOnlyNamespaceSchemaLeftForX_shouldDropSchemaAndNa verify(deleteFromNamespaceTablePrepStmt).setString(1, namespace); verify(connection).prepareStatement(deleteFromNamespaceTableQuery); verify(deleteFromNamespaceTablePrepStmt).execute(); - verify(connection).prepareStatement(selectAllFromNamespaceTableQuery); - verify(selectAllFromNamespaceTablePrepStmt).executeQuery(); + verify(selectAllFromNamespaceTablePrepStmt).executeQuery(selectAllFromNamespaceTableQuery); + verify(selectAllFromMetadataTablePrepStmt).execute(selectAllFromMetadataTableQuery); verify(dropNamespaceTableStmt).execute(dropNamespaceTableQuery); verify(dropMetadataSchemaStmt).execute(dropMetadataSchemaQuery); } @@ -1782,18 +1765,20 @@ private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( Connection connection = mock(Connection.class); Statement dropNamespaceStatementMock = mock(Statement.class); PreparedStatement deleteFromNamespaceTableMock = mock(PreparedStatement.class); - PreparedStatement selectNamespaceStatementMock = mock(PreparedStatement.class); + Statement selectNamespaceStatementMock = mock(Statement.class); if (rdbEngine != RdbEngine.SQLITE) { - when(connection.createStatement()).thenReturn(dropNamespaceStatementMock); + when(connection.createStatement()) + .thenReturn(dropNamespaceStatementMock, selectNamespaceStatementMock); + } else { + when(connection.createStatement()).thenReturn(selectNamespaceStatementMock); } - when(connection.prepareStatement(anyString())) - .thenReturn(deleteFromNamespaceTableMock, selectNamespaceStatementMock); + when(connection.prepareStatement(anyString())).thenReturn(deleteFromNamespaceTableMock); when(dataSource.getConnection()).thenReturn(connection); // Namespaces table contains other namespaces ResultSet resultSet = mockResultSet( new SelectFullTableNameFromMetadataTableResultSetMocker.Row(namespace + ".tbl1")); - when(selectNamespaceStatementMock.executeQuery()).thenReturn(resultSet); + when(selectNamespaceStatementMock.executeQuery(anyString())).thenReturn(resultSet); // Act admin.dropNamespace(namespace); @@ -1805,8 +1790,7 @@ private void dropNamespace_WithOtherNamespaceLeftForX_shouldOnlyDropNamespace( verify(connection).prepareStatement(deleteFromNamespaceTable); verify(deleteFromNamespaceTableMock).setString(1, namespace); verify(deleteFromNamespaceTableMock).execute(); - verify(connection).prepareStatement(selectNamespaceStatement); - verify(selectNamespaceStatementMock).executeQuery(); + verify(selectNamespaceStatementMock).executeQuery(selectNamespaceStatement); } @Test