diff --git a/testkit/derby/src/main/java/org/eclipse/daanse/jdbc/datasource/testkit/derby/DerbyDatabaseProvider.java b/testkit/derby/src/main/java/org/eclipse/daanse/jdbc/datasource/testkit/derby/DerbyDatabaseProvider.java index 8e80c1d..111f58d 100644 --- a/testkit/derby/src/main/java/org/eclipse/daanse/jdbc/datasource/testkit/derby/DerbyDatabaseProvider.java +++ b/testkit/derby/src/main/java/org/eclipse/daanse/jdbc/datasource/testkit/derby/DerbyDatabaseProvider.java @@ -52,6 +52,43 @@ public String id() { @Override public void close() { + for (String key : dbsByKey.keySet()) { + close(key); + } + } + + /** + * Closes and drops just {@code key}'s in-memory database, leaving any others under this provider untouched. A + * no-op if {@code key} was never {@link #activate(String) activate}d, or was already closed. + * + *
Derby keeps an in-memory database for the whole JVM until it is dropped, its connections closed or not: a + * caller that opens one database per key fills the heap with every key it ever used unless it gives them back + * here. + */ + @Override + public void close(String key) { + ActiveDatabase database = dbsByKey.remove(key); + if (database == null) { + return; + } + try { + database.connectionPool().close(); + } catch (RuntimeException e) { + LOGGER.warn("closing a derby connection pool failed", e); + } + if (database.dataSource() instanceof EmbeddedDataSource embedded) { + EmbeddedDataSource drop = new EmbeddedDataSource(); + drop.setDatabaseName(embedded.getDatabaseName()); + drop.setConnectionAttributes("drop=true"); + try (Connection never = drop.getConnection()) { + // a drop answers with an exception, not with a connection + } catch (SQLException e) { + // 08006: the database is dropped + if (!"08006".equals(e.getSQLState())) { + LOGGER.warn("dropping the derby database {} failed", embedded.getDatabaseName(), e); + } + } + } } /** @@ -59,12 +96,16 @@ public void close() { * in place before {@link EmbeddedDataSource#getConnection()} is called. The page cache * defaults to 1000 pages (~4 MB) — FoodMart does not fit, so every aggregate scan walks back * through the pager. {@code durability=test} skips the log syncs, which buy nothing for a - * database that lives and dies with the JVM. + * database that lives and dies with the JVM. A hash join may hold 64 MB of a table instead of + * 1 MB: below that Derby joins a derived table of some ten thousand rows by nested loops, once + * per row of the other side -- one join of 60 000 facts to such a table ran past an hour, with + * the room it took two seconds. */ private static void bootProperties() { setIfAbsent("derby.storage.pageCacheSize", "20000"); setIfAbsent("derby.system.durability", "test"); setIfAbsent("derby.language.statementCacheSize", "500"); + setIfAbsent("derby.language.maxMemoryPerTable", "65536"); } private static void setIfAbsent(String key, String value) {