From b419dc918d610325baecdbe0a1ec34d9a5b0e004 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 11:05:49 -0400 Subject: [PATCH 01/74] Enable parallel execution in junit with worker_thread_pool JUnit 6.1 introduced a new executor service to be used for parallel testing that does not use a ForkJoinPool. The issue with the ForkJoinPool, was that tasks waiting to join futures do not count towards the concurrency. This means that as soon as a test waited on a future, another test would start, causing more load than FDB could handle. The new service does not suffer from this issue. --- gradle/testing.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle/testing.gradle b/gradle/testing.gradle index 193b62a32e..321cea4285 100644 --- a/gradle/testing.gradle +++ b/gradle/testing.gradle @@ -357,9 +357,13 @@ tasks.withType(Test).configureEach { task -> task.testFramework.options.excludeTags.add('Performance') if (task.name == 'test') { - task.systemProperty('junit.jupiter.execution.parallel.enabled', 'false') + task.systemProperty('junit.jupiter.execution.parallel.enabled', 'true') task.systemProperty('junit.jupiter.execution.parallel.mode.default', 'same_thread') task.systemProperty('junit.jupiter.execution.parallel.mode.classes.default', 'concurrent') + // We use worker_thread_pool rather than fork_join_pool because we heavily depend on ForkJoinPool for + // FDB operations, which causes junit to incorrectly increase the number of concurrent tests to well + // beyond what we want. + task.systemProperty('junit.jupiter.execution.parallel.config.executor-service', 'worker_thread_pool') } } From 5186a49a2e1a2d17dc37e8baedf22f43ee7ec283 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 12:11:00 -0400 Subject: [PATCH 02/74] Make CommandsTest pass when run in parallel --- .../plan/cascades/debug/CommandsTest.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java b/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java index e7b71d40a0..0d6b8f0651 100644 --- a/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java +++ b/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java @@ -61,6 +61,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import javax.annotation.Nonnull; import java.io.ByteArrayOutputStream; @@ -77,7 +79,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; - +// Tests set up shared state (the debugger) on a ThreadLocal via Debugger.setDebugger in @BeforeEach. +// When JUnit's parallel execution is enabled, lifecycle methods and the test body can run on +// different worker threads, leaving the test body without a debugger installed. Pin everything in +// this class to a single thread so the thread-local survives between @BeforeEach and @Test. +@Execution(ExecutionMode.SAME_THREAD) class CommandsTest { private String query; private PipedOutputStream outIn; @@ -307,7 +313,7 @@ void testCountingTautologyBreakPoint() throws IOException { terminal.writer().close(); assertThat(outputStream.toString()).contains( - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "insert_into_memo"), @@ -344,7 +350,7 @@ void testOnEventTypeBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("location", "end") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "initphase"), @@ -367,7 +373,7 @@ void testOnPhaseBreakPoint() throws IOException { terminal.writer().close(); assertThat(outputStream.toString()).contains( - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "initphase"), @@ -407,7 +413,7 @@ void testOnRuleBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("count down", "-1"), ReplTestUtil.coloredKeyValue("ruleNamePrefix", "ImplementSimpleSelectRule") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "transform"), @@ -465,7 +471,7 @@ ref, exp, nonMatchingRule, new CascadesRuleCall(PlannerPhase.PLANNING, PlanConte ReplTestUtil.coloredKeyValue("count down", "-1"), ReplTestUtil.coloredKeyValue("ruleNamePrefix", "ImplementSimpleSelectRule") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), @@ -518,7 +524,7 @@ void testOnYieldExpressionBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("location", "end"), ReplTestUtil.coloredKeyValue("expression", "exp1") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "4"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "4"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), @@ -586,7 +592,7 @@ public Set getMatchCandidates() { ReplTestUtil.coloredKeyValue("location", "end"), ReplTestUtil.coloredKeyValue("candidate", "idx1") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "1"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "1"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), From be9b18032ddf1bacabc515f5dacbcd2b31c94a2e Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 16:16:24 -0400 Subject: [PATCH 03/74] Synchronize part of FRL initialization This protects multiple instances in the same JVM from trying to initialize the keyspace/domain concurrently, which could conflict. This shouldn't be a real problem in production environments, but is low cost, so adding it in support of concurrent tests provides real value. There are probably additional issues that need to be resolved around multiple FRLs registering a driver in the same instance... And it may make sense to have some sort of registry so that we can better control for this, and reduce the number of tests depending on the registered driver. --- .../foundationdb/relational/server/FRL.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index bc07f7b266..eb8e533f96 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -78,6 +78,15 @@ // Needs to be public so can be used by sub-packages; i.e. the JDBCService @API(API.Status.EXPERIMENTAL) public class FRL implements AutoCloseable { + /** + * JVM-wide lock guarding the parts of construction that mutate shared static state + * ({@link com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider#registerDomainIfNotExists}) + * and write the catalog bootstrap to FDB. Without this, concurrent {@code new FRL(...)} calls (e.g. from + * parallel test {@code @BeforeAll} hooks) race on the catalog-init transaction commit and fail with + * "Transaction not committed due to conflict with another transaction". + */ + private static final Object INIT_LOCK = new Object(); + private final FdbConnection fdbDatabase; private final RelationalDriver driver; private boolean registeredJDBCEmbedDriver; @@ -102,13 +111,19 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } this.fdbDatabase = new DirectFdbConnection(fdbDb, NoOpMetricRegistry.INSTANCE); - final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); - keyspaceProvider.registerDomainIfNotExists("FRL"); - KeySpace keySpace = keyspaceProvider.getKeySpace(); - StoreCatalog storeCatalog; - try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); + // Serialize the rest of construction: registerDomainIfNotExists mutates the shared keyspace, and + // the catalog-init transaction below races on commit when multiple FRLs are constructed concurrently + // against the same cluster (typical in parallel tests). + final StoreCatalog storeCatalog; + final KeySpace keySpace; + synchronized (INIT_LOCK) { + final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); + keyspaceProvider.registerDomainIfNotExists("FRL"); + keySpace = keyspaceProvider.getKeySpace(); + try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { + storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + } } RecordLayerConfig rlConfig = RecordLayerConfig.getDefault(); From 9a0b7636b7707d7d76f5fb454e707d0617eedfce Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 10:10:14 -0400 Subject: [PATCH 04/74] Claim used ports for ExternalServer across entire JVM instance This allows for running multiple tests in parallel within the same JVM, without risk of having multiple threads trying to start multiple servers against the same port. This replaces the previous per-ExternalServer approach to protecting port conflicts. This does not protect as well for concurrent ExternalServers in different processes, but currently we don't run concurrently in multiple JVMs, so this should be sufficient. --- .../yamltests/server/ExternalServer.java | 186 ++++++++++-------- .../yamltests/server/ExternalServerTest.java | 23 --- 2 files changed, 100 insertions(+), 109 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java index 562c29e9f3..8f533ca8eb 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java @@ -22,8 +22,6 @@ import com.apple.foundationdb.record.logging.KeyValueLogMessage; import com.apple.foundationdb.record.logging.LogMessageKeys; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; -import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.BuildVersion; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.google.common.base.Verify; @@ -42,12 +40,10 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -60,6 +56,16 @@ public class ExternalServer implements Clusters.BoundToCluster { private static final Logger logger = LogManager.getLogger(ExternalServer.class); public static final String EXTERNAL_SERVER_PROPERTY_NAME = "yaml_testing_external_server"; + /** + * JVM-wide set of ports currently reserved by any {@link ExternalServer} instance. Closes the race + * where two concurrent {@link #startMultiple(Collection)} calls (e.g. from parallel test classes' + * {@code @BeforeAll}) each see the same port as available — this would otherwise let two test + * classes both try to bind the same port (e.g. 1111). The atomic {@link Set#add(Object)} on a + * {@link ConcurrentHashMap#newKeySet()} acts as a CAS: the winner returns {@code true} and owns + * the port until {@link #stop()} releases it. + */ + private static final Set JVM_WIDE_CLAIMED_PORTS = ConcurrentHashMap.newKeySet(); + @Nonnull private final File serverJar; private int grpcPort; @@ -135,60 +141,73 @@ public String clusterFile() { return clusterFile; } - public void start(Set unavailablePorts) throws Exception { - grpcPort = getAvailablePort(unavailablePorts); - httpPort = getAvailablePort(unavailablePorts); - ProcessBuilder processBuilder = new ProcessBuilder("java", - // TODO add support for debugging by adding, but need to take care with ports - // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n", - "-jar", serverJar.getAbsolutePath(), - "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); - boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); - @Nullable - File outFile; - @Nullable - File errFile; - if (saveServerLogs) { - // Include current time in file names so that things sort nicely. We want the out and err logs - // for the same server start to be adjacent, and it would be nice for the log files to sort - // chronologically - long currentTime = System.currentTimeMillis(); - outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); - errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); - } else { - outFile = null; - errFile = null; - } - ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); - ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); - processBuilder.redirectOutput(out); - processBuilder.redirectError(err); - if (clusterFile != null) { - processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); - } + public void start() throws Exception { + grpcPort = getAvailablePort(); + boolean success = false; + try { + httpPort = getAvailablePort(); + ProcessBuilder processBuilder = new ProcessBuilder("java", + // TODO add support for debugging by adding, but need to take care with ports + // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n", + "-jar", serverJar.getAbsolutePath(), + "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); + boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); + @Nullable + File outFile; + @Nullable + File errFile; + if (saveServerLogs) { + // Include current time in file names so that things sort nicely. We want the out and err logs + // for the same server start to be adjacent, and it would be nice for the log files to sort + // chronologically + long currentTime = System.currentTimeMillis(); + outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); + errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); + } else { + outFile = null; + errFile = null; + } + ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); + ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); + processBuilder.redirectOutput(out); + processBuilder.redirectError(err); + if (clusterFile != null) { + processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); + } + + if (!startServer(processBuilder)) { + if (logger.isWarnEnabled()) { + logger.warn(KeyValueLogMessage.of("Failed to start external server", + "jar", serverJar, + LogMessageKeys.VERSION, version, + "grpc_port", grpcPort, + "http_port", httpPort, + "out_file", outFile, + "err_file", errFile)); + } + Assertions.fail("Failed to start the external server"); + } - if (!startServer(processBuilder)) { - if (logger.isWarnEnabled()) { - logger.warn(KeyValueLogMessage.of("Failed to start external server", + if (logger.isInfoEnabled()) { + logger.info(KeyValueLogMessage.of("Started external server", "jar", serverJar, LogMessageKeys.VERSION, version, "grpc_port", grpcPort, "http_port", httpPort, "out_file", outFile, - "err_file", errFile)); + "err_file", errFile + )); + } + success = true; + } finally { + // If we claimed ports but the server failed to come up (Assertions.fail, IOException + // from process.start, etc.), release the JVM-wide claims so the ports become available + // to future allocators. stop() will not be called for a failed start, so this is the + // only cleanup hook. + if (!success) { + JVM_WIDE_CLAIMED_PORTS.remove(grpcPort); + JVM_WIDE_CLAIMED_PORTS.remove(httpPort); } - Assertions.fail("Failed to start the external server"); - } - - if (logger.isInfoEnabled()) { - logger.info(KeyValueLogMessage.of("Started external server", - "jar", serverJar, - LogMessageKeys.VERSION, version, - "grpc_port", grpcPort, - "http_port", httpPort, - "out_file", outFile, - "err_file", errFile - )); } } @@ -276,26 +295,26 @@ public void validateConnectionVersion(@Nonnull Connection connection) throws SQL } /** - * Get a port that is currently available for the server. - * @param unavailablePorts a set of ports that are known to be unavailable. This is useful for two reasons: - * (1) when starting multiple servers, we include all previously allocated ports to avoid starting multiple - * servers on the same ports, and (2) each server needs two ports, one for GRPC, and one for HTTP, so the - * GRPC port can be noted as unavailable when asking for the http port. If nothing is unavailable, use an empty set. - * The provided set must be mutable, as it will be updated during run as ports are allocated - * @return a port that is not currently in use on the system. + * Get a port that is currently available for the server and atomically claim it in + * {@link #JVM_WIDE_CLAIMED_PORTS} so no other {@link ExternalServer} instance — in this batch + * or any concurrent batch in another test class — will pick it. The claim is released by + * {@link #stop()} or by {@link #start()} on a failed-startup path. + * @return a port that is not currently in use on the system and not claimed by another ExternalServer. */ - private int getAvailablePort(@Nonnull final Set unavailablePorts) { + private int getAvailablePort() { // running locally on my laptop, testing if a port is available takes 0 milliseconds, so no need to optimize for (int i = 1111; i < 9999; i++) { - // Add the port immediately to the set of unavailable ports. We do this because there are - // three possibilities: (1) it has already been allocated and so add returns false, (2) it - // is determined to be unavailable by isAvailable, or (3) we allocate it to this server. - // In any case, we don't want to consider this port again. Checking the set before calling - // isAvailable() also ensures that we never need to call isAvailable() more than once for any - // port - if (unavailablePorts.add(i) && isAvailable(i)) { + // Atomically claim across the whole JVM. If another ExternalServer already grabbed this + // port, add() returns false and we keep scanning. + if (!JVM_WIDE_CLAIMED_PORTS.add(i)) { + continue; + } + if (isAvailable(i)) { return i; } + // OS-level check failed (port used by something outside the JVM). Release the claim so + // a later allocator can reconsider it once whatever is occupying it goes away. + JVM_WIDE_CLAIMED_PORTS.remove(i); } return Assertions.fail("Could not find available port between 1111 and 9999"); } @@ -310,30 +329,25 @@ public static boolean isAvailable(int port) { } public void stop() { - if ((serverProcess != null) && serverProcess.isAlive()) { - serverProcess.destroy(); + try { + if ((serverProcess != null) && serverProcess.isAlive()) { + serverProcess.destroy(); + } + } finally { + // Release JVM-wide port claims unconditionally so the ports become available to other + // ExternalServer instances even if the subprocess already exited on its own. The default + // int value (0) is never added to JVM_WIDE_CLAIMED_PORTS, so calling stop() before + // start() is harmless. + JVM_WIDE_CLAIMED_PORTS.remove(grpcPort); + JVM_WIDE_CLAIMED_PORTS.remove(httpPort); } } public static void startMultiple(@Nonnull Collection servers) throws Exception { - startMultiple(servers, new HashSet<>()); - } - - public static void startMultiple(@Nonnull Collection servers, @Nonnull Set unavailablePorts) throws Exception { - final Map allocatedPorts = new HashMap<>(); + // Port uniqueness within and across batches is guaranteed by JVM_WIDE_CLAIMED_PORTS — each + // server's start() atomically claims its grpc and http ports before binding. for (ExternalServer server : servers) { - server.start(unavailablePorts); - // Threading through unavailablePorts should be enough to make sure each port is unique. - // Double check both ports, though - checkPortIsUnique(server.getPort(), server, allocatedPorts); - checkPortIsUnique(server.getHttpPort(), server, allocatedPorts); - } - } - - private static void checkPortIsUnique(int port, @Nonnull ExternalServer server, @Nonnull Map allocatedPorts) throws RelationalException { - @Nullable ExternalServer preExistingServer = allocatedPorts.putIfAbsent(port, server); - if (preExistingServer != null) { - Assert.fail("allocated duplicate port (" + server.getPort() + ") to servers for versions " + server.getVersion() + " and " + preExistingServer.getVersion()); + server.start(); } } } diff --git a/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java b/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java index 183a5acbb5..49b408e1ac 100644 --- a/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java +++ b/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java @@ -30,9 +30,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -73,27 +71,6 @@ void startMultiple() throws Exception { } } - @Test - void startMultipleWithAdditionalExclusions() throws Exception { - final List servers = new ArrayList<>(); - final String clusterFile = FDBTestEnvironment.randomClusterFile(); - for (int i = 0; i < 3; i++) { - servers.add(new ExternalServer(currentServerPath, clusterFile)); - } - try { - final Set explicitExcluded = Set.of(1111, 1115, 1116); - ExternalServer.startMultiple(servers, new HashSet<>(explicitExcluded)); - assertDistinctPorts(servers); - assertThat(servers) - .flatMap(ExternalServer::getPort, ExternalServer::getHttpPort) - .doesNotContainAnyElementsOf(explicitExcluded); - } finally { - for (final ExternalServer server : servers) { - server.stop(); - } - } - } - private static void assertDistinctPorts(@Nonnull Collection servers) { // we can't assert about the actual values, because one of the ports may be busy, // so assert that each server has its own port, and none of them have the same port From ac90015be896d937a54e40936ca7de45b0a337e5 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 11:32:11 -0400 Subject: [PATCH 05/74] Avoid constant Yaml classes SnakeYaml is not threadsafe, so reusing the same parser when we are running tests in parallel causes bugs --- .../yamltests/block/IncludeBlock.java | 22 +++++++++++++------ .../yamltests/command/QueryInterpreter.java | 17 ++++++++++++-- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java index 8e0f4a4d14..2db4cfa60b 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java @@ -53,14 +53,22 @@ public class IncludeBlock extends SupportBlock { public static final String INCLUDE = "include"; private static final Logger logger = LogManager.getLogger(IncludeBlock.class); - private static final Yaml YAML_ENGINE; - static { - LoaderOptions loaderOptions = new LoaderOptions(); + /** + * Builds a fresh {@link Yaml} instance for parsing an included resource. + *

+ * SnakeYAML's {@link Yaml} (and its underlying {@code Scanner}/{@code Parser}/{@code StreamReader}) + * is documented as not thread-safe. When yaml-tests run with class-level parallel execution + * enabled (see {@code gradle/testing.gradle}), multiple test classes call {@link #parse} concurrently, + * and a shared static engine corrupted into {@code ScannerException}/{@code ParserException}/ + * {@code ConcurrentModificationException}/{@code ClassCastException} on parser-internal state. + */ + @Nonnull + private static Yaml newYamlEngine() { + final LoaderOptions loaderOptions = new LoaderOptions(); loaderOptions.setAllowDuplicateKeys(true); - DumperOptions dumperOptions = new DumperOptions(); - - YAML_ENGINE = new Yaml(new CustomYamlConstructor(loaderOptions), new Representer(dumperOptions), + final DumperOptions dumperOptions = new DumperOptions(); + return new Yaml(new CustomYamlConstructor(loaderOptions), new Representer(dumperOptions), new DumperOptions(), loaderOptions, new Resolver()); } @@ -88,7 +96,7 @@ public static List parse(@Nonnull final YamlReference.YamlResource resour int blockNumber = 0; executionContext.registerResource(resource); try (var inputStream = getInputStream(resource)) { - final var docs = StreamSupport.stream(YAML_ENGINE.loadAll(inputStream).spliterator(), false).collect(Collectors.toList()); + final var docs = StreamSupport.stream(newYamlEngine().loadAll(inputStream).spliterator(), false).collect(Collectors.toList()); for (var doc : docs) { final var blocks = Block.parse(resource, doc, blockNumber, executionContext, resource.isTopLevel()); allBlocks.addAll(blocks); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java index f4fcddb7fe..f35cd6fe74 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java @@ -95,7 +95,20 @@ public final class QueryInterpreter { */ private final List> injections; - private static final Yaml INTERPRETER = new Yaml(new QueryParameterYamlConstructor(new LoaderOptions())); + /** + * Builds a fresh {@link Yaml} instance for parsing query parameter injections (the {@code !!…!!} + * snippets). + *

+ * SnakeYAML's {@link Yaml} is not thread-safe: its underlying {@code Scanner}/{@code Parser}/ + * {@code StreamReader} hold per-parse state. Tests run with class-level parallel execution (see + * {@code gradle/testing.gradle}), so a shared static instance would let two test classes corrupt + * each other's parse state — observed as {@code ScannerException}, {@code ParserException}, + * {@code ClassCastException} (MappingEndEvent → NodeEvent), {@code NoSuchElementException}, etc. + */ + @Nonnull + private static Yaml newInterpreter() { + return new Yaml(new QueryParameterYamlConstructor(new LoaderOptions())); + } private static final class QueryParameterYamlConstructor extends SafeConstructor { private static final Tag RANDOM_TAG = new Tag("!r"); @@ -301,7 +314,7 @@ private List> getInjections(@Nonnull String query) { int end = query.indexOf("!!", start + 2); Assert.thatUnchecked(end != -1, "Illegal format: Parameter injection not formed correctly in query " + query); cursor = end + 2; - lst.add(Pair.of(query.substring(start, end + 2), INTERPRETER.load(query.substring(start + 2, end)))); + lst.add(Pair.of(query.substring(start, end + 2), newInterpreter().load(query.substring(start + 2, end)))); } return lst; } From 1153f03b25c2552db0e426367928dd7c70fc59ba Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 15:14:37 -0400 Subject: [PATCH 06/74] Lock setup exclusively with FRL initialization This is temporary. Notably, the reason we have to lock the schema setup is because dropping a database calls deleteStore which always bumps the metadata version, even though the stores that we dropping don't cache the store header. This means that deleting a database/schema will conflict with creating a database/schema (or any other operation involving the catalog, I think). --- .../foundationdb/relational/server/FRL.java | 30 +++++++--- .../yamltests/block/SetupBlock.java | 57 ++++++++++++++++++- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index eb8e533f96..d7db360659 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -79,13 +79,29 @@ @API(API.Status.EXPERIMENTAL) public class FRL implements AutoCloseable { /** - * JVM-wide lock guarding the parts of construction that mutate shared static state - * ({@link com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider#registerDomainIfNotExists}) - * and write the catalog bootstrap to FDB. Without this, concurrent {@code new FRL(...)} calls (e.g. from - * parallel test {@code @BeforeAll} hooks) race on the catalog-init transaction commit and fail with - * "Transaction not committed due to conflict with another transaction". + * JVM-wide lock guarding any operation that mutates the shared catalog metadata or its underlying + * static state ({@link com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider#registerDomainIfNotExists} + * and the catalog-bootstrap transaction below). Without this, concurrent {@code new FRL(...)} calls + * — e.g. from parallel test {@code @BeforeAll} hooks — race on the catalog-init transaction commit + * and fail with "Transaction not committed due to conflict with another transaction". + *

+ * Exposed (package-public to {@code .server}; referenced by reflection or test-only callers via + * {@link #catalogLock()}) so that test scaffolding that runs additional catalog-mutating DDL + * (CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE) can serialize against FRL initialization + * on the same JVM and avoid the same race. */ - private static final Object INIT_LOCK = new Object(); + private static final Object CATALOG_LOCK = new Object(); + + /** + * Returns the JVM-wide monitor used to serialize catalog-mutating DDL. Hold this when issuing + * CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, or any other operation that writes the + * cluster-global catalog metadata, to avoid SQLSTATE 40001 conflicts with concurrent + * {@link #FRL(Options, String, boolean)} construction or other DDL on the same JVM. + */ + @Nonnull + public static Object catalogLock() { + return CATALOG_LOCK; + } private final FdbConnection fdbDatabase; private final RelationalDriver driver; @@ -116,7 +132,7 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis // against the same cluster (typical in parallel tests). final StoreCatalog storeCatalog; final KeySpace keySpace; - synchronized (INIT_LOCK) { + synchronized (CATALOG_LOCK) { final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); keyspaceProvider.registerDomainIfNotExists("FRL"); keySpace = keyspaceProvider.getKeySpace(); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java index 0ca83b7923..5193c7c554 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java @@ -21,6 +21,8 @@ package com.apple.foundationdb.relational.yamltests.block; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.server.FRL; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.yamltests.CustomYamlConstructor; import com.apple.foundationdb.relational.yamltests.Matchers; @@ -71,7 +73,7 @@ protected SetupBlock(@Nonnull YamlReference reference, @Nonnull List executeExecutables(executables)); } catch (Throwable e) { throw YamlExecutionContext.wrapContext(e, () -> "‼️ Failed to execute all the setup steps in Setup block at " + getReference(), @@ -79,6 +81,52 @@ public void execute() { } } + /** + * Runs {@code runnable} under {@link FRL#catalogLock()} with a small retry loop on SQLSTATE + * 40001 transaction conflicts. The lock is shared with {@link FRL}'s catalog-bootstrap, so + * setup-block DDL (CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, CREATE SCHEMA) is + * serialized against both other setup blocks and other test classes' FRL construction on the + * same JVM. The retry catches any residual conflicts caused by code paths that write the + * cluster-global meta-data version-stamp outside our control. + *

+ * Retry is safe because the schema_template and destruct executable lists use {@code DROP … + * IF EXISTS} so a partial-success retry doesn't trip "unknown database" errors. Manual setup + * blocks that include catalog-mutating DDL are expected to follow the same convention. + */ + private static void runLockedWithRetry(@Nonnull Runnable runnable) { + synchronized (FRL.catalogLock()) { + final int maxAttempts = 5; + for (int attempt = 1; ; attempt++) { + try { + runnable.run(); + return; + } catch (Throwable e) { + if (attempt >= maxAttempts || !isTransactionConflict(e)) { + throw e; + } + try { + Thread.sleep(10L * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw e; + } + } + } + } + } + + private static boolean isTransactionConflict(@Nonnull Throwable t) { + Throwable cursor = t; + while (cursor != null) { + if (cursor instanceof SQLException + && ErrorCode.SERIALIZATION_FAILURE.getErrorCode().equals(((SQLException) cursor).getSQLState())) { + return true; + } + cursor = cursor.getCause(); + } + return false; + } + public static final class ManualSetupBlock extends SetupBlock { public static final String STEPS = "steps"; @@ -316,8 +364,11 @@ public static DestructTemplateBlock withDatabaseAndSchema(@Nonnull final YamlRef @Nonnull String schemaTemplateName, @Nonnull String databasePath) { try { final var steps = new ArrayList(); - steps.add("DROP DATABASE " + databasePath); - steps.add("DROP SCHEMA TEMPLATE " + schemaTemplateName); + // Use IF EXISTS so a retry after a partial success (first attempt dropped the + // database but conflicted on the schema-template DROP) doesn't fail on the second + // attempt's "Cannot delete unknown database" / "Schema template doesn't exist". + steps.add("DROP DATABASE IF EXISTS " + databasePath); + steps.add("DROP SCHEMA TEMPLATE IF EXISTS " + schemaTemplateName); final var executables = new ArrayList>(); for (final var step : steps) { final var resolvedCommand = QueryCommand.withQueryString(reference, step, executionContext); From f391e135a28216bd24e18acf9f9199778b537d97 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 11:34:07 -0400 Subject: [PATCH 07/74] Used time-based retry for starting external servers --- .../yamltests/server/ExternalServer.java | 82 +++++++++++++++---- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java index 8f533ca8eb..dcf65d00fc 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java @@ -152,30 +152,30 @@ public void start() throws Exception { "-jar", serverJar.getAbsolutePath(), "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); + // Always capture stderr to a tempfile so we can include its tail in the failure message + // if the subprocess dies or never becomes available. Stdout is only retained when the + // tests.saveServerLogs sysProp is set, since it can be voluminous in the happy path. + // Include current time in file names so they sort nicely. + long currentTime = System.currentTimeMillis(); @Nullable File outFile; - @Nullable - File errFile; if (saveServerLogs) { - // Include current time in file names so that things sort nicely. We want the out and err logs - // for the same server start to be adjacent, and it would be nice for the log files to sort - // chronologically - long currentTime = System.currentTimeMillis(); outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); - errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); } else { outFile = null; - errFile = null; } + final File errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); + // Don't keep the diagnostic file around in the success path (clutters tmp on dev boxes). + errFile.deleteOnExit(); ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); - ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); processBuilder.redirectOutput(out); - processBuilder.redirectError(err); + processBuilder.redirectError(ProcessBuilder.Redirect.to(errFile)); if (clusterFile != null) { processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); } if (!startServer(processBuilder)) { + final String errTail = tailFile(errFile, 4096); if (logger.isWarnEnabled()) { logger.warn(KeyValueLogMessage.of("Failed to start external server", "jar", serverJar, @@ -183,9 +183,17 @@ public void start() throws Exception { "grpc_port", grpcPort, "http_port", httpPort, "out_file", outFile, - "err_file", errFile)); + "err_file", errFile, + "subprocess_alive", serverProcess != null && serverProcess.isAlive(), + "exit_value", serverProcess != null && !serverProcess.isAlive() ? serverProcess.exitValue() : "n/a")); } - Assertions.fail("Failed to start the external server"); + Assertions.fail("Failed to start the external server (grpc_port=" + grpcPort + + ", http_port=" + httpPort + + ", alive=" + (serverProcess != null && serverProcess.isAlive()) + + (serverProcess != null && !serverProcess.isAlive() ? ", exit=" + serverProcess.exitValue() : "") + + ")\n--- last bytes of subprocess stderr (" + errFile + ") ---\n" + + errTail + + "\n--- end ---"); } if (logger.isInfoEnabled()) { @@ -211,6 +219,23 @@ public void start() throws Exception { } } + /** Read the trailing {@code maxBytes} of {@code file} as a UTF-8 string, never throwing. */ + @Nonnull + private static String tailFile(@Nonnull File file, int maxBytes) { + try { + final long size = file.length(); + try (java.io.RandomAccessFile raf = new java.io.RandomAccessFile(file, "r")) { + final int read = (int) Math.min(maxBytes, size); + raf.seek(size - read); + final byte[] buf = new byte[read]; + raf.readFully(buf); + return (size > read ? "…(truncated)…\n" : "") + new String(buf, java.nio.charset.StandardCharsets.UTF_8); + } + } catch (IOException e) { + return "(failed to read " + file + ": " + e + ")"; + } + } + /** * Get a list of available servers in the download folder. * @@ -252,12 +277,35 @@ private boolean startServer(ProcessBuilder processBuilder) throws IOException, S } private boolean attemptConnectionWithRetry() throws SQLException, InterruptedException { - final int maxAttempts = 20; - boolean started = false; + // Time-based budget rather than a fixed retry count: under JVM-wide contention from a + // parallel test run (e.g. many test classes each spawning their own external servers, + // YamlIntegrationTests starts ~5) the subprocess can take well over 10s to finish JVM + // warmup, FDB connect, and bind its gRPC port. In isolation the same start completes in + // ~3s, so the larger ceiling only kicks in on contended runs. + final long deadlineMillis = System.currentTimeMillis() + 30_000L; int attempts = 0; - while (!started && attempts < maxAttempts) { - long delay = 50L * attempts; - Thread.sleep(delay); + boolean started = false; + while (!started) { + // If the subprocess has died, no amount of retrying will help — bail immediately so + // the test fails quickly with an actionable signal instead of consuming the full budget. + if (serverProcess != null && !serverProcess.isAlive()) { + if (logger.isWarnEnabled()) { + logger.warn(KeyValueLogMessage.of("External server subprocess exited before becoming available", + LogMessageKeys.VERSION, version, + "grpc_port", getPort(), + "exit_value", serverProcess.exitValue(), + "attempts", attempts)); + } + return false; + } + if (System.currentTimeMillis() >= deadlineMillis) { + return false; + } + // Linear backoff capped at 1s so we don't pace much beyond steady state. + final long delay = Math.min(50L * attempts, 1000L); + if (delay > 0) { + Thread.sleep(delay); + } started = attemptConnection(); attempts++; if (logger.isDebugEnabled()) { From 0986409450e8bcd2dc5508479a068fbf8854f546 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 11:48:45 -0400 Subject: [PATCH 08/74] FRL: Retry conflicts during catalog initialization This should be irrelevant for production, but can be quite a problem when tests are running in parallel. --- .../foundationdb/relational/server/FRL.java | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index d7db360659..199a77b468 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -38,6 +38,7 @@ import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.metrics.NoOpMetricRegistry; import com.apple.foundationdb.relational.jdbc.TypeConversion; @@ -127,19 +128,22 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } this.fdbDatabase = new DirectFdbConnection(fdbDb, NoOpMetricRegistry.INSTANCE); - // Serialize the rest of construction: registerDomainIfNotExists mutates the shared keyspace, and - // the catalog-init transaction below races on commit when multiple FRLs are constructed concurrently - // against the same cluster (typical in parallel tests). + // Serialize the rest of construction in-JVM: registerDomainIfNotExists mutates the shared + // keyspace, and the catalog-init transaction below races on commit when multiple FRLs are + // constructed concurrently against the same cluster (typical in parallel tests). + // + // The lock only covers within-JVM contention; multiple OS processes (e.g. parent test JVM + // + several external-server subprocesses) constructing FRL against the same cluster will + // still race on the catalog-init commit and produce SQLSTATE 40001. Retry handles that + // case — the catalog init is idempotent under retry because openRecordStore opens the + // already-initialized store on a re-attempt. final StoreCatalog storeCatalog; final KeySpace keySpace; synchronized (CATALOG_LOCK) { final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); keyspaceProvider.registerDomainIfNotExists("FRL"); keySpace = keyspaceProvider.getKeySpace(); - try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); - } + storeCatalog = initializeCatalogWithRetry(keySpace); } RecordLayerConfig rlConfig = RecordLayerConfig.getDefault(); @@ -174,6 +178,41 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } } + /** + * Initializes the store catalog under a fresh transaction, retrying on SQLSTATE 40001 + * (transaction conflict) up to a small attempt limit. Used to absorb cross-JVM races on the + * catalog-init commit when several FRL processes start concurrently against the same cluster + * (e.g. the test JVM plus several external-server subprocesses). Within a single JVM the + * surrounding {@link #CATALOG_LOCK} prevents contention; this retry covers the cross-process + * case where that lock has no effect. + */ + @Nonnull + private StoreCatalog initializeCatalogWithRetry(@Nonnull KeySpace keySpace) throws RelationalException { + final int maxAttempts = 10; + RelationalException last = null; + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { + final StoreCatalog catalog = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + return catalog; + } catch (RelationalException e) { + if (e.getErrorCode() != ErrorCode.SERIALIZATION_FAILURE) { + throw e; + } + last = e; + // Short, slightly increasing backoff; cross-process commit-conflict windows are brief. + try { + Thread.sleep(20L * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw e; + } + } + } + // Exhausted retries; surface the most recent conflict so callers see why startup failed. + throw last; + } + public RelationalDriver getDriver() { return driver; } From 599a5ad4be3b42b8dedefd9aa46711b774ef2064 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 11:21:49 -0400 Subject: [PATCH 09/74] Increase plan cache sizes Running more tests in parallel, on the same JVM we can run into cache evictions, which causes the tests to fail when the expect plans to be cached. --- .../relational/yamltests/configs/EmbeddedConfig.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java index d3c50b6b77..e819ad0ecb 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java @@ -61,7 +61,13 @@ public void beforeAll() throws Exception { .withOption(Options.Name.PLAN_CACHE_PRIMARY_TIME_TO_LIVE_MILLIS, 3_600_000L) .withOption(Options.Name.PLAN_CACHE_SECONDARY_TIME_TO_LIVE_MILLIS, 3_600_000L) .withOption(Options.Name.PLAN_CACHE_TERTIARY_TIME_TO_LIVE_MILLIS, 3_600_000L) - .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 10) + // Moderate bump (roughly 10x defaults) of all three tiers so that under heavy + // class-level parallel execution, plans accumulated across many sequential test + // methods in the same FRL don't get evicted before the framework's "should have + // hit the cache by now" assertion runs. Tertiary default (8) is the tight one. + .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 100) + .withOption(Options.Name.PLAN_CACHE_SECONDARY_MAX_ENTRIES, 1000) + .withOption(Options.Name.PLAN_CACHE_TERTIARY_MAX_ENTRIES, 100) .build(); // The primary FRL registers its driver in DriverManager; additional ones do not // We register the primary one to make sure that everything works the same if it is registered vs not, to From ed0a431b0d9afc9d5915b92f4e748b3f78c8bcb6 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 12:03:41 -0400 Subject: [PATCH 10/74] Add comment about growing size too high --- .../relational/yamltests/configs/EmbeddedConfig.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java index e819ad0ecb..ebb5e3051b 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java @@ -65,6 +65,13 @@ public void beforeAll() throws Exception { // class-level parallel execution, plans accumulated across many sequential test // methods in the same FRL don't get evicted before the framework's "should have // hit the cache by now" assertion runs. Tertiary default (8) is the tight one. + // + // Larger sizes (e.g. 2000+) verified to expose a real cache-invalidation gap: + // SELECT/scan plans cached against a schema or record store that a subsequent + // test drops are not invalidated, so the next test that re-uses the cached plan + // fails with RecordStoreDoesNotExistException or "SchemaTemplate=… is not in + // catalog". Keeping the bump moderate sidesteps the gap until it's fixed + // separately. .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 100) .withOption(Options.Name.PLAN_CACHE_SECONDARY_MAX_ENTRIES, 1000) .withOption(Options.Name.PLAN_CACHE_TERTIARY_MAX_ENTRIES, 100) From bccf4487b37f009b262e02992ce7c826085a397d Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 11:05:49 -0400 Subject: [PATCH 11/74] Enable parallel execution in junit with worker_thread_pool JUnit 6.1 introduced a new executor service to be used for parallel testing that does not use a ForkJoinPool. The issue with the ForkJoinPool, was that tasks waiting to join futures do not count towards the concurrency. This means that as soon as a test waited on a future, another test would start, causing more load than FDB could handle. The new service does not suffer from this issue. --- gradle/testing.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle/testing.gradle b/gradle/testing.gradle index 193b62a32e..321cea4285 100644 --- a/gradle/testing.gradle +++ b/gradle/testing.gradle @@ -357,9 +357,13 @@ tasks.withType(Test).configureEach { task -> task.testFramework.options.excludeTags.add('Performance') if (task.name == 'test') { - task.systemProperty('junit.jupiter.execution.parallel.enabled', 'false') + task.systemProperty('junit.jupiter.execution.parallel.enabled', 'true') task.systemProperty('junit.jupiter.execution.parallel.mode.default', 'same_thread') task.systemProperty('junit.jupiter.execution.parallel.mode.classes.default', 'concurrent') + // We use worker_thread_pool rather than fork_join_pool because we heavily depend on ForkJoinPool for + // FDB operations, which causes junit to incorrectly increase the number of concurrent tests to well + // beyond what we want. + task.systemProperty('junit.jupiter.execution.parallel.config.executor-service', 'worker_thread_pool') } } From 542654baa68e10bee48a3b6ecee862deb80399be Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 12:11:00 -0400 Subject: [PATCH 12/74] Make CommandsTest pass when run in parallel --- .../plan/cascades/debug/CommandsTest.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java b/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java index e7b71d40a0..0d6b8f0651 100644 --- a/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java +++ b/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java @@ -61,6 +61,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import javax.annotation.Nonnull; import java.io.ByteArrayOutputStream; @@ -77,7 +79,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; - +// Tests set up shared state (the debugger) on a ThreadLocal via Debugger.setDebugger in @BeforeEach. +// When JUnit's parallel execution is enabled, lifecycle methods and the test body can run on +// different worker threads, leaving the test body without a debugger installed. Pin everything in +// this class to a single thread so the thread-local survives between @BeforeEach and @Test. +@Execution(ExecutionMode.SAME_THREAD) class CommandsTest { private String query; private PipedOutputStream outIn; @@ -307,7 +313,7 @@ void testCountingTautologyBreakPoint() throws IOException { terminal.writer().close(); assertThat(outputStream.toString()).contains( - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "insert_into_memo"), @@ -344,7 +350,7 @@ void testOnEventTypeBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("location", "end") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "initphase"), @@ -367,7 +373,7 @@ void testOnPhaseBreakPoint() throws IOException { terminal.writer().close(); assertThat(outputStream.toString()).contains( - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "initphase"), @@ -407,7 +413,7 @@ void testOnRuleBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("count down", "-1"), ReplTestUtil.coloredKeyValue("ruleNamePrefix", "ImplementSimpleSelectRule") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "transform"), @@ -465,7 +471,7 @@ ref, exp, nonMatchingRule, new CascadesRuleCall(PlannerPhase.PLANNING, PlanConte ReplTestUtil.coloredKeyValue("count down", "-1"), ReplTestUtil.coloredKeyValue("ruleNamePrefix", "ImplementSimpleSelectRule") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), @@ -518,7 +524,7 @@ void testOnYieldExpressionBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("location", "end"), ReplTestUtil.coloredKeyValue("expression", "exp1") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "4"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "4"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), @@ -586,7 +592,7 @@ public Set getMatchCandidates() { ReplTestUtil.coloredKeyValue("location", "end"), ReplTestUtil.coloredKeyValue("candidate", "idx1") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "1"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "1"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), From 9679cd438062b81bed0596e5b85d72ff863241b0 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 16:16:24 -0400 Subject: [PATCH 13/74] Synchronize part of FRL initialization This protects multiple instances in the same JVM from trying to initialize the keyspace/domain concurrently, which could conflict. This shouldn't be a real problem in production environments, but is low cost, so adding it in support of concurrent tests provides real value. There are probably additional issues that need to be resolved around multiple FRLs registering a driver in the same instance... And it may make sense to have some sort of registry so that we can better control for this, and reduce the number of tests depending on the registered driver. --- .../foundationdb/relational/server/FRL.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index bc07f7b266..eb8e533f96 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -78,6 +78,15 @@ // Needs to be public so can be used by sub-packages; i.e. the JDBCService @API(API.Status.EXPERIMENTAL) public class FRL implements AutoCloseable { + /** + * JVM-wide lock guarding the parts of construction that mutate shared static state + * ({@link com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider#registerDomainIfNotExists}) + * and write the catalog bootstrap to FDB. Without this, concurrent {@code new FRL(...)} calls (e.g. from + * parallel test {@code @BeforeAll} hooks) race on the catalog-init transaction commit and fail with + * "Transaction not committed due to conflict with another transaction". + */ + private static final Object INIT_LOCK = new Object(); + private final FdbConnection fdbDatabase; private final RelationalDriver driver; private boolean registeredJDBCEmbedDriver; @@ -102,13 +111,19 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } this.fdbDatabase = new DirectFdbConnection(fdbDb, NoOpMetricRegistry.INSTANCE); - final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); - keyspaceProvider.registerDomainIfNotExists("FRL"); - KeySpace keySpace = keyspaceProvider.getKeySpace(); - StoreCatalog storeCatalog; - try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); + // Serialize the rest of construction: registerDomainIfNotExists mutates the shared keyspace, and + // the catalog-init transaction below races on commit when multiple FRLs are constructed concurrently + // against the same cluster (typical in parallel tests). + final StoreCatalog storeCatalog; + final KeySpace keySpace; + synchronized (INIT_LOCK) { + final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); + keyspaceProvider.registerDomainIfNotExists("FRL"); + keySpace = keyspaceProvider.getKeySpace(); + try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { + storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + } } RecordLayerConfig rlConfig = RecordLayerConfig.getDefault(); From 4700cdb99af6c1dd1dafd8146548f769c5d1dfff Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 10:10:14 -0400 Subject: [PATCH 14/74] Claim used ports for ExternalServer across entire JVM instance This allows for running multiple tests in parallel within the same JVM, without risk of having multiple threads trying to start multiple servers against the same port. This replaces the previous per-ExternalServer approach to protecting port conflicts. This does not protect as well for concurrent ExternalServers in different processes, but currently we don't run concurrently in multiple JVMs, so this should be sufficient. --- .../yamltests/server/ExternalServer.java | 186 ++++++++++-------- .../yamltests/server/ExternalServerTest.java | 23 --- 2 files changed, 100 insertions(+), 109 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java index 562c29e9f3..8f533ca8eb 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java @@ -22,8 +22,6 @@ import com.apple.foundationdb.record.logging.KeyValueLogMessage; import com.apple.foundationdb.record.logging.LogMessageKeys; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; -import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.BuildVersion; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.google.common.base.Verify; @@ -42,12 +40,10 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -60,6 +56,16 @@ public class ExternalServer implements Clusters.BoundToCluster { private static final Logger logger = LogManager.getLogger(ExternalServer.class); public static final String EXTERNAL_SERVER_PROPERTY_NAME = "yaml_testing_external_server"; + /** + * JVM-wide set of ports currently reserved by any {@link ExternalServer} instance. Closes the race + * where two concurrent {@link #startMultiple(Collection)} calls (e.g. from parallel test classes' + * {@code @BeforeAll}) each see the same port as available — this would otherwise let two test + * classes both try to bind the same port (e.g. 1111). The atomic {@link Set#add(Object)} on a + * {@link ConcurrentHashMap#newKeySet()} acts as a CAS: the winner returns {@code true} and owns + * the port until {@link #stop()} releases it. + */ + private static final Set JVM_WIDE_CLAIMED_PORTS = ConcurrentHashMap.newKeySet(); + @Nonnull private final File serverJar; private int grpcPort; @@ -135,60 +141,73 @@ public String clusterFile() { return clusterFile; } - public void start(Set unavailablePorts) throws Exception { - grpcPort = getAvailablePort(unavailablePorts); - httpPort = getAvailablePort(unavailablePorts); - ProcessBuilder processBuilder = new ProcessBuilder("java", - // TODO add support for debugging by adding, but need to take care with ports - // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n", - "-jar", serverJar.getAbsolutePath(), - "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); - boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); - @Nullable - File outFile; - @Nullable - File errFile; - if (saveServerLogs) { - // Include current time in file names so that things sort nicely. We want the out and err logs - // for the same server start to be adjacent, and it would be nice for the log files to sort - // chronologically - long currentTime = System.currentTimeMillis(); - outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); - errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); - } else { - outFile = null; - errFile = null; - } - ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); - ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); - processBuilder.redirectOutput(out); - processBuilder.redirectError(err); - if (clusterFile != null) { - processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); - } + public void start() throws Exception { + grpcPort = getAvailablePort(); + boolean success = false; + try { + httpPort = getAvailablePort(); + ProcessBuilder processBuilder = new ProcessBuilder("java", + // TODO add support for debugging by adding, but need to take care with ports + // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n", + "-jar", serverJar.getAbsolutePath(), + "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); + boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); + @Nullable + File outFile; + @Nullable + File errFile; + if (saveServerLogs) { + // Include current time in file names so that things sort nicely. We want the out and err logs + // for the same server start to be adjacent, and it would be nice for the log files to sort + // chronologically + long currentTime = System.currentTimeMillis(); + outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); + errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); + } else { + outFile = null; + errFile = null; + } + ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); + ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); + processBuilder.redirectOutput(out); + processBuilder.redirectError(err); + if (clusterFile != null) { + processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); + } + + if (!startServer(processBuilder)) { + if (logger.isWarnEnabled()) { + logger.warn(KeyValueLogMessage.of("Failed to start external server", + "jar", serverJar, + LogMessageKeys.VERSION, version, + "grpc_port", grpcPort, + "http_port", httpPort, + "out_file", outFile, + "err_file", errFile)); + } + Assertions.fail("Failed to start the external server"); + } - if (!startServer(processBuilder)) { - if (logger.isWarnEnabled()) { - logger.warn(KeyValueLogMessage.of("Failed to start external server", + if (logger.isInfoEnabled()) { + logger.info(KeyValueLogMessage.of("Started external server", "jar", serverJar, LogMessageKeys.VERSION, version, "grpc_port", grpcPort, "http_port", httpPort, "out_file", outFile, - "err_file", errFile)); + "err_file", errFile + )); + } + success = true; + } finally { + // If we claimed ports but the server failed to come up (Assertions.fail, IOException + // from process.start, etc.), release the JVM-wide claims so the ports become available + // to future allocators. stop() will not be called for a failed start, so this is the + // only cleanup hook. + if (!success) { + JVM_WIDE_CLAIMED_PORTS.remove(grpcPort); + JVM_WIDE_CLAIMED_PORTS.remove(httpPort); } - Assertions.fail("Failed to start the external server"); - } - - if (logger.isInfoEnabled()) { - logger.info(KeyValueLogMessage.of("Started external server", - "jar", serverJar, - LogMessageKeys.VERSION, version, - "grpc_port", grpcPort, - "http_port", httpPort, - "out_file", outFile, - "err_file", errFile - )); } } @@ -276,26 +295,26 @@ public void validateConnectionVersion(@Nonnull Connection connection) throws SQL } /** - * Get a port that is currently available for the server. - * @param unavailablePorts a set of ports that are known to be unavailable. This is useful for two reasons: - * (1) when starting multiple servers, we include all previously allocated ports to avoid starting multiple - * servers on the same ports, and (2) each server needs two ports, one for GRPC, and one for HTTP, so the - * GRPC port can be noted as unavailable when asking for the http port. If nothing is unavailable, use an empty set. - * The provided set must be mutable, as it will be updated during run as ports are allocated - * @return a port that is not currently in use on the system. + * Get a port that is currently available for the server and atomically claim it in + * {@link #JVM_WIDE_CLAIMED_PORTS} so no other {@link ExternalServer} instance — in this batch + * or any concurrent batch in another test class — will pick it. The claim is released by + * {@link #stop()} or by {@link #start()} on a failed-startup path. + * @return a port that is not currently in use on the system and not claimed by another ExternalServer. */ - private int getAvailablePort(@Nonnull final Set unavailablePorts) { + private int getAvailablePort() { // running locally on my laptop, testing if a port is available takes 0 milliseconds, so no need to optimize for (int i = 1111; i < 9999; i++) { - // Add the port immediately to the set of unavailable ports. We do this because there are - // three possibilities: (1) it has already been allocated and so add returns false, (2) it - // is determined to be unavailable by isAvailable, or (3) we allocate it to this server. - // In any case, we don't want to consider this port again. Checking the set before calling - // isAvailable() also ensures that we never need to call isAvailable() more than once for any - // port - if (unavailablePorts.add(i) && isAvailable(i)) { + // Atomically claim across the whole JVM. If another ExternalServer already grabbed this + // port, add() returns false and we keep scanning. + if (!JVM_WIDE_CLAIMED_PORTS.add(i)) { + continue; + } + if (isAvailable(i)) { return i; } + // OS-level check failed (port used by something outside the JVM). Release the claim so + // a later allocator can reconsider it once whatever is occupying it goes away. + JVM_WIDE_CLAIMED_PORTS.remove(i); } return Assertions.fail("Could not find available port between 1111 and 9999"); } @@ -310,30 +329,25 @@ public static boolean isAvailable(int port) { } public void stop() { - if ((serverProcess != null) && serverProcess.isAlive()) { - serverProcess.destroy(); + try { + if ((serverProcess != null) && serverProcess.isAlive()) { + serverProcess.destroy(); + } + } finally { + // Release JVM-wide port claims unconditionally so the ports become available to other + // ExternalServer instances even if the subprocess already exited on its own. The default + // int value (0) is never added to JVM_WIDE_CLAIMED_PORTS, so calling stop() before + // start() is harmless. + JVM_WIDE_CLAIMED_PORTS.remove(grpcPort); + JVM_WIDE_CLAIMED_PORTS.remove(httpPort); } } public static void startMultiple(@Nonnull Collection servers) throws Exception { - startMultiple(servers, new HashSet<>()); - } - - public static void startMultiple(@Nonnull Collection servers, @Nonnull Set unavailablePorts) throws Exception { - final Map allocatedPorts = new HashMap<>(); + // Port uniqueness within and across batches is guaranteed by JVM_WIDE_CLAIMED_PORTS — each + // server's start() atomically claims its grpc and http ports before binding. for (ExternalServer server : servers) { - server.start(unavailablePorts); - // Threading through unavailablePorts should be enough to make sure each port is unique. - // Double check both ports, though - checkPortIsUnique(server.getPort(), server, allocatedPorts); - checkPortIsUnique(server.getHttpPort(), server, allocatedPorts); - } - } - - private static void checkPortIsUnique(int port, @Nonnull ExternalServer server, @Nonnull Map allocatedPorts) throws RelationalException { - @Nullable ExternalServer preExistingServer = allocatedPorts.putIfAbsent(port, server); - if (preExistingServer != null) { - Assert.fail("allocated duplicate port (" + server.getPort() + ") to servers for versions " + server.getVersion() + " and " + preExistingServer.getVersion()); + server.start(); } } } diff --git a/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java b/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java index 183a5acbb5..49b408e1ac 100644 --- a/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java +++ b/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java @@ -30,9 +30,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -73,27 +71,6 @@ void startMultiple() throws Exception { } } - @Test - void startMultipleWithAdditionalExclusions() throws Exception { - final List servers = new ArrayList<>(); - final String clusterFile = FDBTestEnvironment.randomClusterFile(); - for (int i = 0; i < 3; i++) { - servers.add(new ExternalServer(currentServerPath, clusterFile)); - } - try { - final Set explicitExcluded = Set.of(1111, 1115, 1116); - ExternalServer.startMultiple(servers, new HashSet<>(explicitExcluded)); - assertDistinctPorts(servers); - assertThat(servers) - .flatMap(ExternalServer::getPort, ExternalServer::getHttpPort) - .doesNotContainAnyElementsOf(explicitExcluded); - } finally { - for (final ExternalServer server : servers) { - server.stop(); - } - } - } - private static void assertDistinctPorts(@Nonnull Collection servers) { // we can't assert about the actual values, because one of the ports may be busy, // so assert that each server has its own port, and none of them have the same port From 851e5bd7b7fc58888137f23b0818c9e6094f2377 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 11:32:11 -0400 Subject: [PATCH 15/74] Avoid constant Yaml classes SnakeYaml is not threadsafe, so reusing the same parser when we are running tests in parallel causes bugs --- .../yamltests/block/IncludeBlock.java | 22 +++++++++++++------ .../yamltests/command/QueryInterpreter.java | 17 ++++++++++++-- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java index 8e0f4a4d14..2db4cfa60b 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java @@ -53,14 +53,22 @@ public class IncludeBlock extends SupportBlock { public static final String INCLUDE = "include"; private static final Logger logger = LogManager.getLogger(IncludeBlock.class); - private static final Yaml YAML_ENGINE; - static { - LoaderOptions loaderOptions = new LoaderOptions(); + /** + * Builds a fresh {@link Yaml} instance for parsing an included resource. + *

+ * SnakeYAML's {@link Yaml} (and its underlying {@code Scanner}/{@code Parser}/{@code StreamReader}) + * is documented as not thread-safe. When yaml-tests run with class-level parallel execution + * enabled (see {@code gradle/testing.gradle}), multiple test classes call {@link #parse} concurrently, + * and a shared static engine corrupted into {@code ScannerException}/{@code ParserException}/ + * {@code ConcurrentModificationException}/{@code ClassCastException} on parser-internal state. + */ + @Nonnull + private static Yaml newYamlEngine() { + final LoaderOptions loaderOptions = new LoaderOptions(); loaderOptions.setAllowDuplicateKeys(true); - DumperOptions dumperOptions = new DumperOptions(); - - YAML_ENGINE = new Yaml(new CustomYamlConstructor(loaderOptions), new Representer(dumperOptions), + final DumperOptions dumperOptions = new DumperOptions(); + return new Yaml(new CustomYamlConstructor(loaderOptions), new Representer(dumperOptions), new DumperOptions(), loaderOptions, new Resolver()); } @@ -88,7 +96,7 @@ public static List parse(@Nonnull final YamlReference.YamlResource resour int blockNumber = 0; executionContext.registerResource(resource); try (var inputStream = getInputStream(resource)) { - final var docs = StreamSupport.stream(YAML_ENGINE.loadAll(inputStream).spliterator(), false).collect(Collectors.toList()); + final var docs = StreamSupport.stream(newYamlEngine().loadAll(inputStream).spliterator(), false).collect(Collectors.toList()); for (var doc : docs) { final var blocks = Block.parse(resource, doc, blockNumber, executionContext, resource.isTopLevel()); allBlocks.addAll(blocks); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java index f4fcddb7fe..f35cd6fe74 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java @@ -95,7 +95,20 @@ public final class QueryInterpreter { */ private final List> injections; - private static final Yaml INTERPRETER = new Yaml(new QueryParameterYamlConstructor(new LoaderOptions())); + /** + * Builds a fresh {@link Yaml} instance for parsing query parameter injections (the {@code !!…!!} + * snippets). + *

+ * SnakeYAML's {@link Yaml} is not thread-safe: its underlying {@code Scanner}/{@code Parser}/ + * {@code StreamReader} hold per-parse state. Tests run with class-level parallel execution (see + * {@code gradle/testing.gradle}), so a shared static instance would let two test classes corrupt + * each other's parse state — observed as {@code ScannerException}, {@code ParserException}, + * {@code ClassCastException} (MappingEndEvent → NodeEvent), {@code NoSuchElementException}, etc. + */ + @Nonnull + private static Yaml newInterpreter() { + return new Yaml(new QueryParameterYamlConstructor(new LoaderOptions())); + } private static final class QueryParameterYamlConstructor extends SafeConstructor { private static final Tag RANDOM_TAG = new Tag("!r"); @@ -301,7 +314,7 @@ private List> getInjections(@Nonnull String query) { int end = query.indexOf("!!", start + 2); Assert.thatUnchecked(end != -1, "Illegal format: Parameter injection not formed correctly in query " + query); cursor = end + 2; - lst.add(Pair.of(query.substring(start, end + 2), INTERPRETER.load(query.substring(start + 2, end)))); + lst.add(Pair.of(query.substring(start, end + 2), newInterpreter().load(query.substring(start + 2, end)))); } return lst; } From 833b96ab4e62c2e4169f27e2aa11008b0b2c9d81 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 16:07:35 -0400 Subject: [PATCH 16/74] Add mockito as a javaagent, sa recomended by the mockito docs TODO move this to testing.gradle so all modules take advantage --- fdb-record-layer-core/fdb-record-layer-core.gradle | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fdb-record-layer-core/fdb-record-layer-core.gradle b/fdb-record-layer-core/fdb-record-layer-core.gradle index fd7c088253..35510f81fc 100644 --- a/fdb-record-layer-core/fdb-record-layer-core.gradle +++ b/fdb-record-layer-core/fdb-record-layer-core.gradle @@ -24,6 +24,13 @@ plugins { apply from: rootProject.file('gradle/proto.gradle') apply from: rootProject.file('gradle/publishing.gradle') +configurations { + // Resolved to the mockito-core jar so it can be installed as a -javaagent on the test JVM. + // Required on JDK 21+ because Mockito's inline mock maker can no longer self-attach. + // See https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 + mockitoAgent +} + dependencies { annotationProcessor project(':fdb-java-annotations') api project(':fdb-extensions') @@ -47,6 +54,12 @@ dependencies { testImplementation(libs.snakeyaml) testAnnotationProcessor(libs.autoService) + + mockitoAgent(libs.mockito) { transitive = false } +} + +tasks.withType(Test).configureEach { + jvmArgs("-javaagent:${configurations.mockitoAgent.asPath}") } sourceSets { From 191f32f2ea4f2427cb03ab651d750fc986a15bf5 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 16:09:18 -0400 Subject: [PATCH 17/74] Set a multi-thread scheduled executor in FDBDatabaseExtension TODO do this for other modules that are creating using the default scheduled executor --- .../record/test/FDBDatabaseExtension.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/FDBDatabaseExtension.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/FDBDatabaseExtension.java index 144395abbc..a20fc4455b 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/FDBDatabaseExtension.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/FDBDatabaseExtension.java @@ -43,6 +43,8 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -84,6 +86,16 @@ public FDBDatabaseExtension() { @Nonnull private static final Executor threadPoolExecutor = TestExecutors.newThreadPool("fdb-record-layer-test"); + // Override the per-factory scheduled executor so parallel tests don't fight over the + // JVM-wide single-thread ScheduledThreadPoolExecutor that MoreAsyncUtil installs by + // default. When the suite runs N tests concurrently and they each schedule deadline + // timers on a single shared thread, the timers fire late and trigger spurious + // DeadlineExceededException from places like AsyncLoadingCache (e.g. + // FDBDatabase.resolverStateCache during KeySpacePath.toTuple cleanup). + @Nonnull + private static final ScheduledExecutorService scheduledThreadPoolExecutor = Executors.newScheduledThreadPool( + Math.max(Runtime.getRuntime().availableProcessors(), 4), + new TestExecutors.TestThreadFactory("fdb-record-layer-test-scheduled")); public static APIVersion getAPIVersion() { String apiVersionStr = System.getProperty(API_VERSION_PROPERTY); @@ -149,6 +161,7 @@ public FDBDatabaseFactory getDatabaseFactory() { } setupBlockingInAsyncDetection(databaseFactory); databaseFactory.setExecutor(threadPoolExecutor); + databaseFactory.setScheduledExecutor(scheduledThreadPoolExecutor); } return databaseFactory; } From ef7807f86050b91d68e779cc12c6a32ef22292a6 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 09:10:40 -0400 Subject: [PATCH 18/74] Speculative: Increase buffer for cached GRV In parallel runs, cachedVersionMaintenanceOnReadsTest fails because the previous version may be more than 2s ago --- .../foundationdb/FDBDatabaseTest.java | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java index 0b9cbfcb47..8f848c2d49 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java @@ -111,11 +111,11 @@ void cachedVersionMaintenanceOnReadsTest() throws Exception { RecordMetaData metaData = RecordMetaData.build(TestRecords1Proto.getDescriptor()); // First time, does a GRV from FDB - long readVersion1 = getReadVersion(database, 0L, 2_000L); + long readVersion1 = getReadVersion(database, 0L, 60_000L); // Store a record (advances future GRV, but not cached version) final KeySpacePath path = pathManager.createPath(TestKeySpace.RECORD_STORE); - final FDBDatabase.WeakReadSemantics weakReadSemantics = new FDBDatabase.WeakReadSemantics(0L, 2_000L, false); + final FDBDatabase.WeakReadSemantics weakReadSemantics = new FDBDatabase.WeakReadSemantics(0L, 60_000L, false); try (FDBRecordContext context = database.openContext(FDBRecordContextConfig.newBuilder().setWeakReadSemantics(weakReadSemantics).setMdcContext(MDC.getCopyOfContextMap()).build())) { assertEquals(readVersion1, context.getReadVersion()); FDBRecordStore recordStore = FDBRecordStore.newBuilder() @@ -129,8 +129,8 @@ void cachedVersionMaintenanceOnReadsTest() throws Exception { context.commit(); } - // We're fine with any version obtained up to 2s ago, so will get the original readVersion - assertEquals(readVersion1, getReadVersion(database, 0L, 2000L)); + // We're fine with any version obtained up to 60s ago, so will get the original readVersion + assertEquals(readVersion1, getReadVersion(database, 0L, 60_000L)); Thread.sleep(10L); @@ -138,19 +138,32 @@ void cachedVersionMaintenanceOnReadsTest() throws Exception { long readVersion2 = getReadVersion(database, 0L, 11L); assertThat(readVersion1, lessThan(readVersion2)); - // Store another record - testStoreAndRetrieveSimpleRecord(database, metaData, path); + // Store another record. Disable read-tracking for the duration so the fresh GRV + // done inside testStoreAndRetrieveSimpleRecord (which uses database.run(null, ...) + // and therefore bypasses weak-read-semantics) doesn't repopulate the cached + // version with a newer value than readVersion2 and break the assertions below. + database.setTrackLastSeenVersionOnRead(false); + try { + testStoreAndRetrieveSimpleRecord(database, metaData, path); + } finally { + database.setTrackLastSeenVersionOnRead(true); + } - assertEquals(readVersion2, getReadVersion(database, 0L, 2000L)); - assertEquals(readVersion2, getReadVersion(database, readVersion2, 2000L)); + assertEquals(readVersion2, getReadVersion(database, 0L, 60_000L)); + assertEquals(readVersion2, getReadVersion(database, readVersion2, 60_000L)); // Now we want at least readVersion2 + 1, so this will cause a GRV - long readVersion3 = getReadVersion(database, readVersion2 + 1, 2000L); + long readVersion3 = getReadVersion(database, readVersion2 + 1, 60_000L); assertTrue(readVersion2 < readVersion3); - // Store another record - testStoreAndRetrieveSimpleRecord(database, metaData, path); + // Store another record (same read-tracking-disable shield as above) + database.setTrackLastSeenVersionOnRead(false); + try { + testStoreAndRetrieveSimpleRecord(database, metaData, path); + } finally { + database.setTrackLastSeenVersionOnRead(true); + } // Don't use a stored version assertTrue(readVersion3 < getReadVersion(database, null, null)); From 6b0cc77bff8b7677f9e9b2961343b4de9108cd95 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 09:12:24 -0400 Subject: [PATCH 19/74] Add resource lock for indexing tests Since indexing tests do a fair amount of constant work (building indexes), and do their work at batch priority, they are more susceptible to load on the system due to concurrent transactions. Give all of them a ResourceLock so that we don't have multiple tests of this style running concurrently. --- .../provider/foundationdb/OnlineIndexerMergeTest.java | 5 +++++ .../record/provider/foundationdb/OnlineIndexerTest.java | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerMergeTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerMergeTest.java index 188c030fe2..adc193b768 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerMergeTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerMergeTest.java @@ -53,6 +53,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -80,6 +82,9 @@ * Tests specifically of {@link OnlineIndexer#mergeIndex()}. */ @Tag(Tags.RequiresFDB) +// Matches OnlineIndexerTest's @ResourceLock so this class serialises with the rest of the +// OnlineIndexer* test classes, avoiding FDB batch-GRV throttle when run in parallel. +@ResourceLock(value = "ONLINE_INDEXER_BATCH_GRV", mode = ResourceAccessMode.READ_WRITE) public class OnlineIndexerMergeTest extends FDBRecordStoreConcurrentTestBase { private static final String INDEX_NAME = "mergableIndex"; diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerTest.java index c7a0ed0ef8..faf75ce645 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerTest.java @@ -43,6 +43,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -62,6 +64,13 @@ * Tests for {@link OnlineIndexer}. */ @Tag(Tags.RequiresFDB) +// Online indexer operations use batch-priority GRV (OnlineIndexOperationBaseBuilder +// defaults to FDBTransactionPriority.BATCH). When many indexer test classes run in +// parallel they overrun the FDB cluster's batch-GRV rate limit, causing flaky +// "Batch GRV request rate limit exceeded" failures. This @ResourceLock is inherited +// by all 15 subclasses of OnlineIndexerTest, serialising indexer tests with each +// other while still allowing them to run in parallel with non-indexer tests. +@ResourceLock(value = "ONLINE_INDEXER_BATCH_GRV", mode = ResourceAccessMode.READ_WRITE) public abstract class OnlineIndexerTest { @RegisterExtension final FDBDatabaseExtension dbExtension = new FDBDatabaseExtension(); From 0f326cdb1e4e93b848d572e2da62ed1f0a586a79 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 09:14:03 -0400 Subject: [PATCH 20/74] Isolate vector tests due to batch GRV limiting TODO check if we can change this to the same resource lock as the indexing tests instead --- .../provider/foundationdb/indexes/VectorIndexIndexingTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexIndexingTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexIndexingTest.java index a57a861403..837ff78b0d 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexIndexingTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexIndexingTest.java @@ -34,6 +34,7 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Message; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; import java.util.List; @@ -49,6 +50,8 @@ * Tests online indexing of vector indexes. * These tests verify that vector indexes can be built asynchronously after records are saved. */ +// TODO maybe change this to the same resource lock as the indexing tests +@Isolated("Heavy vector index-builder trips FDB cluster's batch-priority GRV rate limit when run concurrently with other indexer tests") class VectorIndexIndexingTest extends VectorIndexTestBase { @Test From c5872b374f9eed8d076c67c3d088c6948b5fb279 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 09:15:11 -0400 Subject: [PATCH 21/74] Protect against DeadlineExceededException resolving paths --- .../record/test/TestKeySpacePathManager.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java index 7f953ee8da..991a6b9cfc 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java @@ -25,6 +25,7 @@ import com.apple.foundationdb.record.logging.LogMessageKeys; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseRunner; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContextConfig; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; import org.slf4j.Logger; @@ -89,6 +90,29 @@ public KeySpacePath createPath(String... pathElements) { LogMessageKeys.KEY_SPACE_PATH, path)); } assertTrue(paths.add(path), "UUID collision"); + + // Eagerly resolve the path under a JVM-wide lock. Path resolution goes through + // FDBDatabase.resolverStateCache, an AsyncLoadingCache with a hardcoded 5-second + // deadline (AsyncLoadingCache.DEFAULT_DEADLINE_TIME_MILLIS). When many parallel + // tests resolve directory-layer entries concurrently, the FDB-side work appears + // to contend with itself (retries triggered by conflicting commits on the + // resolver state) and the 5-second deadline fires from random call sites in + // test bodies and afterEach hooks. Serialising the resolution here means the + // resolver-state cache is warm by the time the test actually uses the path, + // so KeySpacePath.toTuple / FDBRecordStore.createOrOpen / deleteAllData calls + // hit the cache instead of racing on directory-layer reads. + // + // TODO: investigate whether the directory layer / LocatableResolver has a bug + // where concurrent resolutions of distinct keys fail to make progress on + // retry (rather than just being slow). If yes, the production-side fix would + // remove the need for this workaround entirely. See e.g. + // FDBDatabase.resolverStateCache, LocatableResolver.loadResolverState. + synchronized (TestKeySpacePathManager.class) { + try (FDBRecordContext context = db.openContext()) { + path.toTuple(context); + } + } + return path; } From 1f39279306cf0090f930b630e12fc5f5b868ecbd Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 13:46:34 -0400 Subject: [PATCH 22/74] Move createPath eariler in test to avoid affecting GRV cache Since createPath now evaluates the path, it can bump the cached read version, which breaks the expectation of the test TODO can we decrease the 60s back to 2s like it was before --- .../record/provider/foundationdb/FDBDatabaseTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java index 8f848c2d49..d1b25aaa8d 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java @@ -110,11 +110,18 @@ void cachedVersionMaintenanceOnReadsTest() throws Exception { RecordMetaData metaData = RecordMetaData.build(TestRecords1Proto.getDescriptor()); + // Create the path up-front so that the directory-layer resolution done by + // TestKeySpacePathManager.createPath has already warmed FDBDatabase.lastSeenFDBVersion + // by the time we capture readVersion1 below. Otherwise the cached version captured + // here would be stale relative to the one createPath puts in the cache, and the + // weak-read-semantics assertion at "assertEquals(readVersion1, context.getReadVersion())" + // would return the newer (post-createPath) cached version and fail. + final KeySpacePath path = pathManager.createPath(TestKeySpace.RECORD_STORE); + // First time, does a GRV from FDB long readVersion1 = getReadVersion(database, 0L, 60_000L); // Store a record (advances future GRV, but not cached version) - final KeySpacePath path = pathManager.createPath(TestKeySpace.RECORD_STORE); final FDBDatabase.WeakReadSemantics weakReadSemantics = new FDBDatabase.WeakReadSemantics(0L, 60_000L, false); try (FDBRecordContext context = database.openContext(FDBRecordContextConfig.newBuilder().setWeakReadSemantics(weakReadSemantics).setMdcContext(MDC.getCopyOfContextMap()).build())) { assertEquals(readVersion1, context.getReadVersion()); From 087a1e2140c1511808247263a7891877fbdc07cb Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 14:11:15 -0400 Subject: [PATCH 23/74] Mark a couple more tests as Isolated due to batch GRV --- .../provider/foundationdb/OnlineIndexScrubberTest.java | 7 +++++++ .../OnlineIndexerBuildGroupedCountIndexTest.java | 6 ++++++ .../provider/foundationdb/SynchronizedSessionTest.java | 3 +++ 3 files changed, 16 insertions(+) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexScrubberTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexScrubberTest.java index 644f21f68f..475217a591 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexScrubberTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexScrubberTest.java @@ -40,6 +40,7 @@ import com.apple.foundationdb.tuple.Tuple; import com.google.protobuf.Message; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; @@ -63,6 +64,12 @@ /** * Tests for scrubbing readable indexes with {@link OnlineIndexer}. */ +// Scrubber tests issue many batch-priority GRVs per method and still trip the +// FDB cluster's batch-GRV rate limit even when serialised with other indexer +// tests via the inherited @ResourceLock from OnlineIndexerTest. @Isolated +// escalates to full cross-class isolation so no other tests compete for FDB +// resources while the scrubber runs. +@Isolated("Scrubber tests inherently issue many batch-priority GRVs and trip FDB's batch-GRV rate limit when sharing the cluster with other parallel tests") class OnlineIndexScrubberTest extends OnlineIndexerTest { private FDBRecordStoreTestBase.RecordMetaDataHook myHook(Index index) { diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerBuildGroupedCountIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerBuildGroupedCountIndexTest.java index 4afb2f5f5d..93f28394c4 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerBuildGroupedCountIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerBuildGroupedCountIndexTest.java @@ -36,6 +36,7 @@ import com.google.common.collect.Maps; import com.google.protobuf.Message; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -61,6 +62,11 @@ /** * Tests for building a grouped count index. */ +// Parameterized invocations of buildWhileUpdatingGroupedCount issue many batch-priority +// GRVs and trip the FDB cluster's batch-GRV rate limit even when the class is serialised +// with other indexer tests via the inherited @ResourceLock from OnlineIndexerTest. +// Escalate to full cross-class isolation, matching OnlineIndexScrubberTest. +@Isolated("Grouped-count indexer tests issue many batch-priority GRVs and trip FDB's batch-GRV rate limit when sharing the cluster with other parallel tests") public class OnlineIndexerBuildGroupedCountIndexTest extends OnlineIndexerTest { private static final KeyExpression PRIMARY_KEY = concatenateFields("num_value_2", "rec_no"); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/SynchronizedSessionTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/SynchronizedSessionTest.java index ee42087d61..167afefc6a 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/SynchronizedSessionTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/SynchronizedSessionTest.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -352,6 +353,7 @@ private void waitLongEnough() throws InterruptedException { /** * Run {@link SynchronizedSessionRunner} synchronously. */ + @Isolated("Lease-renewal tests Thread.sleep then assert the lease was renewed; under parallel CPU starvation the renewing thread misses its window and the assertion fails") public static class Run extends SynchronizedSessionTest { Run() { super(false); @@ -361,6 +363,7 @@ public static class Run extends SynchronizedSessionTest { /** * Run {@link SynchronizedSessionRunner} asynchronously. */ + @Isolated("Lease-renewal tests Thread.sleep then assert the lease was renewed; under parallel CPU starvation the renewing thread misses its window and the assertion fails") public static class RunAsync extends SynchronizedSessionTest { RunAsync() { super(true); From 4835db0ab8c7cda02cbc7602a7959c6a08c1d644 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 14:14:12 -0400 Subject: [PATCH 24/74] Add retries to TestKeySpacePathManager This still runs into DeadlineExceededException, more investigation is probably needed, but this gets the tests to pass better. --- .../record/test/TestKeySpacePathManager.java | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java index 991a6b9cfc..8a9815a4e5 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java @@ -20,12 +20,13 @@ package com.apple.foundationdb.record.test; +import com.apple.foundationdb.async.MoreAsyncUtil; import com.apple.foundationdb.record.RecordCoreException; +import com.apple.foundationdb.record.RecordCoreRetriableTransactionException; import com.apple.foundationdb.record.logging.KeyValueLogMessage; import com.apple.foundationdb.record.logging.LogMessageKeys; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseRunner; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContextConfig; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; import org.slf4j.Logger; @@ -102,14 +103,35 @@ public KeySpacePath createPath(String... pathElements) { // so KeySpacePath.toTuple / FDBRecordStore.createOrOpen / deleteAllData calls // hit the cache instead of racing on directory-layer reads. // + // Use db.newRunner so we get the runner's standard retry loop for free. + // DeadlineExceededException extends LoggableException (not FDBException nor + // RecordCoreRetriableTransactionException) so the runner doesn't consider it + // retriable by default; wrap it explicitly to opt in. + // // TODO: investigate whether the directory layer / LocatableResolver has a bug // where concurrent resolutions of distinct keys fail to make progress on // retry (rather than just being slow). If yes, the production-side fix would // remove the need for this workaround entirely. See e.g. // FDBDatabase.resolverStateCache, LocatableResolver.loadResolverState. synchronized (TestKeySpacePathManager.class) { - try (FDBRecordContext context = db.openContext()) { - path.toTuple(context); + final FDBRecordContextConfig.Builder config = FDBRecordContextConfig.newBuilder() + .setTransactionId("pathManager_createPath_" + UUID.randomUUID()) + .setLogTransaction(true) + .setMdcContext(MDC.getCopyOfContextMap()); + try (FDBDatabaseRunner runner = db.newRunner(config)) { + final KeySpacePath finalPath = path; + runner.run(context -> { + try { + finalPath.toTuple(context); + } catch (MoreAsyncUtil.DeadlineExceededException e) { + // The 5-second AsyncLoadingCache deadline occasionally fires + // under heavy parallel test load even though the work would + // succeed on retry. Wrap so the runner's retry loop picks it up. + throw new RecordCoreRetriableTransactionException( + "deadline exceeded resolving test key space path", e); + } + return null; + }); } } @@ -129,12 +151,22 @@ public void close() { .setMdcContext(MDC.getCopyOfContextMap()); try (FDBDatabaseRunner runner = db.newRunner(config)) { runner.run(context -> { - for (KeySpacePath path : paths) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(KeyValueLogMessage.of("deleting test key space path", - LogMessageKeys.KEY_SPACE_PATH, path.toString(path.toTuple(context)))); + try { + for (KeySpacePath path : paths) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(KeyValueLogMessage.of("deleting test key space path", + LogMessageKeys.KEY_SPACE_PATH, path.toString(path.toTuple(context)))); + } + path.deleteAllData(context); } - path.deleteAllData(context); + } catch (MoreAsyncUtil.DeadlineExceededException e) { + // Same reason as in createPath: the 5-second AsyncLoadingCache + // deadline on FDBDatabase.resolverStateCache occasionally fires + // under heavy parallel test load. Wrap as a retriable exception + // so the runner's retry loop picks it up instead of failing the + // afterEach cleanup. + throw new RecordCoreRetriableTransactionException( + "deadline exceeded resolving test key space path during cleanup", e); } return null; }); From e16d38ebf9352b4c91668f32c320e66a475a4d27 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 11:05:49 -0400 Subject: [PATCH 25/74] Enable parallel execution in junit with worker_thread_pool JUnit 6.1 introduced a new executor service to be used for parallel testing that does not use a ForkJoinPool. The issue with the ForkJoinPool, was that tasks waiting to join futures do not count towards the concurrency. This means that as soon as a test waited on a future, another test would start, causing more load than FDB could handle. The new service does not suffer from this issue. --- gradle/testing.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle/testing.gradle b/gradle/testing.gradle index 193b62a32e..321cea4285 100644 --- a/gradle/testing.gradle +++ b/gradle/testing.gradle @@ -357,9 +357,13 @@ tasks.withType(Test).configureEach { task -> task.testFramework.options.excludeTags.add('Performance') if (task.name == 'test') { - task.systemProperty('junit.jupiter.execution.parallel.enabled', 'false') + task.systemProperty('junit.jupiter.execution.parallel.enabled', 'true') task.systemProperty('junit.jupiter.execution.parallel.mode.default', 'same_thread') task.systemProperty('junit.jupiter.execution.parallel.mode.classes.default', 'concurrent') + // We use worker_thread_pool rather than fork_join_pool because we heavily depend on ForkJoinPool for + // FDB operations, which causes junit to incorrectly increase the number of concurrent tests to well + // beyond what we want. + task.systemProperty('junit.jupiter.execution.parallel.config.executor-service', 'worker_thread_pool') } } From a25ffd8230d8a3554ab43dcb838f399e122a9803 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 12:11:00 -0400 Subject: [PATCH 26/74] Make CommandsTest pass when run in parallel --- .../plan/cascades/debug/CommandsTest.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java b/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java index e7b71d40a0..0d6b8f0651 100644 --- a/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java +++ b/fdb-record-layer-debugger/src/test/java/com/apple/foundationdb/record/query/plan/cascades/debug/CommandsTest.java @@ -61,6 +61,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import javax.annotation.Nonnull; import java.io.ByteArrayOutputStream; @@ -77,7 +79,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; - +// Tests set up shared state (the debugger) on a ThreadLocal via Debugger.setDebugger in @BeforeEach. +// When JUnit's parallel execution is enabled, lifecycle methods and the test body can run on +// different worker threads, leaving the test body without a debugger installed. Pin everything in +// this class to a single thread so the thread-local survives between @BeforeEach and @Test. +@Execution(ExecutionMode.SAME_THREAD) class CommandsTest { private String query; private PipedOutputStream outIn; @@ -307,7 +313,7 @@ void testCountingTautologyBreakPoint() throws IOException { terminal.writer().close(); assertThat(outputStream.toString()).contains( - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "insert_into_memo"), @@ -344,7 +350,7 @@ void testOnEventTypeBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("location", "end") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "initphase"), @@ -367,7 +373,7 @@ void testOnPhaseBreakPoint() throws IOException { terminal.writer().close(); assertThat(outputStream.toString()).contains( - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "2"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "initphase"), @@ -407,7 +413,7 @@ void testOnRuleBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("count down", "-1"), ReplTestUtil.coloredKeyValue("ruleNamePrefix", "ImplementSimpleSelectRule") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "transform"), @@ -465,7 +471,7 @@ ref, exp, nonMatchingRule, new CascadesRuleCall(PlannerPhase.PLANNING, PlanConte ReplTestUtil.coloredKeyValue("count down", "-1"), ReplTestUtil.coloredKeyValue("ruleNamePrefix", "ImplementSimpleSelectRule") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "3"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), @@ -518,7 +524,7 @@ void testOnYieldExpressionBreakPoint() throws IOException { ReplTestUtil.coloredKeyValue("location", "end"), ReplTestUtil.coloredKeyValue("expression", "exp1") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "4"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "4"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), @@ -586,7 +592,7 @@ public Set getMatchCandidates() { ReplTestUtil.coloredKeyValue("location", "end"), ReplTestUtil.coloredKeyValue("candidate", "idx1") ), - ReplTestUtil.coloredKeyValue("paused in", "Test worker at") + " " + ReplTestUtil.coloredKeyValue("tick", "1"), + ReplTestUtil.coloredKeyValue("paused in", Thread.currentThread().getName() + " at") + " " + ReplTestUtil.coloredKeyValue("tick", "1"), String.join( "; ", ReplTestUtil.coloredKeyValue("shorthand", "rulecall"), From f91af19264e10583394dadc28ae9192b9b7ccef8 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 24 Jun 2026 16:16:24 -0400 Subject: [PATCH 27/74] Synchronize part of FRL initialization This protects multiple instances in the same JVM from trying to initialize the keyspace/domain concurrently, which could conflict. This shouldn't be a real problem in production environments, but is low cost, so adding it in support of concurrent tests provides real value. There are probably additional issues that need to be resolved around multiple FRLs registering a driver in the same instance... And it may make sense to have some sort of registry so that we can better control for this, and reduce the number of tests depending on the registered driver. --- .../foundationdb/relational/server/FRL.java | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index bc07f7b266..eb8e533f96 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -78,6 +78,15 @@ // Needs to be public so can be used by sub-packages; i.e. the JDBCService @API(API.Status.EXPERIMENTAL) public class FRL implements AutoCloseable { + /** + * JVM-wide lock guarding the parts of construction that mutate shared static state + * ({@link com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider#registerDomainIfNotExists}) + * and write the catalog bootstrap to FDB. Without this, concurrent {@code new FRL(...)} calls (e.g. from + * parallel test {@code @BeforeAll} hooks) race on the catalog-init transaction commit and fail with + * "Transaction not committed due to conflict with another transaction". + */ + private static final Object INIT_LOCK = new Object(); + private final FdbConnection fdbDatabase; private final RelationalDriver driver; private boolean registeredJDBCEmbedDriver; @@ -102,13 +111,19 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } this.fdbDatabase = new DirectFdbConnection(fdbDb, NoOpMetricRegistry.INSTANCE); - final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); - keyspaceProvider.registerDomainIfNotExists("FRL"); - KeySpace keySpace = keyspaceProvider.getKeySpace(); - StoreCatalog storeCatalog; - try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); + // Serialize the rest of construction: registerDomainIfNotExists mutates the shared keyspace, and + // the catalog-init transaction below races on commit when multiple FRLs are constructed concurrently + // against the same cluster (typical in parallel tests). + final StoreCatalog storeCatalog; + final KeySpace keySpace; + synchronized (INIT_LOCK) { + final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); + keyspaceProvider.registerDomainIfNotExists("FRL"); + keySpace = keyspaceProvider.getKeySpace(); + try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { + storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + } } RecordLayerConfig rlConfig = RecordLayerConfig.getDefault(); From 20003c8453ac653a2f476b638bfe399372bd4a35 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 10:10:14 -0400 Subject: [PATCH 28/74] Claim used ports for ExternalServer across entire JVM instance This allows for running multiple tests in parallel within the same JVM, without risk of having multiple threads trying to start multiple servers against the same port. This replaces the previous per-ExternalServer approach to protecting port conflicts. This does not protect as well for concurrent ExternalServers in different processes, but currently we don't run concurrently in multiple JVMs, so this should be sufficient. --- .../yamltests/server/ExternalServer.java | 186 ++++++++++-------- .../yamltests/server/ExternalServerTest.java | 23 --- 2 files changed, 100 insertions(+), 109 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java index 562c29e9f3..8f533ca8eb 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java @@ -22,8 +22,6 @@ import com.apple.foundationdb.record.logging.KeyValueLogMessage; import com.apple.foundationdb.record.logging.LogMessageKeys; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; -import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.BuildVersion; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.google.common.base.Verify; @@ -42,12 +40,10 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -60,6 +56,16 @@ public class ExternalServer implements Clusters.BoundToCluster { private static final Logger logger = LogManager.getLogger(ExternalServer.class); public static final String EXTERNAL_SERVER_PROPERTY_NAME = "yaml_testing_external_server"; + /** + * JVM-wide set of ports currently reserved by any {@link ExternalServer} instance. Closes the race + * where two concurrent {@link #startMultiple(Collection)} calls (e.g. from parallel test classes' + * {@code @BeforeAll}) each see the same port as available — this would otherwise let two test + * classes both try to bind the same port (e.g. 1111). The atomic {@link Set#add(Object)} on a + * {@link ConcurrentHashMap#newKeySet()} acts as a CAS: the winner returns {@code true} and owns + * the port until {@link #stop()} releases it. + */ + private static final Set JVM_WIDE_CLAIMED_PORTS = ConcurrentHashMap.newKeySet(); + @Nonnull private final File serverJar; private int grpcPort; @@ -135,60 +141,73 @@ public String clusterFile() { return clusterFile; } - public void start(Set unavailablePorts) throws Exception { - grpcPort = getAvailablePort(unavailablePorts); - httpPort = getAvailablePort(unavailablePorts); - ProcessBuilder processBuilder = new ProcessBuilder("java", - // TODO add support for debugging by adding, but need to take care with ports - // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n", - "-jar", serverJar.getAbsolutePath(), - "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); - boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); - @Nullable - File outFile; - @Nullable - File errFile; - if (saveServerLogs) { - // Include current time in file names so that things sort nicely. We want the out and err logs - // for the same server start to be adjacent, and it would be nice for the log files to sort - // chronologically - long currentTime = System.currentTimeMillis(); - outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); - errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); - } else { - outFile = null; - errFile = null; - } - ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); - ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); - processBuilder.redirectOutput(out); - processBuilder.redirectError(err); - if (clusterFile != null) { - processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); - } + public void start() throws Exception { + grpcPort = getAvailablePort(); + boolean success = false; + try { + httpPort = getAvailablePort(); + ProcessBuilder processBuilder = new ProcessBuilder("java", + // TODO add support for debugging by adding, but need to take care with ports + // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n", + "-jar", serverJar.getAbsolutePath(), + "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); + boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); + @Nullable + File outFile; + @Nullable + File errFile; + if (saveServerLogs) { + // Include current time in file names so that things sort nicely. We want the out and err logs + // for the same server start to be adjacent, and it would be nice for the log files to sort + // chronologically + long currentTime = System.currentTimeMillis(); + outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); + errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); + } else { + outFile = null; + errFile = null; + } + ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); + ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); + processBuilder.redirectOutput(out); + processBuilder.redirectError(err); + if (clusterFile != null) { + processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); + } + + if (!startServer(processBuilder)) { + if (logger.isWarnEnabled()) { + logger.warn(KeyValueLogMessage.of("Failed to start external server", + "jar", serverJar, + LogMessageKeys.VERSION, version, + "grpc_port", grpcPort, + "http_port", httpPort, + "out_file", outFile, + "err_file", errFile)); + } + Assertions.fail("Failed to start the external server"); + } - if (!startServer(processBuilder)) { - if (logger.isWarnEnabled()) { - logger.warn(KeyValueLogMessage.of("Failed to start external server", + if (logger.isInfoEnabled()) { + logger.info(KeyValueLogMessage.of("Started external server", "jar", serverJar, LogMessageKeys.VERSION, version, "grpc_port", grpcPort, "http_port", httpPort, "out_file", outFile, - "err_file", errFile)); + "err_file", errFile + )); + } + success = true; + } finally { + // If we claimed ports but the server failed to come up (Assertions.fail, IOException + // from process.start, etc.), release the JVM-wide claims so the ports become available + // to future allocators. stop() will not be called for a failed start, so this is the + // only cleanup hook. + if (!success) { + JVM_WIDE_CLAIMED_PORTS.remove(grpcPort); + JVM_WIDE_CLAIMED_PORTS.remove(httpPort); } - Assertions.fail("Failed to start the external server"); - } - - if (logger.isInfoEnabled()) { - logger.info(KeyValueLogMessage.of("Started external server", - "jar", serverJar, - LogMessageKeys.VERSION, version, - "grpc_port", grpcPort, - "http_port", httpPort, - "out_file", outFile, - "err_file", errFile - )); } } @@ -276,26 +295,26 @@ public void validateConnectionVersion(@Nonnull Connection connection) throws SQL } /** - * Get a port that is currently available for the server. - * @param unavailablePorts a set of ports that are known to be unavailable. This is useful for two reasons: - * (1) when starting multiple servers, we include all previously allocated ports to avoid starting multiple - * servers on the same ports, and (2) each server needs two ports, one for GRPC, and one for HTTP, so the - * GRPC port can be noted as unavailable when asking for the http port. If nothing is unavailable, use an empty set. - * The provided set must be mutable, as it will be updated during run as ports are allocated - * @return a port that is not currently in use on the system. + * Get a port that is currently available for the server and atomically claim it in + * {@link #JVM_WIDE_CLAIMED_PORTS} so no other {@link ExternalServer} instance — in this batch + * or any concurrent batch in another test class — will pick it. The claim is released by + * {@link #stop()} or by {@link #start()} on a failed-startup path. + * @return a port that is not currently in use on the system and not claimed by another ExternalServer. */ - private int getAvailablePort(@Nonnull final Set unavailablePorts) { + private int getAvailablePort() { // running locally on my laptop, testing if a port is available takes 0 milliseconds, so no need to optimize for (int i = 1111; i < 9999; i++) { - // Add the port immediately to the set of unavailable ports. We do this because there are - // three possibilities: (1) it has already been allocated and so add returns false, (2) it - // is determined to be unavailable by isAvailable, or (3) we allocate it to this server. - // In any case, we don't want to consider this port again. Checking the set before calling - // isAvailable() also ensures that we never need to call isAvailable() more than once for any - // port - if (unavailablePorts.add(i) && isAvailable(i)) { + // Atomically claim across the whole JVM. If another ExternalServer already grabbed this + // port, add() returns false and we keep scanning. + if (!JVM_WIDE_CLAIMED_PORTS.add(i)) { + continue; + } + if (isAvailable(i)) { return i; } + // OS-level check failed (port used by something outside the JVM). Release the claim so + // a later allocator can reconsider it once whatever is occupying it goes away. + JVM_WIDE_CLAIMED_PORTS.remove(i); } return Assertions.fail("Could not find available port between 1111 and 9999"); } @@ -310,30 +329,25 @@ public static boolean isAvailable(int port) { } public void stop() { - if ((serverProcess != null) && serverProcess.isAlive()) { - serverProcess.destroy(); + try { + if ((serverProcess != null) && serverProcess.isAlive()) { + serverProcess.destroy(); + } + } finally { + // Release JVM-wide port claims unconditionally so the ports become available to other + // ExternalServer instances even if the subprocess already exited on its own. The default + // int value (0) is never added to JVM_WIDE_CLAIMED_PORTS, so calling stop() before + // start() is harmless. + JVM_WIDE_CLAIMED_PORTS.remove(grpcPort); + JVM_WIDE_CLAIMED_PORTS.remove(httpPort); } } public static void startMultiple(@Nonnull Collection servers) throws Exception { - startMultiple(servers, new HashSet<>()); - } - - public static void startMultiple(@Nonnull Collection servers, @Nonnull Set unavailablePorts) throws Exception { - final Map allocatedPorts = new HashMap<>(); + // Port uniqueness within and across batches is guaranteed by JVM_WIDE_CLAIMED_PORTS — each + // server's start() atomically claims its grpc and http ports before binding. for (ExternalServer server : servers) { - server.start(unavailablePorts); - // Threading through unavailablePorts should be enough to make sure each port is unique. - // Double check both ports, though - checkPortIsUnique(server.getPort(), server, allocatedPorts); - checkPortIsUnique(server.getHttpPort(), server, allocatedPorts); - } - } - - private static void checkPortIsUnique(int port, @Nonnull ExternalServer server, @Nonnull Map allocatedPorts) throws RelationalException { - @Nullable ExternalServer preExistingServer = allocatedPorts.putIfAbsent(port, server); - if (preExistingServer != null) { - Assert.fail("allocated duplicate port (" + server.getPort() + ") to servers for versions " + server.getVersion() + " and " + preExistingServer.getVersion()); + server.start(); } } } diff --git a/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java b/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java index 183a5acbb5..49b408e1ac 100644 --- a/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java +++ b/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java @@ -30,9 +30,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -73,27 +71,6 @@ void startMultiple() throws Exception { } } - @Test - void startMultipleWithAdditionalExclusions() throws Exception { - final List servers = new ArrayList<>(); - final String clusterFile = FDBTestEnvironment.randomClusterFile(); - for (int i = 0; i < 3; i++) { - servers.add(new ExternalServer(currentServerPath, clusterFile)); - } - try { - final Set explicitExcluded = Set.of(1111, 1115, 1116); - ExternalServer.startMultiple(servers, new HashSet<>(explicitExcluded)); - assertDistinctPorts(servers); - assertThat(servers) - .flatMap(ExternalServer::getPort, ExternalServer::getHttpPort) - .doesNotContainAnyElementsOf(explicitExcluded); - } finally { - for (final ExternalServer server : servers) { - server.stop(); - } - } - } - private static void assertDistinctPorts(@Nonnull Collection servers) { // we can't assert about the actual values, because one of the ports may be busy, // so assert that each server has its own port, and none of them have the same port From 3ccad8b47303a6c130edbe0368a51a300e316f09 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 11:32:11 -0400 Subject: [PATCH 29/74] Avoid constant Yaml classes SnakeYaml is not threadsafe, so reusing the same parser when we are running tests in parallel causes bugs --- .../yamltests/block/IncludeBlock.java | 22 +++++++++++++------ .../yamltests/command/QueryInterpreter.java | 17 ++++++++++++-- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java index 8e0f4a4d14..2db4cfa60b 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java @@ -53,14 +53,22 @@ public class IncludeBlock extends SupportBlock { public static final String INCLUDE = "include"; private static final Logger logger = LogManager.getLogger(IncludeBlock.class); - private static final Yaml YAML_ENGINE; - static { - LoaderOptions loaderOptions = new LoaderOptions(); + /** + * Builds a fresh {@link Yaml} instance for parsing an included resource. + *

+ * SnakeYAML's {@link Yaml} (and its underlying {@code Scanner}/{@code Parser}/{@code StreamReader}) + * is documented as not thread-safe. When yaml-tests run with class-level parallel execution + * enabled (see {@code gradle/testing.gradle}), multiple test classes call {@link #parse} concurrently, + * and a shared static engine corrupted into {@code ScannerException}/{@code ParserException}/ + * {@code ConcurrentModificationException}/{@code ClassCastException} on parser-internal state. + */ + @Nonnull + private static Yaml newYamlEngine() { + final LoaderOptions loaderOptions = new LoaderOptions(); loaderOptions.setAllowDuplicateKeys(true); - DumperOptions dumperOptions = new DumperOptions(); - - YAML_ENGINE = new Yaml(new CustomYamlConstructor(loaderOptions), new Representer(dumperOptions), + final DumperOptions dumperOptions = new DumperOptions(); + return new Yaml(new CustomYamlConstructor(loaderOptions), new Representer(dumperOptions), new DumperOptions(), loaderOptions, new Resolver()); } @@ -88,7 +96,7 @@ public static List parse(@Nonnull final YamlReference.YamlResource resour int blockNumber = 0; executionContext.registerResource(resource); try (var inputStream = getInputStream(resource)) { - final var docs = StreamSupport.stream(YAML_ENGINE.loadAll(inputStream).spliterator(), false).collect(Collectors.toList()); + final var docs = StreamSupport.stream(newYamlEngine().loadAll(inputStream).spliterator(), false).collect(Collectors.toList()); for (var doc : docs) { final var blocks = Block.parse(resource, doc, blockNumber, executionContext, resource.isTopLevel()); allBlocks.addAll(blocks); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java index f4fcddb7fe..f35cd6fe74 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java @@ -95,7 +95,20 @@ public final class QueryInterpreter { */ private final List> injections; - private static final Yaml INTERPRETER = new Yaml(new QueryParameterYamlConstructor(new LoaderOptions())); + /** + * Builds a fresh {@link Yaml} instance for parsing query parameter injections (the {@code !!…!!} + * snippets). + *

+ * SnakeYAML's {@link Yaml} is not thread-safe: its underlying {@code Scanner}/{@code Parser}/ + * {@code StreamReader} hold per-parse state. Tests run with class-level parallel execution (see + * {@code gradle/testing.gradle}), so a shared static instance would let two test classes corrupt + * each other's parse state — observed as {@code ScannerException}, {@code ParserException}, + * {@code ClassCastException} (MappingEndEvent → NodeEvent), {@code NoSuchElementException}, etc. + */ + @Nonnull + private static Yaml newInterpreter() { + return new Yaml(new QueryParameterYamlConstructor(new LoaderOptions())); + } private static final class QueryParameterYamlConstructor extends SafeConstructor { private static final Tag RANDOM_TAG = new Tag("!r"); @@ -301,7 +314,7 @@ private List> getInjections(@Nonnull String query) { int end = query.indexOf("!!", start + 2); Assert.thatUnchecked(end != -1, "Illegal format: Parameter injection not formed correctly in query " + query); cursor = end + 2; - lst.add(Pair.of(query.substring(start, end + 2), INTERPRETER.load(query.substring(start + 2, end)))); + lst.add(Pair.of(query.substring(start, end + 2), newInterpreter().load(query.substring(start + 2, end)))); } return lst; } From d5c903e7fb4bd9e43501b8bfb8ee2543b7782f63 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 11:23:38 -0400 Subject: [PATCH 30/74] Mark some heavy lucene tests as non-concurrent These overwhelm FDB, so mark them as SAME-THREAD --- .../record/lucene/LuceneIndexMaintenanceTest.java | 5 +++++ .../apple/foundationdb/record/lucene/LuceneIndexTest.java | 8 ++++++++ .../record/lucene/directory/AgilityContextTest.java | 5 +++++ 3 files changed, 18 insertions(+) diff --git a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexMaintenanceTest.java b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexMaintenanceTest.java index 607845f974..601d031814 100644 --- a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexMaintenanceTest.java +++ b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexMaintenanceTest.java @@ -72,6 +72,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -139,6 +141,9 @@ * Tests of the consistency of the Lucene Index. */ @Tag(Tags.RequiresFDB) +// Runs SAME_THREAD because this class is FDB-heavy and saturates the shared cluster's Batch GRV +// quota when run concurrently with itself or other FDB-heavy tests. +@Execution(ExecutionMode.SAME_THREAD) public class LuceneIndexMaintenanceTest extends FDBRecordStoreConcurrentTestBase { private static final Logger LOGGER = LoggerFactory.getLogger(LuceneIndexMaintenanceTest.class); diff --git a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexTest.java b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexTest.java index bff8ec1788..0ee2e7679e 100644 --- a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexTest.java +++ b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexTest.java @@ -101,6 +101,8 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -226,6 +228,11 @@ */ @SuppressWarnings({"resource", "SameParameterValue"}) @Tag(Tags.RequiresFDB) +// Runs SAME_THREAD because this class (and its subclass below) is FDB-heavy and saturates the +// shared cluster's Batch GRV quota when run concurrently with itself or other FDB-heavy tests +// (LuceneIndexMaintenanceTest, AgilityContextTest). @Execution is not @Inherited, so the nested +// LuceneIndexWithLuceneAsyncToSyncTest below repeats this annotation. +@Execution(ExecutionMode.SAME_THREAD) public class LuceneIndexTest extends FDBLuceneTestBase { private static final Logger LOGGER = LoggerFactory.getLogger(LuceneIndexTest.class); @@ -6313,6 +6320,7 @@ void luceneIndexAttributesNotOptimizedForMutualIndexing() { /** * A version of the tests that runs with the new version of asyncToSync. */ + @Execution(ExecutionMode.SAME_THREAD) public static class LuceneIndexWithLuceneAsyncToSyncTest extends LuceneIndexTest { @Override protected RecordLayerPropertyStorage.Builder addDefaultProps(final RecordLayerPropertyStorage.Builder props) { diff --git a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java index 1c8635802a..cad8a24d3e 100644 --- a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java +++ b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java @@ -45,6 +45,8 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; @@ -74,6 +76,9 @@ * Tests for AgilityContext. */ @Tag(Tags.RequiresFDB) +// Runs SAME_THREAD because this class commits to the shared FDB cluster and races (write-write +// conflicts, GRV throttling) with other FDB-heavy lucene tests when run concurrently. +@Execution(ExecutionMode.SAME_THREAD) class AgilityContextTest extends FDBRecordStoreTestBase { enum AgilityContextType { AGILE, NON_AGILE, READ_ONLY } From e57c0815b4ea76f30f258f3aa926d0c06c1d90d9 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 15:14:37 -0400 Subject: [PATCH 31/74] Lock setup exclusively with FRL initialization This is temporary. Notably, the reason we have to lock the schema setup is because dropping a database calls deleteStore which always bumps the metadata version, even though the stores that we dropping don't cache the store header. This means that deleting a database/schema will conflict with creating a database/schema (or any other operation involving the catalog, I think). --- .../foundationdb/relational/server/FRL.java | 30 +++++++--- .../yamltests/block/SetupBlock.java | 57 ++++++++++++++++++- 2 files changed, 77 insertions(+), 10 deletions(-) diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index eb8e533f96..d7db360659 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -79,13 +79,29 @@ @API(API.Status.EXPERIMENTAL) public class FRL implements AutoCloseable { /** - * JVM-wide lock guarding the parts of construction that mutate shared static state - * ({@link com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider#registerDomainIfNotExists}) - * and write the catalog bootstrap to FDB. Without this, concurrent {@code new FRL(...)} calls (e.g. from - * parallel test {@code @BeforeAll} hooks) race on the catalog-init transaction commit and fail with - * "Transaction not committed due to conflict with another transaction". + * JVM-wide lock guarding any operation that mutates the shared catalog metadata or its underlying + * static state ({@link com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider#registerDomainIfNotExists} + * and the catalog-bootstrap transaction below). Without this, concurrent {@code new FRL(...)} calls + * — e.g. from parallel test {@code @BeforeAll} hooks — race on the catalog-init transaction commit + * and fail with "Transaction not committed due to conflict with another transaction". + *

+ * Exposed (package-public to {@code .server}; referenced by reflection or test-only callers via + * {@link #catalogLock()}) so that test scaffolding that runs additional catalog-mutating DDL + * (CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE) can serialize against FRL initialization + * on the same JVM and avoid the same race. */ - private static final Object INIT_LOCK = new Object(); + private static final Object CATALOG_LOCK = new Object(); + + /** + * Returns the JVM-wide monitor used to serialize catalog-mutating DDL. Hold this when issuing + * CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, or any other operation that writes the + * cluster-global catalog metadata, to avoid SQLSTATE 40001 conflicts with concurrent + * {@link #FRL(Options, String, boolean)} construction or other DDL on the same JVM. + */ + @Nonnull + public static Object catalogLock() { + return CATALOG_LOCK; + } private final FdbConnection fdbDatabase; private final RelationalDriver driver; @@ -116,7 +132,7 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis // against the same cluster (typical in parallel tests). final StoreCatalog storeCatalog; final KeySpace keySpace; - synchronized (INIT_LOCK) { + synchronized (CATALOG_LOCK) { final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); keyspaceProvider.registerDomainIfNotExists("FRL"); keySpace = keyspaceProvider.getKeySpace(); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java index 0ca83b7923..5193c7c554 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java @@ -21,6 +21,8 @@ package com.apple.foundationdb.relational.yamltests.block; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.server.FRL; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.yamltests.CustomYamlConstructor; import com.apple.foundationdb.relational.yamltests.Matchers; @@ -71,7 +73,7 @@ protected SetupBlock(@Nonnull YamlReference reference, @Nonnull List executeExecutables(executables)); } catch (Throwable e) { throw YamlExecutionContext.wrapContext(e, () -> "‼️ Failed to execute all the setup steps in Setup block at " + getReference(), @@ -79,6 +81,52 @@ public void execute() { } } + /** + * Runs {@code runnable} under {@link FRL#catalogLock()} with a small retry loop on SQLSTATE + * 40001 transaction conflicts. The lock is shared with {@link FRL}'s catalog-bootstrap, so + * setup-block DDL (CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, CREATE SCHEMA) is + * serialized against both other setup blocks and other test classes' FRL construction on the + * same JVM. The retry catches any residual conflicts caused by code paths that write the + * cluster-global meta-data version-stamp outside our control. + *

+ * Retry is safe because the schema_template and destruct executable lists use {@code DROP … + * IF EXISTS} so a partial-success retry doesn't trip "unknown database" errors. Manual setup + * blocks that include catalog-mutating DDL are expected to follow the same convention. + */ + private static void runLockedWithRetry(@Nonnull Runnable runnable) { + synchronized (FRL.catalogLock()) { + final int maxAttempts = 5; + for (int attempt = 1; ; attempt++) { + try { + runnable.run(); + return; + } catch (Throwable e) { + if (attempt >= maxAttempts || !isTransactionConflict(e)) { + throw e; + } + try { + Thread.sleep(10L * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw e; + } + } + } + } + } + + private static boolean isTransactionConflict(@Nonnull Throwable t) { + Throwable cursor = t; + while (cursor != null) { + if (cursor instanceof SQLException + && ErrorCode.SERIALIZATION_FAILURE.getErrorCode().equals(((SQLException) cursor).getSQLState())) { + return true; + } + cursor = cursor.getCause(); + } + return false; + } + public static final class ManualSetupBlock extends SetupBlock { public static final String STEPS = "steps"; @@ -316,8 +364,11 @@ public static DestructTemplateBlock withDatabaseAndSchema(@Nonnull final YamlRef @Nonnull String schemaTemplateName, @Nonnull String databasePath) { try { final var steps = new ArrayList(); - steps.add("DROP DATABASE " + databasePath); - steps.add("DROP SCHEMA TEMPLATE " + schemaTemplateName); + // Use IF EXISTS so a retry after a partial success (first attempt dropped the + // database but conflicted on the schema-template DROP) doesn't fail on the second + // attempt's "Cannot delete unknown database" / "Schema template doesn't exist". + steps.add("DROP DATABASE IF EXISTS " + databasePath); + steps.add("DROP SCHEMA TEMPLATE IF EXISTS " + schemaTemplateName); final var executables = new ArrayList>(); for (final var step : steps) { final var resolvedCommand = QueryCommand.withQueryString(reference, step, executionContext); From 00ce76b135f54d8b3c661f07a6ba65b1a82943c3 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 11:34:07 -0400 Subject: [PATCH 32/74] Used time-based retry for starting external servers --- .../yamltests/server/ExternalServer.java | 82 +++++++++++++++---- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java index 8f533ca8eb..dcf65d00fc 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java @@ -152,30 +152,30 @@ public void start() throws Exception { "-jar", serverJar.getAbsolutePath(), "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); + // Always capture stderr to a tempfile so we can include its tail in the failure message + // if the subprocess dies or never becomes available. Stdout is only retained when the + // tests.saveServerLogs sysProp is set, since it can be voluminous in the happy path. + // Include current time in file names so they sort nicely. + long currentTime = System.currentTimeMillis(); @Nullable File outFile; - @Nullable - File errFile; if (saveServerLogs) { - // Include current time in file names so that things sort nicely. We want the out and err logs - // for the same server start to be adjacent, and it would be nice for the log files to sort - // chronologically - long currentTime = System.currentTimeMillis(); outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); - errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); } else { outFile = null; - errFile = null; } + final File errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); + // Don't keep the diagnostic file around in the success path (clutters tmp on dev boxes). + errFile.deleteOnExit(); ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); - ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); processBuilder.redirectOutput(out); - processBuilder.redirectError(err); + processBuilder.redirectError(ProcessBuilder.Redirect.to(errFile)); if (clusterFile != null) { processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); } if (!startServer(processBuilder)) { + final String errTail = tailFile(errFile, 4096); if (logger.isWarnEnabled()) { logger.warn(KeyValueLogMessage.of("Failed to start external server", "jar", serverJar, @@ -183,9 +183,17 @@ public void start() throws Exception { "grpc_port", grpcPort, "http_port", httpPort, "out_file", outFile, - "err_file", errFile)); + "err_file", errFile, + "subprocess_alive", serverProcess != null && serverProcess.isAlive(), + "exit_value", serverProcess != null && !serverProcess.isAlive() ? serverProcess.exitValue() : "n/a")); } - Assertions.fail("Failed to start the external server"); + Assertions.fail("Failed to start the external server (grpc_port=" + grpcPort + + ", http_port=" + httpPort + + ", alive=" + (serverProcess != null && serverProcess.isAlive()) + + (serverProcess != null && !serverProcess.isAlive() ? ", exit=" + serverProcess.exitValue() : "") + + ")\n--- last bytes of subprocess stderr (" + errFile + ") ---\n" + + errTail + + "\n--- end ---"); } if (logger.isInfoEnabled()) { @@ -211,6 +219,23 @@ public void start() throws Exception { } } + /** Read the trailing {@code maxBytes} of {@code file} as a UTF-8 string, never throwing. */ + @Nonnull + private static String tailFile(@Nonnull File file, int maxBytes) { + try { + final long size = file.length(); + try (java.io.RandomAccessFile raf = new java.io.RandomAccessFile(file, "r")) { + final int read = (int) Math.min(maxBytes, size); + raf.seek(size - read); + final byte[] buf = new byte[read]; + raf.readFully(buf); + return (size > read ? "…(truncated)…\n" : "") + new String(buf, java.nio.charset.StandardCharsets.UTF_8); + } + } catch (IOException e) { + return "(failed to read " + file + ": " + e + ")"; + } + } + /** * Get a list of available servers in the download folder. * @@ -252,12 +277,35 @@ private boolean startServer(ProcessBuilder processBuilder) throws IOException, S } private boolean attemptConnectionWithRetry() throws SQLException, InterruptedException { - final int maxAttempts = 20; - boolean started = false; + // Time-based budget rather than a fixed retry count: under JVM-wide contention from a + // parallel test run (e.g. many test classes each spawning their own external servers, + // YamlIntegrationTests starts ~5) the subprocess can take well over 10s to finish JVM + // warmup, FDB connect, and bind its gRPC port. In isolation the same start completes in + // ~3s, so the larger ceiling only kicks in on contended runs. + final long deadlineMillis = System.currentTimeMillis() + 30_000L; int attempts = 0; - while (!started && attempts < maxAttempts) { - long delay = 50L * attempts; - Thread.sleep(delay); + boolean started = false; + while (!started) { + // If the subprocess has died, no amount of retrying will help — bail immediately so + // the test fails quickly with an actionable signal instead of consuming the full budget. + if (serverProcess != null && !serverProcess.isAlive()) { + if (logger.isWarnEnabled()) { + logger.warn(KeyValueLogMessage.of("External server subprocess exited before becoming available", + LogMessageKeys.VERSION, version, + "grpc_port", getPort(), + "exit_value", serverProcess.exitValue(), + "attempts", attempts)); + } + return false; + } + if (System.currentTimeMillis() >= deadlineMillis) { + return false; + } + // Linear backoff capped at 1s so we don't pace much beyond steady state. + final long delay = Math.min(50L * attempts, 1000L); + if (delay > 0) { + Thread.sleep(delay); + } started = attemptConnection(); attempts++; if (logger.isDebugEnabled()) { From 6c78f3fd9b4eac78acf6d13190995389cb05a402 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 11:48:45 -0400 Subject: [PATCH 33/74] FRL: Retry conflicts during catalog initialization This should be irrelevant for production, but can be quite a problem when tests are running in parallel. --- .../foundationdb/relational/server/FRL.java | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index d7db360659..199a77b468 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -38,6 +38,7 @@ import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.metrics.NoOpMetricRegistry; import com.apple.foundationdb.relational.jdbc.TypeConversion; @@ -127,19 +128,22 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } this.fdbDatabase = new DirectFdbConnection(fdbDb, NoOpMetricRegistry.INSTANCE); - // Serialize the rest of construction: registerDomainIfNotExists mutates the shared keyspace, and - // the catalog-init transaction below races on commit when multiple FRLs are constructed concurrently - // against the same cluster (typical in parallel tests). + // Serialize the rest of construction in-JVM: registerDomainIfNotExists mutates the shared + // keyspace, and the catalog-init transaction below races on commit when multiple FRLs are + // constructed concurrently against the same cluster (typical in parallel tests). + // + // The lock only covers within-JVM contention; multiple OS processes (e.g. parent test JVM + // + several external-server subprocesses) constructing FRL against the same cluster will + // still race on the catalog-init commit and produce SQLSTATE 40001. Retry handles that + // case — the catalog init is idempotent under retry because openRecordStore opens the + // already-initialized store on a re-attempt. final StoreCatalog storeCatalog; final KeySpace keySpace; synchronized (CATALOG_LOCK) { final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); keyspaceProvider.registerDomainIfNotExists("FRL"); keySpace = keyspaceProvider.getKeySpace(); - try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); - } + storeCatalog = initializeCatalogWithRetry(keySpace); } RecordLayerConfig rlConfig = RecordLayerConfig.getDefault(); @@ -174,6 +178,41 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } } + /** + * Initializes the store catalog under a fresh transaction, retrying on SQLSTATE 40001 + * (transaction conflict) up to a small attempt limit. Used to absorb cross-JVM races on the + * catalog-init commit when several FRL processes start concurrently against the same cluster + * (e.g. the test JVM plus several external-server subprocesses). Within a single JVM the + * surrounding {@link #CATALOG_LOCK} prevents contention; this retry covers the cross-process + * case where that lock has no effect. + */ + @Nonnull + private StoreCatalog initializeCatalogWithRetry(@Nonnull KeySpace keySpace) throws RelationalException { + final int maxAttempts = 10; + RelationalException last = null; + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { + final StoreCatalog catalog = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + return catalog; + } catch (RelationalException e) { + if (e.getErrorCode() != ErrorCode.SERIALIZATION_FAILURE) { + throw e; + } + last = e; + // Short, slightly increasing backoff; cross-process commit-conflict windows are brief. + try { + Thread.sleep(20L * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw e; + } + } + } + // Exhausted retries; surface the most recent conflict so callers see why startup failed. + throw last; + } + public RelationalDriver getDriver() { return driver; } From b2f4b085f96aae6b9add0390da17c3a5158e4fba Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 25 Jun 2026 16:07:35 -0400 Subject: [PATCH 34/74] Add mockito as a javaagent, sa recomended by the mockito docs TODO move this to testing.gradle so all modules take advantage --- fdb-record-layer-core/fdb-record-layer-core.gradle | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fdb-record-layer-core/fdb-record-layer-core.gradle b/fdb-record-layer-core/fdb-record-layer-core.gradle index fd7c088253..35510f81fc 100644 --- a/fdb-record-layer-core/fdb-record-layer-core.gradle +++ b/fdb-record-layer-core/fdb-record-layer-core.gradle @@ -24,6 +24,13 @@ plugins { apply from: rootProject.file('gradle/proto.gradle') apply from: rootProject.file('gradle/publishing.gradle') +configurations { + // Resolved to the mockito-core jar so it can be installed as a -javaagent on the test JVM. + // Required on JDK 21+ because Mockito's inline mock maker can no longer self-attach. + // See https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 + mockitoAgent +} + dependencies { annotationProcessor project(':fdb-java-annotations') api project(':fdb-extensions') @@ -47,6 +54,12 @@ dependencies { testImplementation(libs.snakeyaml) testAnnotationProcessor(libs.autoService) + + mockitoAgent(libs.mockito) { transitive = false } +} + +tasks.withType(Test).configureEach { + jvmArgs("-javaagent:${configurations.mockitoAgent.asPath}") } sourceSets { From 431f1f5d243b56999a04ce2b5e499a42c7612808 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 14:18:48 -0400 Subject: [PATCH 35/74] Move mockitoAgent to testing.gradle --- .../fdb-record-layer-core.gradle | 13 ----------- gradle/testing.gradle | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/fdb-record-layer-core/fdb-record-layer-core.gradle b/fdb-record-layer-core/fdb-record-layer-core.gradle index 35510f81fc..fd7c088253 100644 --- a/fdb-record-layer-core/fdb-record-layer-core.gradle +++ b/fdb-record-layer-core/fdb-record-layer-core.gradle @@ -24,13 +24,6 @@ plugins { apply from: rootProject.file('gradle/proto.gradle') apply from: rootProject.file('gradle/publishing.gradle') -configurations { - // Resolved to the mockito-core jar so it can be installed as a -javaagent on the test JVM. - // Required on JDK 21+ because Mockito's inline mock maker can no longer self-attach. - // See https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 - mockitoAgent -} - dependencies { annotationProcessor project(':fdb-java-annotations') api project(':fdb-extensions') @@ -54,12 +47,6 @@ dependencies { testImplementation(libs.snakeyaml) testAnnotationProcessor(libs.autoService) - - mockitoAgent(libs.mockito) { transitive = false } -} - -tasks.withType(Test).configureEach { - jvmArgs("-javaagent:${configurations.mockitoAgent.asPath}") } sourceSets { diff --git a/gradle/testing.gradle b/gradle/testing.gradle index 321cea4285..872cefe90d 100644 --- a/gradle/testing.gradle +++ b/gradle/testing.gradle @@ -29,6 +29,19 @@ jacoco { toolVersion = libs.versions.jacoco.get() } +// Resolved to the mockito-core jar so it can be installed as a -javaagent on every Test JVM. +// Required on JDK 21+ because Mockito's inline mock maker can no longer self-attach to a +// running VM (see https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3). +// Under parallel JUnit class execution this also avoids the Byte-Buddy self-attach race that +// manifests as `IllegalStateException: Could not initialize plugin: interface +// org.mockito.plugins.MockMaker`. Hooked into every Test task in `tasks.withType(Test)` below. +configurations { + mockitoAgent +} +dependencies { + mockitoAgent(libs.mockito) { transitive = false } +} + // if you add to or modify these testing configurations, make sure to update the CONTRIBUTING.md to include any updates test { useJUnitPlatform { @@ -311,6 +324,15 @@ tasks.withType(Test).configureEach { task -> // useJUnitPlatform() here is a no-op for tasks that already have it. task.useJUnitPlatform() + // Install Mockito as a JVM agent on every Test task. See the comment on the + // `mockitoAgent` configuration above for why this is necessary. Deferred to `doFirst` + // so that the `mockitoAgent` configuration is not resolved during evaluation (which + // would prevent the root build's `resolutionStrategy.eachDependency` callback from + // attaching to it). + task.doFirst { + task.jvmArgs("-javaagent:${configurations.mockitoAgent.asPath}") + } + // Configure whether or not tests will validate that asyncToSync isn't being called in async // context. See BlockingInAsyncDetection class for details on values. // disable BLOCKING_DETECTION to better simulate performance From 832554242bde3ceee3da13ff96e29ae9a193d6f0 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 14:20:14 -0400 Subject: [PATCH 36/74] Add lock & retry to catalog operations for Rules --- .../relational/utils/CatalogOperations.java | 108 ++++++++++++++++++ .../relational/utils/DatabaseRule.java | 30 ++--- .../relational/utils/SchemaRule.java | 28 +++-- .../relational/utils/SchemaTemplateRule.java | 46 ++++---- 4 files changed, 167 insertions(+), 45 deletions(-) create mode 100644 fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java new file mode 100644 index 0000000000..21b092a007 --- /dev/null +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -0,0 +1,108 @@ +/* + * CatalogOperations.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2021-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.relational.utils; + +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; + +import javax.annotation.Nonnull; +import java.sql.SQLException; + +/** + * Test-only synchronization helpers for catalog-mutating DDL (CREATE/DROP DATABASE, + * CREATE/DROP SCHEMA TEMPLATE, CREATE/DROP SCHEMA). + *

+ * Under parallel JUnit class execution, every test class's {@code @BeforeEach}/{@code @AfterEach} + * hooks race on the cluster-wide catalog metadata stored in {@code /__SYS/CATALOG}. The FDB + * commit layer surfaces those races as SQLSTATE 40001 transaction conflicts. The + * {@link #runLockedWithRetry(ThrowingRunnable)} wrapper serializes the operations within the JVM + * (mirroring the {@code FRL.catalogLock()} pattern used by yaml-tests' {@code SetupBlock}) and + * retries any residual conflicts caused by metadata writes the lock doesn't cover + * (cluster-global version-stamp updates, etc.). + *

+ * Wrap CREATE/DROP DDL in {@code @BeforeEach}/{@code @AfterEach} hooks with this helper. Drop + * statements should use {@code DROP ... IF EXISTS} so a retry after a partial success doesn't + * trip an "unknown ..." error. + */ +public final class CatalogOperations { + + /** + * JVM-wide monitor guarding catalog-mutating DDL issued from test rules. Local to this + * fdb-relational-core test classpath; FRL construction in fdb-relational-server has its own + * separate {@code FRL.catalogLock()} and is also retry-guarded. + */ + private static final Object CATALOG_LOCK = new Object(); + + private static final int MAX_ATTEMPTS = 5; + + private CatalogOperations() { + } + + /** + * Functional interface for actions that may throw {@link SQLException}, since most + * catalog-mutating DDL runs through JDBC. + */ + @FunctionalInterface + public interface ThrowingRunnable { + void run() throws SQLException; + } + + /** + * Runs {@code action} under the JVM-wide catalog lock with a small retry loop on SQLSTATE + * 40001 transaction conflicts. Use for any CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, + * or CREATE/DROP SCHEMA executed against {@code /__SYS/CATALOG}. + * + * @param action the catalog DDL to run + * @throws SQLException if the action ultimately fails (after retries, or with a + * non-retriable error) + */ + public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) throws SQLException { + synchronized (CATALOG_LOCK) { + for (int attempt = 1; ; attempt++) { + try { + action.run(); + return; + } catch (SQLException e) { + if (attempt >= MAX_ATTEMPTS || !isTransactionConflict(e)) { + throw e; + } + try { + Thread.sleep(10L * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw e; + } + } + } + } + } + + private static boolean isTransactionConflict(@Nonnull final Throwable t) { + Throwable cursor = t; + while (cursor != null) { + if (cursor instanceof SQLException + && ErrorCode.SERIALIZATION_FAILURE.getErrorCode().equals(((SQLException) cursor).getSQLState())) { + return true; + } + cursor = cursor.getCause(); + } + return false; + } +} diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java index 3a01005605..43b7b66131 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java @@ -69,24 +69,28 @@ public void beforeEach(ExtensionContext context) throws SQLException { } private void setup() throws SQLException { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - Utils.setConnectionOptions(connection, options); - connection.setSchema("CATALOG"); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); - statement.executeUpdate("CREATE DATABASE \"" + databasePath.getPath() + "\""); + CatalogOperations.runLockedWithRetry(() -> { + try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + Utils.setConnectionOptions(connection, options); + connection.setSchema("CATALOG"); + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); + statement.executeUpdate("CREATE DATABASE \"" + databasePath.getPath() + "\""); + } } - } + }); } private void tearDown() throws SQLException { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - Utils.setConnectionOptions(connection, options); - connection.setSchema("CATALOG"); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP DATABASE \"" + databasePath.getPath() + "\""); + CatalogOperations.runLockedWithRetry(() -> { + try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + Utils.setConnectionOptions(connection, options); + connection.setSchema("CATALOG"); + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); + } } - } + }); } @Nonnull diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java index 126751cc59..40ad551a05 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java @@ -72,22 +72,26 @@ public String getSchemaName() { } private void setup() throws Exception { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("CREATE SCHEMA \"" + dbUri.getPath() + "/" + schemaName + "\" WITH TEMPLATE \"" + templateName + "\""); + CatalogOperations.runLockedWithRetry(() -> { + try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + connection.setSchema("CATALOG"); + Utils.setConnectionOptions(connection, connectionOptions); + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("CREATE SCHEMA \"" + dbUri.getPath() + "/" + schemaName + "\" WITH TEMPLATE \"" + templateName + "\""); + } } - } + }); } private void tearDown() throws SQLException { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP SCHEMA \"" + dbUri.getPath() + "/" + schemaName + "\""); + CatalogOperations.runLockedWithRetry(() -> { + try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + connection.setSchema("CATALOG"); + Utils.setConnectionOptions(connection, connectionOptions); + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP SCHEMA IF EXISTS \"" + dbUri.getPath() + "/" + schemaName + "\""); + } } - } + }); } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java index c8486558d2..c3572ee8af 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java @@ -93,28 +93,32 @@ public String getSchemaTemplateName() { @Override public void afterEach(ExtensionContext context) throws SQLException { - final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE \"").append(templateName).append("\""); - - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(dropStatement.toString()); + final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE IF EXISTS \"").append(templateName).append("\""); + + CatalogOperations.runLockedWithRetry(() -> { + try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + connection.setSchema("CATALOG"); + Utils.setConnectionOptions(connection, connectionOptions); + try (Statement statement = connection.createStatement()) { + statement.executeUpdate(dropStatement.toString()); + } } - } + }); } @Override public void beforeEach(ExtensionContext context) throws SQLException { final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE IF EXISTS\"").append(templateName).append("\""); - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(dropStatement.toString()); + CatalogOperations.runLockedWithRetry(() -> { + try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + connection.setSchema("CATALOG"); + Utils.setConnectionOptions(connection, connectionOptions); + try (Statement statement = connection.createStatement()) { + statement.executeUpdate(dropStatement.toString()); + } } - } + }); final StringBuilder createStatement = new StringBuilder("CREATE SCHEMA TEMPLATE \"").append(templateName).append("\" "); createStatement.append(typeCreator.getTypeDefinition()); createStatement.append(tableCreator.getTypeDefinition()); @@ -123,13 +127,15 @@ public void beforeEach(ExtensionContext context) throws SQLException { createStatement.append(schemaTemplateOptions.getOptionsString()); } - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(createStatement.toString()); + CatalogOperations.runLockedWithRetry(() -> { + try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + connection.setSchema("CATALOG"); + Utils.setConnectionOptions(connection, connectionOptions); + try (Statement statement = connection.createStatement()) { + statement.executeUpdate(createStatement.toString()); + } } - } + }); } public static final class SchemaTemplateOptions { From ae1dfd8ba6ced4184e50e372d8dcbbcbe6e38576 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 14:32:28 -0400 Subject: [PATCH 37/74] Use randomly generated database names for Ddl --- .../DeleteRangeNoMetadataKeyTest.java | 2 +- .../recordlayer/DeleteRangeTest.java | 4 +- .../recordlayer/NullsInResultSetTest.java | 4 +- .../relational/recordlayer/TableTest.java | 6 +- .../query/CaseSensitivityQueryTests.java | 6 +- .../recordlayer/query/ExplainTests.java | 18 +-- .../recordlayer/query/GroupByQueryTests.java | 48 +++---- .../query/PreparedStatementTests.java | 70 +++++----- .../query/QueryWithContinuationTest.java | 20 +-- .../recordlayer/query/StandardQueryTests.java | 126 +++++++++--------- .../query/TemporaryFunctionTests.java | 56 ++++---- .../query/V2PlanGeneratorTests.java | 78 +++++------ .../cache/ValueSpecificConstraintTests.java | 26 ++-- .../structuredsql/SqlVisitorTests.java | 14 +- .../structuredsql/StatementBuilderTests.java | 28 ++-- .../foundationdb/relational/utils/Ddl.java | 19 +++ 16 files changed, 272 insertions(+), 253 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java index f05c2d255c..3e652e9041 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java @@ -397,7 +397,7 @@ void testDeleteWithIndexSamePrefixButDeleteGoesBeyondIndex() throws Exception { private Ddl getDdl(String templateSuffix) throws Exception { return Ddl.builder() - .database(URI.create("/TEST/QT")) + .database() .relationalExtension(relationalExtension) .schemaTemplate(SCHEMA_TEMPLATE + templateSuffix) .schemaTemplateOptions(new SchemaTemplateRule.SchemaTemplateOptions(true, true)) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java index 29f8933c3d..a712ea8563 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java @@ -221,7 +221,7 @@ void deleteUsingNonKeyColumns() throws Exception { @Test void testDeleteWithIndexWithSamePrefix() throws Exception { final String schemaTemplate = SCHEMA_TEMPLATE + " CREATE INDEX idx1 ON t1(id, a)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var stmt = ddl.setSchemaAndGetConnection().createStatement()) { insertData(stmt); KeySet toDelete = new KeySet() @@ -251,7 +251,7 @@ void testDeleteWithIndexWithSamePrefix() throws Exception { @Test void testDeleteWithIndexSamePrefixButDeleteGoesBeyondIndex() throws Exception { final String schemaTemplate = SCHEMA_TEMPLATE + " CREATE INDEX idx1 ON t1(id)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var stmt = ddl.setSchemaAndGetConnection().createStatement()) { insertData(stmt); KeySet toDelete = new KeySet() diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java index 557b85efcd..9a359d3a24 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java @@ -50,7 +50,7 @@ public NullsInResultSetTest() { @Test void nullValues() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (RelationalConnection conn = ddl.setSchemaAndGetConnection()) { try (RelationalStatement s = conn.createStatement()) { s.execute("INSERT INTO T(PK) VALUES(100)"); @@ -93,7 +93,7 @@ void nullValues() throws Exception { @Test void notNullValues() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (RelationalConnection conn = ddl.setSchemaAndGetConnection()) { try (RelationalStatement s = conn.createStatement()) { final var struct = EmbeddedRelationalStruct.newBuilder() diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java index 26478888b2..2c16d38aac 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java @@ -93,7 +93,7 @@ void wrongSizeOfPrimaryKeyInGet() { @Test void wrongSizeOfPrimaryKeyInGetLongerKey() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate("CREATE TABLE FOO(A bigint, B bigint, C bigint, PRIMARY KEY(C, A))").build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate("CREATE TABLE FOO(A bigint, B bigint, C bigint, PRIMARY KEY(C, A))").build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { RelationalAssertions.assertThrowsSqlException( () -> statement.executeGet("FOO", new KeySet().setKeyColumn("C", 5), Options.NONE)) @@ -324,7 +324,7 @@ void testIndexCreatedUsingLastColumn() throws Exception { final String schema = " CREATE TABLE tbl1 (id bigint, a string, b string, c string, PRIMARY KEY(id))" + " CREATE INDEX c_name_idx as select c from tbl1"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { var result = EmbeddedRelationalStruct.newBuilder().addLong("ID", 42L).addString("A", "valuea1").addString("B", "valueb1").addString("C", "valuec1").build(); @@ -357,7 +357,7 @@ void testIndexCreatedUsingMultipleColumns() throws Exception { final String schema = " CREATE TABLE tbl1 (id bigint, a string, b string, c string, d string, PRIMARY KEY(id))" + " CREATE INDEX c_name_idx as select c, d from tbl1 order by c, d"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { var result = EmbeddedRelationalStruct.newBuilder().addLong("ID", 42L).addString("A", "valuea1").addString("B", "valueb1").addString("C", "valuec1").addString("D", "valued1").build(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java index 4dd9c73f72..ef853acaa7 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java @@ -47,7 +47,7 @@ void caseSensitiveConnectionTest() throws Exception { final String schemaTemplate = "create type as struct LoCaTiOn (address string, latitude string, longitude string)" + " create table ReStAuRaNt(rest_no bigint, name string, location LoCaTiOn, primary key(rest_no))"; try (var extensionResource = EmbeddedRelationalExtension.newAsResource(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()); - var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(extensionResource.getUnderlyingExtension()) + var ddl = Ddl.builder().database().relationalExtension(extensionResource.getUnderlyingExtension()) .withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true) .schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { @@ -80,7 +80,7 @@ void caseSensitiveConnectionTestCase2() throws Exception { final String schemaTemplate = "create type as struct LoCaTiOn (address string, latitude string, longitude string)" + " create table ReStAuRaNt(rest_no bigint, name string, location LoCaTiOn, primary key(rest_no))"; try (var extensionResource = EmbeddedRelationalExtension.newAsResource(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, false).build()); - var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(extensionResource.getUnderlyingExtension()) + var ddl = Ddl.builder().database().relationalExtension(extensionResource.getUnderlyingExtension()) .withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, false) .schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { @@ -114,7 +114,7 @@ void caseSensitiveConnectionTestCase3(boolean isCaseSensitive) throws Exception final String schemaTemplate = "create type as struct \"Location\" (address string, latitude string, longitude string)" + " create table \"Restaurant\"(rest_no bigint, name string, location \"Location\", primary key(rest_no))"; try (var extensionResource = EmbeddedRelationalExtension.newAsResource(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, isCaseSensitive).build()); - var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(extensionResource.getUnderlyingExtension()) + var ddl = Ddl.builder().database().relationalExtension(extensionResource.getUnderlyingExtension()) .withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, isCaseSensitive) .schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java index 1e34a66ed0..01b827ebea 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java @@ -96,7 +96,7 @@ void explainResultSetMetadataTest() throws Exception { "PLANNING_PHASE_TASKS_TOTAL_TIME_NS" ); final var expectedPlannerMetricsTypes = Collections.nCopies(expectedPlannerMetricsLabels.size(), Types.BIGINT); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { try (final RelationalResultSet resultSet = ps.executeQuery()) { @@ -125,7 +125,7 @@ void explainResultSetMetadataTest() throws Exception { @Test void explainWithNoContinuationTest() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { ps.setMaxRows(2); @@ -143,7 +143,7 @@ void explainWithNoContinuationTest() throws Exception { @Test void explainContainsEventStatsWithADebuggerInstalled() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { ps.setMaxRows(2); @@ -171,7 +171,7 @@ void explainContainsEventStats() throws Exception { Debugger.setDebugger(null); org.junit.jupiter.api.Assertions.assertNull(Debugger.getDebugger()); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { ps.setMaxRows(2); @@ -222,7 +222,7 @@ public java.util.Optional ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM bla").execute()); @@ -314,7 +314,7 @@ void failExplain() throws Exception { @Test void explainWithContinuationSerializedPlanTest() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); Continuation continuation; try (final var connection = ddl.setSchemaAndGetConnection()) { @@ -346,7 +346,7 @@ void explainWithContinuationSerializedPlanTest() throws Exception { @Test void explainExecuteStatementTest() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); Continuation continuation; try (final var connection = ddl.setSchemaAndGetConnection()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java index 7b0cb37d94..a2dae22a42 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java @@ -54,7 +54,7 @@ void groupByWithScanLimit() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var conn = ddl.setSchemaAndGetConnection()) { Continuation continuation = null; conn.setOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 2); @@ -120,7 +120,7 @@ void groupByWithRowLimit() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, b bigint, c bigint, primary key(pk))\n" + "create index i1 ON t1(a)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var conn = ddl.setSchemaAndGetConnection()) { conn.setOption(Options.Name.MAX_ROWS, 1); try (var statement = conn.createStatement()) { @@ -169,7 +169,7 @@ void isNullPredicateUsesGroupIndex() throws Exception { "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))\n" + "CREATE INDEX idx1 as select a, b, c from t1 order by a, b, c\n" + "CREATE INDEX sum_idx as select sum(c) from t1 group by a\n"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -200,7 +200,7 @@ void groupByClauseWithPredicateWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -225,7 +225,7 @@ void groupByClauseWithPredicateMultipleAggregationsWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 as select a, b, c from T1 order by a, b, c"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -250,7 +250,7 @@ void groupByClauseWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON T1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -279,7 +279,7 @@ void groupByClauseWorksWithSubquery() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON T1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -308,7 +308,7 @@ void groupByClauseWorksWithSubqueryAliases() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON T1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -337,7 +337,7 @@ void groupByClauseWorksWithSubqueryAliasesComplex() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -366,7 +366,7 @@ void groupByClauseWorksDifferentAggregations() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -395,7 +395,7 @@ void groupByClauseWorksComplexGrouping() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -424,7 +424,7 @@ void groupByClauseSingleGroup() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -445,7 +445,7 @@ void groupByClauseWithoutGroupingColumnsInProjectionList() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -466,7 +466,7 @@ void groupByClauseWithoutAggregationsInProjectionList() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -487,7 +487,7 @@ void groupByInSubSelectWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -508,7 +508,7 @@ void groupByConstantColumn() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 2, 10); @@ -529,7 +529,7 @@ void aggregationWithoutGroupBy() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -550,7 +550,7 @@ void nestedGroupByStatements() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -571,7 +571,7 @@ void groupByClauseWorksComplexAggregations() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -600,7 +600,7 @@ void groupByClauseWithNestedAggregationsIsSupported() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -629,7 +629,7 @@ void expansionOfStarWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -656,7 +656,7 @@ void groupByClauseWithNamedGroupingColumns() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -686,7 +686,7 @@ void groupByOverJoinWorks() throws Exception { "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, x bigint, y bigint, z bigint, primary key(pk))" + "CREATE INDEX idx1 ON T1(B)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -713,7 +713,7 @@ void groupByWithNamedGroups() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java index 6c0560e056..abb58036b8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java @@ -91,7 +91,7 @@ public PreparedStatementTests() { @Test void failsToQueryWithoutASchema() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (Connection conn = ddl.getConnection()) { conn.setSchema(null); @@ -105,7 +105,7 @@ void failsToQueryWithoutASchema() throws Exception { @Test void simpleSelect() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -119,7 +119,7 @@ void simpleSelect() throws Exception { @Test void basicParameterizedQuery() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -138,7 +138,7 @@ void basicParameterizedQuery() throws Exception { @Test void parameterizedQueryMultipleParameters() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, 'testName')"); } @@ -164,7 +164,7 @@ void parameterizedQueryMultipleParameters() throws Exception { @Test void parameterizedQueryNamedParameters() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, 'testName')"); } @@ -190,7 +190,7 @@ void parameterizedQueryNamedParameters() throws Exception { @Test void parameterizedQueryNamedAndUnnamedParameters() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, 'testName')"); } @@ -216,7 +216,7 @@ void parameterizedQueryNamedAndUnnamedParameters() throws Exception { @Test void parameterizedQueryQuestionAndDollarParameter() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, 'testName')"); } @@ -242,7 +242,7 @@ void parameterizedQueryQuestionAndDollarParameter() throws Exception { @Test void parameterizedQueryMissingNamedParameters() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -256,7 +256,7 @@ void parameterizedQueryMissingNamedParameters() throws Exception { @Test void executeWithoutNeededParameterShouldThrow() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var ps = ddl.setSchemaAndGetConnection().prepareStatement("SELECT * FROM RestaurantComplexRecord WHERE REST_NO = ?")) { RelationalAssertions.assertThrowsSqlException(ps::executeQuery) .hasErrorCode(ErrorCode.UNDEFINED_PARAMETER); @@ -272,7 +272,7 @@ void executeWithoutNeededParameterShouldThrow() throws Exception { @Test void limit() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14), (15)"); } @@ -315,7 +315,7 @@ void limit() throws Exception { @Test void setMaxRowsExtremeValues() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14), (15)"); } @@ -340,7 +340,7 @@ void setMaxRowsExtremeValues() throws Exception { @Test void continuation() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -411,7 +411,7 @@ void continuation() throws Exception { @Test void setArrayTypeOfByte() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var count = statement.executeUpdate("INSERT INTO RestaurantReviewer(id) VALUES (1)"); Assertions.assertThat(count).isEqualTo(1); @@ -437,7 +437,7 @@ void setArrayTypeOfByte() throws Exception { @Test void setByteType() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var count = statement.executeUpdate("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (1)"); Assertions.assertThat(count).isEqualTo(1); @@ -456,7 +456,7 @@ void setByteType() throws Exception { @Test void prepareInList() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -517,7 +517,7 @@ void prepareInList() throws Exception { @Test void prepareInListWithMixedTypes() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (5), (6), (7), (8)"); } @@ -535,7 +535,7 @@ void prepareInListWithMixedTypes() throws Exception { @Disabled("equals does work with structs") // TODO ([SQL] Equals comparison does not support tuples) @Test void prepareSelectWithStruct() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantReviewer(id, name, email) VALUES " + "(1, 'alpha', 'alpha@example.com'), " + @@ -598,7 +598,7 @@ void prepareSelectWithStruct() throws Exception { @Test void prepareSelectWithStructList() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantReviewer(id, name, email) VALUES " + "(1, 'alpha', 'alpha@example.com'), " + @@ -664,7 +664,7 @@ void prepareSelectWithStructList() throws Exception { @Test void prepareUpdateWithStruct() throws Exception { final var statsAttributes = new Object[]{3L, "c", "d"}; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantReviewer(id, stats) VALUES (1, (2, 'a', 'b')), (2, (3, 'b', 'c')), (3, (4, 'c', 'd')), (4, (5, 'd', 'e')), (5, (6, 'e', 'f'))"); } @@ -701,7 +701,7 @@ static Stream prepareUpdateWithNestedStructMethodSource() { @ParameterizedTest @MethodSource("prepareUpdateWithNestedStructMethodSource") void prepareUpdateWithNestedStruct(Object[] attributes, boolean succeed) throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (1, 'mango & miso'), (2, 'basil & brawn'), (3, 'peach & pepper'), (4, 'smoky skillet'), (5, 'the tin pot')"); } @@ -736,7 +736,7 @@ void prepareUpdateWithNestedStruct(Object[] attributes, boolean succeed) throws @Test void prepareUpdateWithArrayOfPrimitives() throws Exception { final var customerAttributes = new String[]{"george", "adam", "billy"}; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (1, 'mango & miso'), (2, 'basil & brawn'), (3, 'peach & pepper'), (4, 'smoky skillet'), (5, 'the tin pot')"); } @@ -763,7 +763,7 @@ void prepareUpdateWithArrayOfPrimitives() throws Exception { @Test void prepareUpdateWithArrayOfStructs() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (1, 'mango & miso'), (2, 'basil & brawn'), (3, 'peach & pepper'), (4, 'smoky skillet'), (5, 'the tin pot')"); } @@ -835,7 +835,7 @@ private void assertTags(RelationalResultSet resultSet, Object[][] restaurantTagA @Test void prepareInListWrongTypeInArray() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { // IN list parameter is an array of Long, but has some non-Long elements. try (var ps = ddl.setSchemaAndGetConnection().prepareStatement("SELECT * FROM RestaurantComplexRecord WHERE rest_no in ?")) { @@ -879,7 +879,7 @@ void prepareInListWrongTypeInArray() throws Exception { @Test void prepareInListOfTuple() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (1, 'mango & miso'), (2, 'basil & brawn'), (3, 'peach & pepper'), (4, 'smoky skillet'), (5, 'the tin pot')"); } @@ -902,7 +902,7 @@ void prepareInListOfTuple() throws Exception { @Test void prepareInListWrongTypeShouldThrow() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -916,7 +916,7 @@ void prepareInListWrongTypeShouldThrow() throws Exception { @Test void prepareEmptyInList() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -933,7 +933,7 @@ void prepareEmptyInList() throws Exception { @Test void withPlanCache() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -964,7 +964,7 @@ void withPlanCache() throws Exception { @Test // TODO (Prepared Statement does not cast fields if set with the wrong types) void setWrongTypeForQuestionMarkParameter() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -1001,7 +1001,7 @@ void setWrongTypeForQuestionMarkParameter() throws Exception { @Test // TODO (Prepared Statement does not cast fields if set with the wrong types) void setWrongTypeForQuestionMarkNamedParameter() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -1036,7 +1036,7 @@ void setWrongTypeForQuestionMarkNamedParameter() throws Exception { @Test void setNull() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().prepareStatement("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, ?), (11, ?named), (12, ?)")) { statement.setNull(1, Types.NULL); statement.setNull("named", Types.NULL); @@ -1142,7 +1142,7 @@ static Stream listParameterProvider() { void emptyParametersInTheInList(String ignored, String column, BiConsumer consumer) throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT nested (a bigint)" + " CREATE TABLE T1(pk bigint, a1 bigint, a2 double, a3 string, a4 nested, a5 bytes, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().prepareStatement("select * from t1 where " + column + " in ?")) { consumer.accept(statement, ddl.getConnection()); statement.execute(); @@ -1155,7 +1155,7 @@ void emptyParametersInTheInList(String ignored, String column, BiConsumer consumer) throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT nested (a bigint)" + " CREATE TABLE T1(pk bigint, a1 bigint, a2 double, a3 string, a4 nested, a5 bytes, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().prepareStatement("select * from t1 where " + column + " in ? OPTIONS(LOG QUERY)")) { consumer.accept(statement, ddl.getConnection()); statement.execute(); @@ -1180,7 +1180,7 @@ void cachingQueryWithEmptyList(String ignored, String column, BiConsumer 10")) { resultSet.next(); @@ -248,7 +248,7 @@ void explainTableScan() throws Exception { @Test void explainHintedIndexScan() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { try (final RelationalResultSet resultSet = statement.executeQuery("EXPLAIN SELECT * FROM RestaurantComplexRecord USE INDEX (record_name_idx) WHERE rest_no > 10")) { resultSet.next(); @@ -261,7 +261,7 @@ void explainHintedIndexScan() throws Exception { @Test void explainUnhintedIndexScan() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { try (final RelationalResultSet resultSet = statement.executeQuery("EXPLAIN SELECT * FROM RestaurantComplexRecord AS R WHERE EXISTS (SELECT * FROM R.reviews AS RE WHERE RE.rating >= 9)")) { resultSet.next(); @@ -274,7 +274,7 @@ void explainUnhintedIndexScan() throws Exception { @Test void selectWithPredicateCompositionVariants() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); final var l42 = insertRestaurantComplexRecord(statement, 42L, "rest1"); @@ -314,7 +314,7 @@ void selectWithPredicateCompositionVariants() throws Exception { @Test @Disabled("(yhatem) until https://github.com/FoundationDB/fdb-record-layer/issues/1945 is fixed") void selectWithNullInComparisonOperator() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { var insertedRecord = insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT * FROM RestaurantComplexRecord WHERE 1 is null")) { @@ -350,7 +350,7 @@ void selectWithNullInComparisonOperator() throws Exception { @Test void selectWithFalsePredicate() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 11L); @@ -363,7 +363,7 @@ void selectWithFalsePredicate() throws Exception { @Test void selectWithFalsePredicate2() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 11L); @@ -376,7 +376,7 @@ void selectWithFalsePredicate2() throws Exception { @Test void selectWithContinuation() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.setMaxRows(1); insertRestaurantComplexRecord(statement); @@ -413,7 +413,7 @@ void selectWithContinuation() throws Exception { @Test void selectWithContinuationBeginEndShouldFail() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 42L, "rest1"); @@ -429,7 +429,7 @@ void selectWithContinuationBeginEndShouldFail() throws Exception { @Test void testSelectWithIndexHint() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); // successfully execute a query with hinted index @@ -459,7 +459,7 @@ void testSelectWithIndexHint() throws Exception { void testSelectWithCoveringIndexHint() throws Exception { final String schema = "CREATE TABLE T1(COL1 bigint, COL2 bigint, COL3 bigint, PRIMARY KEY(COL1, COL3))" + " CREATE INDEX T1_IDX as select col1, col3, col2 from t1 order by col1, col3"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var row1 = EmbeddedRelationalStruct.newBuilder().addLong("COL1", 42L).addLong("COL2", 100L).addLong("COL3", 200L).build(); int cnt = statement.executeInsert("T1", row1); @@ -482,7 +482,7 @@ void testSelectWithCoveringIndexHint() throws Exception { @Test void projectIndividualColumns() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT name FROM RestaurantComplexRecord WHERE 11 <= rest_no")) { @@ -495,7 +495,7 @@ void projectIndividualColumns() throws Exception { @Test void projectIndividualQualifiedColumns() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT RestaurantComplexRecord.name FROM RestaurantComplexRecord WHERE 11 <= rest_no")) { @@ -508,7 +508,7 @@ void projectIndividualQualifiedColumns() throws Exception { @Test void projectIndividualQualifiedColumnsOverAlias() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT name FROM RestaurantComplexRecord AS X WHERE 11 <= rest_no")) { @@ -521,7 +521,7 @@ void projectIndividualQualifiedColumnsOverAlias() throws Exception { @Test void projectIndividualQualifiedColumnsOverAlias2() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT X.name FROM RestaurantComplexRecord AS X WHERE 11 <= rest_no")) { @@ -534,7 +534,7 @@ void projectIndividualQualifiedColumnsOverAlias2() throws Exception { @Test void getBytes() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 1, "getBytes", "blob1".getBytes(StandardCharsets.UTF_8)); @@ -566,7 +566,7 @@ void partiqlNestingWorks() throws Exception { " CREATE TYPE AS STRUCT D ( e E )" + " CREATE TYPE AS STRUCT E ( f bigint )" + " CREATE TABLE tbl1 (id bigint, c C, a A, PRIMARY KEY(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var result = EmbeddedRelationalStruct.newBuilder() .addLong("ID", 42L) @@ -618,7 +618,7 @@ void partiqlNestingWorksWithRepeatedLeaf() throws Exception { " CREATE TYPE AS STRUCT D ( e E )" + " CREATE TYPE AS STRUCT E ( f bigint array )" + " CREATE TABLE tbl1 (id bigint, c C, a A, PRIMARY KEY(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var result = EmbeddedRelationalStruct.newBuilder() .addLong("ID", 42L) @@ -663,7 +663,7 @@ void partiqlAccessingNestedFieldWithInnerRepeatedFieldsFails() throws Exception " CREATE TYPE AS STRUCT D ( e E array )" + " CREATE TYPE AS STRUCT E ( f bigint array )" + " CREATE TABLE tbl1 (id bigint, c C, a A, PRIMARY KEY(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { try { statement.execute("SELECT id, c.d.e.f, a.b.c.d.e.f FROM tbl1"); @@ -678,7 +678,7 @@ void partiqlAccessingNestedFieldWithInnerRepeatedFieldsFails() throws Exception @Disabled // until we fix the implicit fetch operator in record layer. void projectIndividualPredicateColumns() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT rest_no FROM RestaurantComplexRecord WHERE 11 <= rest_no")) { @@ -691,7 +691,7 @@ void projectIndividualPredicateColumns() throws Exception { @Disabled // until we implement1 operators for type promotion and casts in record layer. void predicateWithImplicitCast() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); RelationalStruct l42 = insertRestaurantComplexRecord(statement, 42L, "rest1"); @@ -707,7 +707,7 @@ void predicateWithImplicitCast() throws Exception { @Test void existsPredicateWorks() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 42L, "rest1", List.of(Triple.of(1L, 4L, List.of()), Triple.of(2L, 5L, List.of()))); @@ -722,7 +722,7 @@ void existsPredicateWorks() throws Exception { @Test void existsPredicateWorksWithNonNullableArray() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateWithNonNullableArrays).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateWithNonNullableArrays).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement, 42L, "rest1", List.of(Triple.of(1L, 4L, List.of()), Triple.of(2L, 5L, List.of()))); RelationalStruct l43 = insertRestaurantComplexRecord(statement, 43L, "rest2", List.of(Triple.of(3L, 9L, List.of()), Triple.of(4L, 8L, List.of()))); @@ -736,7 +736,7 @@ void existsPredicateWorksWithNonNullableArray() throws Exception { @Test void existsPredicateNestedWorks() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); RelationalStruct l42 = insertRestaurantComplexRecord(statement, 42L, "rest1", @@ -758,7 +758,7 @@ void testSubquery() throws Exception { final String schema = "CREATE TYPE AS STRUCT contact_detail(name string, phone_number string, address string) " + "CREATE TYPE AS STRUCT messages(\"TEXT\" string, timestamp bigint,sent boolean) " + "CREATE TABLE conversations(id bigint, other_party CONTACT_DETAIL, messages MESSAGES ARRAY,primary key(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var row1 = EmbeddedRelationalStruct.newBuilder() .addLong("ID", 0L) @@ -848,7 +848,7 @@ void testSubquery() throws Exception { @Test void aliasingColumnsWorks() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT Y.M FROM (SELECT X.N AS M FROM (SELECT name AS N FROM RestaurantComplexRecord WHERE 11 <= rest_no) X) Y")) { @@ -862,7 +862,7 @@ void aliasingColumnsWorks() throws Exception { @Test void aliasingTableToResolveAmbiguityWorks() throws Exception { final String schema = "CREATE TABLE FOO(FOO bigint, PRIMARY KEY(FOO))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var row1 = EmbeddedRelationalStruct.newBuilder().addLong("FOO", 42L).build(); int cnt = statement.executeInsert("FOO", row1); @@ -897,14 +897,14 @@ void testIncorrectUserDefinedFunction() throws Exception { "CREATE FUNCTION lat(IN x TYPE Location) RETURNS string AS x.coor.latitude\n"; // "coor" is wrong // fail to build the MacroFunctionValue in the DDL step - Assertions.assertThrows(SQLException.class, () -> Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate1).build()).getMessage(); + Assertions.assertThrows(SQLException.class, () -> Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate1).build()).getMessage(); final String schemaTemplate3 = "CREATE TYPE AS STRUCT LATLON (latitude string, longitude string)\n" + "CREATE TYPE AS STRUCT Location (name string, coord LATLON)" + "CREATE TABLE T1(uid bigint, loc Location, PRIMARY KEY(uid))\n" + "CREATE FUNCTION name(IN x TYPE Location) RETURNS string AS x.name\n"; // name is a reserved word - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate3).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate3).build()) { try (var ps = ddl.setSchemaAndGetConnection().prepareStatement("SELECT * FROM T1 WHERE name(loc) = ?name")) { ps.setString("name", "Apple Park Visitor Center"); final var errorMsg3 = Assertions.assertThrows(SQLException.class, ps::executeQuery).getMessage(); @@ -957,7 +957,7 @@ void testBitmapNoBitmapIndex() throws Exception { private void testBitmapResult(String schemaTemplate, String query) throws Exception { int expectedByteArrayLength = 1250; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'world')"); statement.executeUpdate("insert into t1 values (2, 'world')"); @@ -1021,7 +1021,7 @@ private static List collectOnBits(@Nullable byte[] bitmap, int expectedArr private void testBitmapResultWithEmptyGroup(String schemaTemplate) throws Exception { int expectedByteArrayLength = 1250; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'world')"); statement.executeUpdate("insert into t1 values (2, 'world')"); @@ -1058,7 +1058,7 @@ private void testBitmapResultWithEmptyGroup(String schemaTemplate) throws Except @Test void queryJavaCallFunctionLocallyCreatedUdf() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'world')"); Assertions.assertTrue(statement.execute("SELECT java_call('com.apple.foundationdb.relational.recordlayer.query.udf.SumUdf', pk, 42) + 100 FROM T1"), "Did not return a result set from a select statement!"); @@ -1075,7 +1075,7 @@ void queryJavaCallFunctionLocallyCreatedUdf() throws Exception { void queryJavaCallSimulatecustomerFunction() throws Exception { final var expected = EmbeddedRelationalArray.newBuilder().addBytes(new byte[]{0xA, 0xB}).build(); final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bytes, b bytes array, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, X'0A', [ X'0B' ])"); Assertions.assertTrue(statement.execute("SELECT java_call('com.apple.foundationdb.relational.recordlayer.query.udf.ByteOperationsUdf', a, b) FROM T1"), "Did not return a result set from a select statement!"); @@ -1091,7 +1091,7 @@ void queryJavaCallSimulatecustomerFunction() throws Exception { @Test void selectStarStatement() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 101)"); Assertions.assertTrue(statement.execute("select * from t1")); @@ -1107,7 +1107,7 @@ void selectStarStatement() throws Exception { @Test void selectWithEmptyListAsPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'bla')"); } @@ -1122,7 +1122,7 @@ void selectWithEmptyListAsPredicate() throws Exception { @Test void deleteLimit() throws Exception { final String schemaTemplate = "CREATE TABLE simple (rest_no bigint, name string, primary key(rest_no))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into simple values (1,'testRecord1'), (2, 'testRecord2')"); Assertions.assertTrue(statement.execute("select * from simple")); @@ -1146,7 +1146,7 @@ void deleteLimit() throws Exception { @Test void selectNestedStarWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 101)"); Assertions.assertTrue(statement.execute("select (*) from t1")); @@ -1175,7 +1175,7 @@ void selectNestedStarWorks() throws Exception { @Test void testNamingStruct() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); Assertions.assertTrue(statement.execute("select struct asd (a, 42, struct def (b, c)) as X from t1")); @@ -1193,7 +1193,7 @@ void testNamingStruct() throws Exception { @Test void testNamingStructsSameType() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); Assertions.assertTrue(statement.execute("select struct asd (a, 42, struct def (b, c), struct def(b, c)) as X from t1")); @@ -1214,7 +1214,7 @@ void testNamingStructsSameType() throws Exception { @Test void testNamingStructsDifferentTypesThrows() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); final var message = Assertions.assertThrows(SQLException.class, () -> statement.execute("select struct asd (a, 42, struct def (b, c), struct def(b, c, a)) as X from t1")).getMessage(); @@ -1226,7 +1226,7 @@ void testNamingStructsDifferentTypesThrows() throws Exception { @Test void testNamingStructsSameTypeDifferentNestingLevels() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); Assertions.assertTrue(statement.execute("select a, 42, struct def (b, c), (a, b, c, struct def(b, c)) as X from t1")); @@ -1246,7 +1246,7 @@ void testNamingStructsSameTypeDifferentNestingLevels() throws Exception { @Test void testNamingStructWithNameOfTableIsNotPermitted() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); ContextualSQLException err = Assertions.assertThrows(ContextualSQLException.class, () -> statement.execute("select a, 42, struct T1 (b, c) as X from t1")); @@ -1264,7 +1264,7 @@ void tupleInListAsPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, PRIMARY KEY(pk))" + " CREATE INDEX a_index as select pk, a from T1 order by pk, a"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'bla')"); statement.executeUpdate("insert into t1 values (40, 'foo')"); @@ -1307,7 +1307,7 @@ void tupleInListAsPredicate2() throws Exception { " CREATE INDEX pk_a as select pk, a from T1 order by pk, a" + " CREATE INDEX b_a as select b, a from T1 order by b, a"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'bla', 1)"); statement.executeUpdate("insert into t1 values (40, 'foo', 2)"); @@ -1344,7 +1344,7 @@ void tupleThreeInListAsPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, b bigint, PRIMARY KEY(pk))" + " CREATE INDEX pk_a_b as select pk, a, b from T1 order by pk, a, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'bla', 1)"); statement.executeUpdate("insert into t1 values (40, 'foo', 2)"); @@ -1381,7 +1381,7 @@ void tupleThreeInListAsPredicate() throws Exception { void unionIsNotSupported() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { RelationalAssertions.assertThrows(() -> ((EmbeddedRelationalStatement) statement) @@ -1395,7 +1395,7 @@ void unionIsNotSupported() throws Exception { void structArrayContains() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT A(col2 string, col3 bigint, col4 bigint) " + "CREATE TABLE T1(col1 bigint, a A Array, col5 bigint, primary key(col1))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, [('Apple', 1, 100), ('Orange', 2, 200)], 142), (44, [('Grape', 3, 300), ('Pear', 4, 400)], 144)"); Assertions.assertTrue(statement.execute("SELECT T1.col5 FROM T1 where exists (SELECT 1 FROM T1.A r where r.col2 = 'Grape') ")); @@ -1426,7 +1426,7 @@ void structArrayContains() throws Exception { @Test void primitiveArrayContains() throws Exception { final String schemaTemplate = "CREATE TABLE T1(col1 bigint, a string Array, primary key(col1))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, ['Apple', 'Orange']), (44, ['Grape', 'Pear'])"); Assertions.assertTrue(statement.execute("SELECT * FROM T1 where exists (SELECT 1 FROM T1.A r where r = 'Grape')")); @@ -1457,7 +1457,7 @@ void primitiveArrayContains() throws Exception { @Test void cteWorksCorrectly() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101), (44, 101, 501, 102)"); //Assertions.assertTrue(statement.execute("with C1 (X, Y, Z) as (SELECT a, b, c from T1) select Y, Z from C1")); @@ -1477,7 +1477,7 @@ void cteWorksCorrectly() throws Exception { @Test void cteWorksCorrectly2() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101), (44, 101, 501, 102)"); //Assertions.assertTrue(statement.execute("with C1 (X, Y, Z) as (SELECT a, b, c from T1) select Y, Z from C1")); @@ -1497,7 +1497,7 @@ void cteWorksCorrectly2() throws Exception { @Test void cteWithColumnAliasesWorksCorrectly() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101), (44, 101, 501, 102)"); Assertions.assertTrue(statement.execute("with C1 (X, Y, Z) as (SELECT a, b, c from T1) select Y, Z from C1")); @@ -1516,7 +1516,7 @@ void cteWithColumnAliasesWorksCorrectly() throws Exception { void unionParenthesisIsNotSupported() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { RelationalAssertions.assertThrows(() -> ((EmbeddedRelationalStatement) statement) @@ -1529,7 +1529,7 @@ void unionParenthesisIsNotSupported() throws Exception { @Test void selfJoinTest() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20)"); Assertions.assertTrue(statement.execute("select * from t1 as x, t1 as y")); @@ -1552,7 +1552,7 @@ void selfJoinTest() throws Exception { void testInsertUuidTest() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a UUID, PRIMARY KEY(pk))"; final var actualUuidValue = UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0"); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (1, '14b387cd-79ad-4860-9588-9c4e81588af0')")) { final var numActualInserted = insert.executeUpdate(); Assertions.assertEquals(1, numActualInserted); @@ -1567,7 +1567,7 @@ void testInsertUuidTest() throws Exception { } } } - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (?pk, ?a)")) { insert.setLong("pk", 1L); insert.setUUID("a", actualUuidValue); @@ -1596,7 +1596,7 @@ void testInsertStructWithUuidTest() throws Exception { .addLong("A", 2L) .addUuid("B", actualUuidValue2) .build(); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (?pk, ?a, ?b)")) { insert.setLong("pk", 1L); insert.setUUID("a", actualUuidValue1); @@ -1632,7 +1632,7 @@ private static Stream vectorTypeProvider() { void vectorInsertSelectPrepared(String vectorType, RealVector vector) throws Exception { final String schemaTemplate = String.format("CREATE TABLE T1(pk bigint, v %s, PRIMARY KEY(pk))", vectorType); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (?pk, ?v)")) { insert.setLong("pk", 1L); insert.setObject("v", vector); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java index 8ddfbbdede..9f5edaa675 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java @@ -89,7 +89,7 @@ public TemporaryFunctionTests() { @Test void createTemporaryFunctionWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -106,7 +106,7 @@ void createTemporaryFunctionWorks() throws Exception { @Test void createTemporaryFunctionAcrossTransactionsWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -131,7 +131,7 @@ void createTemporaryFunctionAcrossTransactionsWorks() throws Exception { void createTemporaryFunctionWithNameCollisionsThrows() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) " + "create function foo() as select * from t1 where a < 43"; // add non-temporary function called foo - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); connection.setAutoCommit(false); try (var statement = connection.createStatement()) { @@ -152,7 +152,7 @@ void createTemporaryFunctionWithNameCollisionsThrows() throws Exception { @Test void temporaryFunctionVisibilityAcrossTransactionAfterCommit() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")) + try (var ddl = Ddl.builder().database() .relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); @@ -177,7 +177,7 @@ void temporaryFunctionVisibilityAcrossTransactionAfterCommit() throws Exception @Test void temporaryFunctionVisibilityAcrossTransactionsAfterRollback() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")) + try (var ddl = Ddl.builder().database() .relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); @@ -202,7 +202,7 @@ void temporaryFunctionVisibilityAcrossTransactionsAfterRollback() throws Excepti @Test void createOrReplaceTemporaryFunctionWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -220,7 +220,7 @@ void createOrReplaceTemporaryFunctionWorks() throws Exception { @Test void createOrReplaceTemporaryFunctionAndInvokeMultipleTimesWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -242,7 +242,7 @@ void createOrReplaceTemporaryFunctionAndInvokeMultipleTimesWorks() throws Except @Test void temporaryFunctionIsMemoizedAcrossInvocations() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -277,7 +277,7 @@ private UserDefinedFunction getUserDefinedFunction(@Nonnull RelationalConnection @Test void dropTemporaryFunctionWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -307,7 +307,7 @@ void dropTemporaryFunctionWorks() throws Exception { void dropNonTemporaryFunctionFails() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) " + "create function sq0(in x bigint) as select * from t1 where a > x"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -329,7 +329,7 @@ void dropNonTemporaryFunctionFails() throws Exception { @Test void dropTemporaryFunctionIfExistsWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -356,7 +356,7 @@ void dropTemporaryFunctionIfExistsWorks() throws Exception { @Test void dropTemporaryFunctionMultipleCallsWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -391,7 +391,7 @@ void dropTemporaryFunctionMultipleCallsWorks() throws Exception { @Test void dropNestedTemporaryFunctionCallsWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -436,7 +436,7 @@ void dropNestedTemporaryFunctionCallsWorks() throws Exception { @Test void createTemporaryFunctionSameNameThrows() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -456,7 +456,7 @@ void createTemporaryFunctionSameNameThrows() throws Exception { @Test void createTemporaryFunctionWithPreparedParameters() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -487,7 +487,7 @@ void createTemporaryFunctionWithPreparedParameters() throws Exception { @Test void createNestedTemporaryFunctionWithPreparedParameters() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -522,7 +522,7 @@ void createNestedTemporaryFunctionWithPreparedParameters() throws Exception { @Test void createTemporaryFunctionWithPreparedParametersWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -545,7 +545,7 @@ void createTemporaryFunctionWithPreparedParametersWorks() throws Exception { @Test void createNestedTemporaryFunctionsWithPreparedParameters() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -602,7 +602,7 @@ void createNestedTemporaryFunctionsWithPreparedParameters() throws Exception { @Test void createNestedTemporaryFunctionsWithVariousLiterals() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -653,7 +653,7 @@ void createNestedTemporaryFunctionsWithVariousLiterals() throws Exception { @Test void createNestedTemporaryFunctionsWithPreparedParametersOptimizationConstraintPertainingNestedFunction() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -721,7 +721,7 @@ void createNestedTemporaryFunctionsWithPreparedParametersOptimizationConstraintP @Test void createNestedTemporaryFunctionsWithLiteralsOptimizationConstraintPertainingNestedFunction() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -784,7 +784,7 @@ void createNestedTemporaryFunctionsWithLiteralsOptimizationConstraintPertainingN @Test void useMultipleReferencesOfTemporaryFunctionsWithPreparedParameters() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -870,7 +870,7 @@ void useMultipleReferencesOfTemporaryFunctionsWithPreparedParameters() throws Ex @Test void useMultipleReferencesOfTemporaryFunctionsWithPreparedParametersAcrossContinuations() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 15)"); } @@ -944,7 +944,7 @@ void useMultipleReferencesOfTemporaryFunctionsWithPreparedParametersAcrossContin @Test void useMultipleReferencesOfTemporaryFunctionsWithPreparedParametersContinuationsAcrossTransactions() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 15)"); } @@ -1048,7 +1048,7 @@ void useMultipleReferencesOfTemporaryFunctionsWithPreparedParametersContinuation void createTemporaryFunctionCaseSensitivityOption(boolean isCaseSensitive) throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; try (var ddl = Ddl.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, isCaseSensitive) - .database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + .database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -1068,7 +1068,7 @@ void createTemporaryFunctionCaseSensitivityOption(boolean isCaseSensitive) throw void attemptToCreateTemporaryFunctionWithDifferentCaseSensitivityOptionCase1() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; try (var ddl = Ddl.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true) - .database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + .database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -1092,7 +1092,7 @@ void attemptToCreateTemporaryFunctionWithDifferentCaseSensitivityOptionCase1() t void attemptToCreateTemporaryFunctionWithDifferentCaseSensitivityOptionCase2() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; try (var ddl = Ddl.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, false) - .database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + .database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -1161,7 +1161,7 @@ public LogicalOperator visitStatementBody(final RelationalParser.StatementBodyCo void unpivotRepeatedFieldInSqlFunctionWorksCorrectly() throws Exception { final String schemaTemplate = "create type as struct city(name string, population bigint) " + "create table country(id bigint, name string, continent string, cities city array, primary key(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into country values " + "(1, 'USA', 'North America', [('New York' ,8419600), ('Los Angeles', 3980400)]), " + diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java index 97b7b17c4b..f6bc74ebe7 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java @@ -49,7 +49,7 @@ public V2PlanGeneratorTests() { @Test void simpleQueryWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -68,7 +68,7 @@ void simpleQueryWorks() throws Exception { @Test void simpleQuery2Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -91,7 +91,7 @@ void simpleQuery2Works() throws Exception { @Test void simpleQuery3Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -114,7 +114,7 @@ void simpleQuery3Works() throws Exception { @Test void simpleSubquery4Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -133,7 +133,7 @@ void simpleSubquery4Works() throws Exception { @Test void simpleSubquery5Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -152,7 +152,7 @@ void simpleSubquery5Works() throws Exception { @Test void simpleSubquery6Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -171,7 +171,7 @@ void simpleSubquery6Works() throws Exception { @Test void simpleSubquery7Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -194,7 +194,7 @@ void simpleCorrelatedJoinWorks() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT Location (longitude bigint, latitude bigint) " + "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info array, b bigint array, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500)), (34, (100, 200))], [500, 501], 101), (43, [(2, (2000, 2200)), (9, (2000,2400))], [700, 701], 102)"); @@ -224,7 +224,7 @@ void starExpansionTest1() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -251,7 +251,7 @@ void starExpansionTest2() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -278,7 +278,7 @@ void starExpansionTest3() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -305,7 +305,7 @@ void starExpansionTest4() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -330,7 +330,7 @@ void starExpansionTest5() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -357,7 +357,7 @@ void starExpansionTest6() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -382,7 +382,7 @@ void starExpansionTest7() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -407,7 +407,7 @@ void starExpansionTest8() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info array, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500))], 500, 101), (43, [(34, (5001, 5501))], 501, 102)"); @@ -432,7 +432,7 @@ void starExpansionTestDisabled() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT Info (ratings bigint, nested1 bigint, nested2 bigint) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, 5000, 5500), 500, 101), (43, (34, 5001, 5501), 501, 102)"); @@ -460,7 +460,7 @@ void orderByTest1() throws Exception { "CREATE TABLE T1(pk bigint, i Info array, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select pk, i from t2 order by pk, i"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500))], 500, 101), (43, [(34, (5001, 5501))], 501, 102)"); @@ -486,7 +486,7 @@ void orderByTest2() throws Exception { "CREATE TABLE T1(pk bigint, i Info array, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select pk, i from t2 order by pk, i"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500))], 500, 101), (43, [(34, (5001, 5501))], 501, 102)"); @@ -512,7 +512,7 @@ void orderByTest3() throws Exception { "CREATE TABLE T1(pk bigint, i Info array, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select pk, i from t2 order by pk, i"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500))], 500, 101), (43, [(34, (5001, 5501))], 501, 102)"); @@ -527,7 +527,7 @@ void aggregateFunctionTest1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -552,7 +552,7 @@ void aggregateFunctionTest2() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -575,7 +575,7 @@ void aggregateFunctionTest3SelectStar() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension) + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension) .schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; @@ -593,7 +593,7 @@ void simpleArithmeticExpression1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -616,7 +616,7 @@ void simplePredicate1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -635,7 +635,7 @@ void simplePredicate2InPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -654,7 +654,7 @@ void simplePredicate3InPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -673,7 +673,7 @@ void simplePredicate4LikePredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 'hello'), (43, 15, 'world')"); @@ -692,7 +692,7 @@ void simplePredicate5LikePredicateWithEscape() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, '___abcdef'), (43, 15, 'world')"); @@ -711,7 +711,7 @@ void simplePredicate6IsNull() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -730,7 +730,7 @@ void simplePredicate6IsNotNull() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -754,7 +754,7 @@ void simplePredicate7ExistsPredicate() throws Exception { " create table b(idb integer, q integer, r integer, primary key(idb))\n" + " create index ib as select q from b\n" + " create index ir as select sq.f from r, (select f from r.nr) sq;"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into A values (1, 1), (2, 2), (3, 3)"); @@ -843,7 +843,7 @@ void simpleInsert1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -869,7 +869,7 @@ void visitSelectWithLimit() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -897,7 +897,7 @@ void updateStatement1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -920,7 +920,7 @@ void updateStatement3SetQualifiedField() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -943,7 +943,7 @@ void updateStatement4SetNestedField() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT Location (longitude bigint, latitude bigint) " + "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -971,7 +971,7 @@ void testNestedValues() throws Exception { " CREATE TYPE AS STRUCT B (c C)" + " CREATE TYPE AS STRUCT A (b B) " + "CREATE TABLE T1(pk bigint, a A, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (((((34, 35))))))"); @@ -997,7 +997,7 @@ void deleteStatement1() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT Location (longitude bigint, latitude bigint) " + "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java index d4d1495524..38700814f7 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java @@ -94,7 +94,7 @@ private void preparedQueryShouldHitCache(@Nonnull final RelationalConnection con @Test void constrainingLiteralTrueBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(ddl.getConnection(), "select a + 42 from t where b = true"); queryShouldHitCache(ddl.getConnection(), "select a + 42 from t where b = true"); @@ -112,7 +112,7 @@ void constrainingLiteralTrueBooleanWorks() throws Exception { @Test void constrainingPreparedTrueBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); preparedQueryShouldMissCache(connection, "select a + 42 from t where b = ?", ImmutableMap.of(1, true)); preparedQueryShouldHitCache(connection, "select a + 42 from t where b = ?", ImmutableMap.of(1, true)); @@ -132,7 +132,7 @@ void constrainingPreparedTrueBooleanWorks() throws Exception { @Test void constrainingLiteralFalseBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(connection, "select a + 42 from t where b = false"); queryShouldHitCache(connection, "select a + 42 from t where b = false"); @@ -151,7 +151,7 @@ void constrainingLiteralFalseBooleanWorks() throws Exception { @Test void constrainingPreparedFalseBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); preparedQueryShouldMissCache(connection, "select a + 42 from t where b = ?", ImmutableMap.of(1, false)); preparedQueryShouldHitCache(connection, "select a + 42 from t where b = ?", ImmutableMap.of(1, false)); @@ -171,7 +171,7 @@ void constrainingPreparedFalseBooleanWorks() throws Exception { @Test void constrainingLiteralNullBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(connection, "select a + 42 from t where b = null"); queryShouldHitCache(connection, "select a + 42 from t where b = null"); @@ -189,7 +189,7 @@ void constrainingLiteralNullBooleanWorks() throws Exception { @Test void constrainingPreparedNullBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); final Map map = new TreeMap<>(); map.put(1, null); @@ -209,7 +209,7 @@ void constrainingPreparedNullBooleanWorks() throws Exception { @Test void constrainingNonNullLiteralWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(connection, "select a + 43 from t where a = 42"); queryShouldHitCache(connection, "select a + 43 from t where a = 45"); // different literals are ok here (no index filters) @@ -223,7 +223,7 @@ void constrainingNonNullLiteralWorks() throws Exception { @Test void constrainingPreparedNotNullWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); preparedQueryShouldMissCache(connection, "select a + 43 from t where a = ?", ImmutableMap.of(1, 42)); preparedQueryShouldHitCache(connection, "select a + 43 from t where a = ?", ImmutableMap.of(1, 45)); // different literals are ok here (no index filters) @@ -239,7 +239,7 @@ void constrainingPreparedNotNullWorks() throws Exception { @Test void constrainingNullLiteralWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(connection, "select a + 43 from t where a = null"); @@ -254,7 +254,7 @@ void constrainingNullLiteralWorks() throws Exception { @Test void constrainingPreparedNullWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); final Map map = new TreeMap<>(); map.put(1, null); @@ -270,7 +270,7 @@ void constrainingPreparedNullWorks() throws Exception { @Test void sameQueryDifferentRewriteRulesEnablement() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); final Map map = new TreeMap<>(); map.put(1, null); @@ -305,7 +305,7 @@ void hnswQueriesWithDifferentRuntimeOptionsAreCachedCorrectly() throws Exception final var queryVector = new HalfRealVector(new double[] {1.2f, -0.4f, 3.14f}); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension) + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension) .schemaTemplate(schemaTemplate).schemaTemplateOptions((new SchemaTemplateRule.SchemaTemplateOptions(true, true))).build()) { final var connection = ddl.setSchemaAndGetConnection(); @@ -344,7 +344,7 @@ void hnswQueriesWithDifferentRuntimeOptionsAreCachedCorrectly() throws Exception @Test void sameQueryDifferentRightDeepJoinOption() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); final Map map = new TreeMap<>(); map.put(1, null); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java index 028131a31c..32e29e674a 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java @@ -56,7 +56,7 @@ public SqlVisitorTests() { @Test public void addExtraExpressionsToSetAndWhereClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -77,7 +77,7 @@ public void addExtraExpressionsToSetAndWhereClause() throws Exception { @Test public void addReturning() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -91,7 +91,7 @@ public void addReturning() throws Exception { @Test public void addNestedWhereClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -105,7 +105,7 @@ public void addNestedWhereClause() throws Exception { @Test public void addQueryOptions() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); updateBuilder.withOption(StructuredQuery.QueryOptions.DRY_RUN); @@ -119,7 +119,7 @@ public void addQueryOptions() throws Exception { @Test public void rewriteColumnAliases() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set customer_facing_a = 42 where CUSTOMER_FACING_PK = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement, Map.of("CUSTOMER_FACING_A", List.of("a"), "CUSTOMER_FACING_PK", List.of("pk"))); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -133,7 +133,7 @@ public void rewriteColumnAliases() throws Exception { @Test public void rewriteColumnAliasesQuotes() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var connection = ddl.setSchemaAndGetConnection(); connection.setOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true); final var updateStatement = "update T1 set \"customer_facing_a\" = 42 where \"CUSTOMER_FACING_PK\" = 444"; @@ -149,7 +149,7 @@ public void rewriteColumnAliasesQuotes() throws Exception { @Test public void rewriteColumnAliasesInsideUdf() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set customer_facing_a = 42 where CUSTOMER_FACING_PK = greatest(42, customer_FACING_B)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement, Map.of("CUSTOMER_FACING_A", List.of("a"), diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java index 7aeb6fb5ef..c03e94a75f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java @@ -56,7 +56,7 @@ public StatementBuilderTests() { @Test void addExtraSetClauseToUpdate() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -70,7 +70,7 @@ void addExtraSetClauseToUpdate() throws Exception { @Test void setSameFieldMultipleTimes() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -86,7 +86,7 @@ void setSameFieldMultipleTimes() throws Exception { @Test void removeSetFieldClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, b = 44 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -100,7 +100,7 @@ void removeSetFieldClause() throws Exception { @Test void removeAllSetFieldClausesThrows() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set b = 44 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -114,7 +114,7 @@ void removeAllSetFieldClausesThrows() throws Exception { @Test void examineSetFields() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); var setFields = updateBuilder.getSetClauses().keySet(); @@ -133,7 +133,7 @@ void examineSetFields() throws Exception { @Test void examineWhereClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); var whereClause = updateBuilder.getWhereClause(); @@ -147,7 +147,7 @@ void examineWhereClause() throws Exception { @Test void examineMultipleWhereClauses() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444 AND (a < 42)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); var whereClause = updateBuilder.getWhereClause(); @@ -176,7 +176,7 @@ static Stream queryOptionsParameters() { @MethodSource("queryOptionsParameters") void examineQueryOptions(@Nonnull final String queryOptionsClause, @Nonnull final Set expectedOptions) throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444 AND (a < 42) " + queryOptionsClause; var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); assertThat(updateBuilder.getOptions()) @@ -187,7 +187,7 @@ void examineQueryOptions(@Nonnull final String queryOptionsClause, @Nonnull fina @Test void addWhereClauses() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444 AND (a < 42)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -204,7 +204,7 @@ void addWhereClauses() throws Exception { @Test void examineReturning() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' returning *"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var returning = updateBuilder.getReturning(); @@ -218,7 +218,7 @@ void examineReturning() throws Exception { @Test void examineReturningMultipleColumns() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' returning \"old\".a, b, *, c+1, d + (5 + 4)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var returning = updateBuilder.getReturning(); @@ -240,7 +240,7 @@ void examineReturningMultipleColumns() throws Exception { @Test void setReturningClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' returning \"old\".a, b, *, c+1, d + (5 + 4)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); var returning = updateBuilder.getReturning(); @@ -271,7 +271,7 @@ void setReturningClause() throws Exception { @Test void greatestFunction() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -296,7 +296,7 @@ void greatestFunction() throws Exception { @Test void mathInSetClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java index 9578aeb4c9..6f2eb9ea3f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java @@ -34,6 +34,7 @@ import java.net.URISyntaxException; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.concurrent.ThreadLocalRandom; public class Ddl implements AutoCloseable { @Nonnull @@ -156,6 +157,24 @@ public Builder database(@Nonnull final URI dbName) throws URISyntaxException { return this; } + /** + * Sets the database path to a freshly-generated unique value. Use this when the test + * doesn't care about the specific database name and just needs an isolated database for + * its scope. Under parallel JUnit class execution, tests that hard-code the same path + * (e.g. {@code /TEST/QT}) trip over each other's catalog state; this overload sidesteps + * that by giving every {@link Ddl} instance a unique path. + */ + @Nonnull + public Builder database() { + // 16 random hex chars = 64 bits of entropy — enough to avoid collisions across a + // single test run and short enough to keep error messages readable. We deliberately + // avoid a UUID to keep the path SQL-identifier-safe and human-scannable. + final String uniqueSuffix = Long.toHexString(ThreadLocalRandom.current().nextLong()) + + Long.toHexString(ThreadLocalRandom.current().nextLong()); + database = URI.create("/TEST/AUTO_" + uniqueSuffix); + return this; + } + @Nonnull public Builder schemaTemplate(@Nonnull final String schemaTemplate) { this.templateDefinition = schemaTemplate; From a6702399550fae786565171ce2afff28b130609b Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 16:54:26 -0400 Subject: [PATCH 38/74] Add synchronization & retry to more catalog operations --- .../EmbeddedRelationalExtension.java | 17 +++++-- .../relational/utils/CatalogOperations.java | 50 +++++++++++++++++-- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java index 9df631bbce..647d29bd0e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java @@ -35,6 +35,7 @@ import com.apple.foundationdb.relational.recordlayer.catalog.StoreCatalogProvider; import com.apple.foundationdb.relational.recordlayer.ddl.RecordLayerMetadataOperationsFactory; import com.apple.foundationdb.relational.recordlayer.query.cache.RelationalPlanCache; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.test.FDBTestEnvironment; import com.codahale.metrics.MetricRegistry; import org.junit.jupiter.api.extension.AfterEachCallback; @@ -126,11 +127,17 @@ public EmbeddedRelationalDriver getDriver(@Nonnull final FormatVersion formatVer private void makeDatabase(String clusterFile) throws RelationalException { database = FDBDatabaseFactory.instance().getDatabase(clusterFile); - try (var connection = new DirectFdbConnection(database); - Transaction txn = connection.getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); - } + // The catalog-bootstrap transaction below writes the cluster-wide catalog metadata, which + // races with every other test class's @BeforeEach. Without the JVM-wide lock, parallel + // class execution surfaces these races as `FDBStoreTransactionConflictException`. See + // CatalogOperations for the lock contract. + CatalogOperations.runLockedWithRelationalRetry(() -> { + try (var connection = new DirectFdbConnection(database); + Transaction txn = connection.getTransactionManager().createTransaction(Options.NONE)) { + storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + } + }); } private EmbeddedRelationalEngine makeEngine(final @Nonnull FDBDatabase database, final @Nonnull FormatVersion formatVersion) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java index 21b092a007..8863a7b105 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.exceptions.RelationalException; import javax.annotation.Nonnull; import java.sql.SQLException; @@ -64,6 +65,16 @@ public interface ThrowingRunnable { void run() throws SQLException; } + /** + * Functional interface for catalog actions that may throw {@link RelationalException} + * (used by extensions that talk to the catalog via the lower-level Transaction API rather + * than JDBC, e.g. {@code EmbeddedRelationalExtension.makeDatabase}). + */ + @FunctionalInterface + public interface RelationalThrowingRunnable { + void run() throws RelationalException; + } + /** * Runs {@code action} under the JVM-wide catalog lock with a small retry loop on SQLSTATE * 40001 transaction conflicts. Use for any CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, @@ -83,17 +94,44 @@ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) th if (attempt >= MAX_ATTEMPTS || !isTransactionConflict(e)) { throw e; } - try { - Thread.sleep(10L * attempt); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); + sleepBeforeRetry(attempt, e); + } + } + } + } + + /** + * {@link RelationalException}-throwing variant of {@link #runLockedWithRetry(ThrowingRunnable)}. + * Same lock and retry policy; detects conflicts via either {@link SQLException} SQLSTATE + * 40001 or {@link RelationalException} carrying {@link ErrorCode#SERIALIZATION_FAILURE}. + * Named distinctly because Java can't disambiguate lambdas between the two + * functional-interface overloads (their abstract methods differ only in their throws clause). + */ + public static void runLockedWithRelationalRetry(@Nonnull final RelationalThrowingRunnable action) throws RelationalException { + synchronized (CATALOG_LOCK) { + for (int attempt = 1; ; attempt++) { + try { + action.run(); + return; + } catch (RelationalException e) { + if (attempt >= MAX_ATTEMPTS || !isTransactionConflict(e)) { throw e; } + sleepBeforeRetry(attempt, e); } } } } + private static void sleepBeforeRetry(final int attempt, @Nonnull final E pending) throws E { + try { + Thread.sleep(10L * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw pending; + } + } + private static boolean isTransactionConflict(@Nonnull final Throwable t) { Throwable cursor = t; while (cursor != null) { @@ -101,6 +139,10 @@ private static boolean isTransactionConflict(@Nonnull final Throwable t) { && ErrorCode.SERIALIZATION_FAILURE.getErrorCode().equals(((SQLException) cursor).getSQLState())) { return true; } + if (cursor instanceof RelationalException + && ((RelationalException) cursor).getErrorCode() == ErrorCode.SERIALIZATION_FAILURE) { + return true; + } cursor = cursor.getCause(); } return false; From e496e93d015fb912c671a7f50dbe478dda10055b Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 26 Jun 2026 21:38:54 -0400 Subject: [PATCH 39/74] Add random DB suffix to some more tests --- .../relational/recordlayer/AutoCommitTests.java | 2 +- .../relational/recordlayer/BasicMetadataTest.java | 4 ++-- .../relational/utils/SimpleDatabaseRule.java | 10 +++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java index f3ab88316d..50dfafd60f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java @@ -461,7 +461,7 @@ public void catalogMetadata(TransactionType transactionType) throws SQLException conn = getConnectionWithExistingTransaction(conn, database.getConnectionUri(), alternateDriver); } setAutoCommit(conn, transactionType); - try (final var rs = conn.getMetaData().getTables("/TEST/AutoCommitTests", "TEST_SCHEMA", null, null)) { + try (final var rs = conn.getMetaData().getTables(database.getDatabasePath().getPath(), "TEST_SCHEMA", null, null)) { ResultSetAssert.assertThat(rs) .hasNextRow() .hasNextRow() diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java index 3f67c03c99..6bab256e3d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java @@ -162,10 +162,10 @@ void canGetTableColumns() throws SQLException { void canGetTableIndexes() throws SQLException { final RelationalDatabaseMetaData metaData = dbConn.getMetaData(); Assertions.assertNotNull(metaData, "Null metadata returned"); - try (final RelationalResultSet tableData = metaData.getIndexInfo("/TEST/BasicMetadataTest", "TEST_SCHEMA", "RESTAURANT_REVIEWER", false, false)) { + try (final RelationalResultSet tableData = metaData.getIndexInfo(database.getDatabasePath().getPath(), "TEST_SCHEMA", "RESTAURANT_REVIEWER", false, false)) { ResultSetAssert.assertThat(tableData).hasNextRow() .isRowExactly( - "/TEST/BasicMetadataTest", //table_cat + database.getDatabasePath().getPath(), //table_cat "TEST_SCHEMA", //table_schem "RESTAURANT_REVIEWER", //table_name false, //non_unique diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java index 60b7310f1b..297b490ddb 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java @@ -29,6 +29,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.URI; +import java.util.concurrent.ThreadLocalRandom; /** * A JUnit extension that automatically creates all the framework necessary for a unique database and schema. @@ -55,7 +56,14 @@ public SimpleDatabaseRule(@Nonnull Class testClass, @Nonnull Options connectionOptions, @Nullable SchemaTemplateRule.SchemaTemplateOptions templateOptions) { final var schemaName = "TEST_SCHEMA"; - final var dbPath = URI.create("/TEST/" + testClass.getSimpleName()); + // Per-instance unique suffix so that two instances of this rule (whether from + // concurrent test classes, or from a fresh run after a prior run crashed without + // cleaning up) never collide on the same database path. The test class name is kept as + // a prefix to make path strings in error messages and FDB tracing self-identifying. + // 16 random hex chars = 64 bits of entropy — enough to avoid collisions across any + // realistic test run and short enough to keep names scannable. + final var uniqueSuffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + final var dbPath = URI.create("/TEST/" + testClass.getSimpleName() + "_" + uniqueSuffix); final var templateName = dbPath.getPath().substring(dbPath.getPath().lastIndexOf("/") + 1); this.templateRule = new SchemaTemplateRule(templateName + "_TEMPLATE", Options.none(), templateOptions, templateDefinition); From 1c009ab1fd7decda30c1e6cb039ef6553924b6a1 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 11:30:10 -0400 Subject: [PATCH 40/74] Change RelationalExtension to not register FRL Registering and de-regestering FRL causes issues with running the tests in parallel. Instead have the relational extension provide a driver for tests and other extensions to use. --- .../relational/SqlInsertTest.java | 2 +- .../relational/api/DriverManagerTest.java | 7 +++ .../api/ddl/DdlStatementParsingTest.java | 4 +- .../relational/api/ddl/IndexTest.java | 4 +- .../relational/api/ddl/SqlFunctionTest.java | 4 +- .../cases/DirectPrimaryKeyScanTest.java | 2 +- .../autotest/cases/KnownPkGetTest.java | 2 +- .../recordlayer/AutoCommitTests.java | 4 +- .../recordlayer/BasicMetadataTest.java | 4 +- .../CaseSensitiveDbObjectsTest.java | 4 +- .../recordlayer/CaseSensitivityTest.java | 19 ++++---- .../relational/recordlayer/CursorTest.java | 2 +- .../DeleteRangeNoMetadataKeyTest.java | 4 +- .../recordlayer/DeleteRangeTest.java | 4 +- .../EmbeddedRelationalExtension.java | 45 ++++++++++++++++--- .../relational/recordlayer/InsertTest.java | 2 +- .../recordlayer/IntermingledTablesTest.java | 4 +- .../recordlayer/JoinWithLimitTest.java | 4 +- .../OfflinePlanGenerationTest.java | 4 +- .../recordlayer/OptionScopeTest.java | 6 ++- .../recordlayer/PlanGenerationStackTest.java | 4 +- .../recordlayer/QueryLoggingTest.java | 20 ++++----- .../recordlayer/QueryPropertiesTest.java | 2 +- .../recordlayer/RecordTypeKeyTest.java | 4 +- .../recordlayer/RecordTypeTableSerDeTest.java | 4 +- .../recordlayer/RelationalArrayTest.java | 2 +- .../recordlayer/RelationalConnectionRule.java | 11 +++-- .../recordlayer/RelationalExtension.java | 8 ++++ .../recordlayer/ResultSetMetaDataTest.java | 4 +- .../SimpleDirectAccessInsertionTests.java | 16 ++++--- .../recordlayer/SqlFunctionsTest.java | 4 +- .../recordlayer/StructDataMetadataTest.java | 4 +- .../recordlayer/SystemCatalogQueryTest.java | 12 ++--- .../recordlayer/TableMetadataVersionTest.java | 4 +- .../relational/recordlayer/TableTest.java | 4 +- .../recordlayer/TableWithEnumTest.java | 4 +- .../recordlayer/TableWithNoPkTest.java | 16 ++++--- .../TransactionBoundDatabaseTest.java | 4 +- .../TransactionBoundDatabaseWithEnumTest.java | 4 +- .../recordlayer/TransactionConfigTest.java | 2 +- .../recordlayer/UniqueIndexTests.java | 4 +- .../recordlayer/VectorDirectAccessTest.java | 2 +- .../recordlayer/ddl/DdlDatabaseTest.java | 17 +++---- .../ddl/DdlRecordLayerSchemaTemplateTest.java | 3 +- .../ddl/DdlRecordLayerSchemaTest.java | 14 +++--- .../metric/MetricsCollectionTest.java | 2 +- .../recordlayer/query/CountQueryTest.java | 4 +- .../query/ExecutePropertyTests.java | 14 +++--- .../query/ForceContinuationQueryTests.java | 4 +- .../query/TransactionBoundQueryTest.java | 35 +++++++-------- .../recordlayer/query/UpdateTest.java | 7 +-- .../query/cache/ConstraintValidityTests.java | 4 +- .../query/cache/RelationalPlanCacheTests.java | 4 +- .../query/functions/IncarnationTest.java | 2 +- .../relational/utils/ConnectionUtils.java | 5 --- .../relational/utils/DatabaseRule.java | 22 +++++++-- .../foundationdb/relational/utils/Ddl.java | 14 +++--- .../relational/utils/SchemaRule.java | 23 ++++++++-- .../relational/utils/SchemaTemplateRule.java | 38 ++++++++++++---- .../relational/utils/SimpleDatabaseRule.java | 25 ++++++----- 60 files changed, 310 insertions(+), 197 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java index 64bf2a3a16..c1ae43577b 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java @@ -52,7 +52,7 @@ public class SqlInsertTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(BasicMetadataTest.class, + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, BasicMetadataTest.class, """ CREATE TABLE simple (rest_no bigint, name string, primary key(rest_no)) CREATE TYPE AS STRUCT location (address string, latitude string, longitude string) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java index 7474d9a106..f8f4a7d5d3 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java @@ -25,6 +25,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -34,6 +36,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +// Runs SAME_THREAD because @BeforeEach nukes every driver registered with java.sql.DriverManager +// (which is JVM-global). Allowing this class to run concurrently with anything else that touches +// DriverManager — i.e. most of the relational-core tests via EmbeddedRelationalExtension — would +// race and pull the driver out from under those tests. +@Execution(ExecutionMode.SAME_THREAD) class DriverManagerTest { RelationalDriver driver1 = Mockito.mock(RelationalDriver.class); RelationalDriver driver2 = Mockito.mock(RelationalDriver.class); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java index e604d9b084..1d906f4315 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java @@ -106,12 +106,12 @@ public class DdlStatementParsingTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DdlStatementParsingTest.class, TestSchemas.books(), + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DdlStatementParsingTest.class, TestSchemas.books(), Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build(), null); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA") .withOptions(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java index e17f22f4d4..2b1edf5738 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java @@ -78,11 +78,11 @@ public class IndexTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DdlStatementParsingTest.class, TestSchemas.books()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DdlStatementParsingTest.class, TestSchemas.books()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @BeforeAll diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/SqlFunctionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/SqlFunctionTest.java index 15fc4e46ca..54c29057cd 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/SqlFunctionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/SqlFunctionTest.java @@ -64,11 +64,11 @@ public class SqlFunctionTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DdlStatementParsingTest.class, TestSchemas.books()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DdlStatementParsingTest.class, TestSchemas.books()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @BeforeAll diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java index 8d7a6c2cad..78048fd45e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java @@ -78,7 +78,7 @@ public class DirectPrimaryKeyScanTest { public Connector relationalConnector = new Connector() { @Override public RelationalConnection connect(URI dbUri) throws SQLException { - return DriverManager.getConnection("jdbc:embed:" + dbUri.getPath()).unwrap(RelationalConnection.class); + return relational.getDriver().connect(URI.create("jdbc:embed:" + dbUri.getPath())).unwrap(RelationalConnection.class); } @Override diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java index 576b70408c..6f20bf1119 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java @@ -82,7 +82,7 @@ public KnownPkGetTest() { public Connector relationalConnector = new Connector() { @Override public RelationalConnection connect(URI dbUri) throws SQLException { - return DriverManager.getConnection("jdbc:embed:" + dbUri.getPath()).unwrap(RelationalConnection.class); + return relational.getDriver().connect(URI.create("jdbc:embed:" + dbUri.getPath())).unwrap(RelationalConnection.class); } @Override diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java index 50dfafd60f..bb8d1b46f0 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java @@ -54,11 +54,11 @@ public class AutoCommitTests { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(AutoCommitTests.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, AutoCommitTests.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java index 6bab256e3d..a5e151947e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java @@ -54,12 +54,12 @@ public class BasicMetadataTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, BasicMetadataTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule dbConn = new RelationalConnectionRule(database::getConnectionUri); + public final RelationalConnectionRule dbConn = new RelationalConnectionRule(relationalExtension, database::getConnectionUri); @Test void canGetPrimaryKeysForTable() throws SQLException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java index 5e0ea17305..b9f03250b6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java @@ -56,13 +56,13 @@ public class CaseSensitiveDbObjectsTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(CaseSensitiveDbObjectsTest.class, + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, CaseSensitiveDbObjectsTest.class, SCHEMA_TEMPLATE, Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build(), new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java index 918b6bb167..dc7a9c2339 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java @@ -31,6 +31,7 @@ import com.apple.foundationdb.relational.utils.Ddl; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.RelationalAssertions; +import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; @@ -77,7 +78,7 @@ void selectFromCaseInsensitiveTable() throws Exception { @Test public void databaseWithSameUpperName() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement()) { //create a database @@ -106,7 +107,7 @@ private String quote(String dbObject, boolean quote) { @ValueSource(booleans = {true, false}) public void variousDatabases(boolean quoted) throws Exception { List databases = List.of("/TEST/ABC1", "/TEST/def2", "/TEST/Ghi3", "/TEST/jKL4"); - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try { try (final var statement = conn.createStatement()) { @@ -142,7 +143,7 @@ public void variousDatabases(boolean quoted) throws Exception { @ValueSource(booleans = {true, false}) public void variousSchemas(boolean quoted) throws Exception { List schemas = List.of("ABC2", "def3", "Ghi4", "jKL5"); - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try { try (final var statement = conn.createStatement()) { @@ -179,7 +180,7 @@ public void variousSchemas(boolean quoted) throws Exception { "SchemaTemplateCatalog))") public void variousStructs(boolean quoted) throws Exception { List structs = List.of(/*"ABC1",*/ "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try { try (RelationalStatement statement = conn.createStatement()) { @@ -204,7 +205,7 @@ public void variousStructs(boolean quoted) throws Exception { @ValueSource(booleans = {true, false}) public void variousTables(boolean quoted) throws Exception { List tables = List.of("ABC1", "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try { try (RelationalStatement statement = conn.createStatement()) { @@ -245,7 +246,7 @@ public void variousTables(boolean quoted) throws Exception { @ValueSource(booleans = {true, false}) public void variousColumns(boolean quoted) throws Exception { List columns = List.of("ABC1", "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try { try (RelationalStatement statement = conn.createStatement()) { @@ -286,7 +287,7 @@ public void overload() throws Exception { List schemas = List.of("schema", "SCHEMA", "Schema", "ScHeMa"); List tables = List.of("table", "TABLE", "Table", "TaBlE"); List columns = List.of("column", "COLUMN", "Column", "CoLuMn"); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try { try (RelationalStatement statement = conn.createStatement()) { @@ -315,7 +316,7 @@ public void overload() throws Exception { } long value = 0; // Data insertion - try (RelationalConnection dbConn = DriverManager.getConnection("jdbc:embed:" + database).unwrap(RelationalConnection.class)) { + try (RelationalConnection dbConn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:" + database)).unwrap(RelationalConnection.class)) { try (RelationalStatement statement = dbConn.createStatement()) { for (String schema : schemas) { dbConn.setSchema(schema); @@ -331,7 +332,7 @@ public void overload() throws Exception { } value = 0; // Data retrieval - try (RelationalConnection dbConn = DriverManager.getConnection("jdbc:embed:" + database).unwrap(RelationalConnection.class)) { + try (RelationalConnection dbConn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:" + database)).unwrap(RelationalConnection.class)) { try (RelationalStatement statement = dbConn.createStatement()) { for (String schema : schemas) { dbConn.setSchema(schema); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java index b4968acdda..7e205b1edc 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java @@ -54,7 +54,7 @@ public class CursorTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, CursorTest.class, TestSchemas.restaurant()); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java index 3e652e9041..b3d086f622 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java @@ -59,11 +59,11 @@ CREATE TABLE t2 (id bigint, a string, b string, e bigint, f boolean, PRIMARY KEY @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DeleteRangeNoMetadataKeyTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DeleteRangeNoMetadataKeyTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java index a712ea8563..cb9cadfda7 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java @@ -52,11 +52,11 @@ public class DeleteRangeTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DeleteRangeTest.class, SCHEMA_TEMPLATE); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DeleteRangeTest.class, SCHEMA_TEMPLATE); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java index 647d29bd0e..4577d5cfcb 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java @@ -51,6 +51,17 @@ public class EmbeddedRelationalExtension implements RelationalExtension, BeforeEachCallback, AfterEachCallback { + /** + * One driver kept registered with {@link DriverManager} for the JVM's lifetime. A handful of + * test bodies still call {@code DriverManager.getConnection("jdbc:embed:...")} directly; they + * need at least one driver in the registry. Registered idempotently on the first extension + * setup, never deregistered, so it can never disappear mid-test the way the per-extension + * driver used to. Test helpers that go through this extension (Schema/Database/SchemaTemplate + * rules, {@code Ddl}, {@code RelationalConnectionRule}) instead use the per-extension + * {@link #getDriver() driver} directly and never touch DriverManager. + */ + private static volatile RelationalDriver fallbackRegisteredDriver; + @Nonnull private final KeySpace keySpace; @@ -90,12 +101,10 @@ public EmbeddedRelationalExtension(final String clusterFile, final boolean regis @Override public void afterEach(ExtensionContext ignored) throws Exception { - if (driver != null) { - if (register) { - DriverManager.deregisterDriver(driver); - } - driver = null; - } + // Drop the per-instance driver reference. We do NOT deregister from DriverManager — + // that's what caused the parallel-test race we used to hit (one class's afterEach pulled + // the driver out from under another class's mid-flight test). + driver = null; } @Override @@ -117,7 +126,29 @@ private void setup() throws RelationalException, SQLException { engine = makeEngine(database, FormatVersion.getDefaultFormatVersion()); driver = new EmbeddedRelationalDriver(engine); if (register) { - DriverManager.registerDriver(driver); + // Keep ONE driver permanently registered for the small number of test bodies that + // still call DriverManager.getConnection("jdbc:embed:...") directly. The fallback + // is a SEPARATE driver instance (not the per-extension `driver` above) so that + // tests which deregister/re-register their own driver — e.g. TransactionBoundQueryTest + // — can never knock the fallback out from under other concurrent tests. + ensureFallbackDriverRegistered(engine); + } + } + + private static synchronized void ensureFallbackDriverRegistered(@Nonnull final EmbeddedRelationalEngine engineToWrap) throws SQLException { + if (fallbackRegisteredDriver == null) { + // Brand-new driver instance wrapping the first extension's engine. No test ever + // gets a reference to this object (extensions return their own `driver` field + // instead), so deregistering "the extension driver" can't pull this one out. + fallbackRegisteredDriver = new EmbeddedRelationalDriver(engineToWrap); + } + // Check on every call whether the fallback is still in DriverManager's registry — + // DriverManagerTest's @BeforeEach deliberately nukes every registered driver, so we may + // need to re-add ours each time a test that depends on it sets up. + final RelationalDriver fallback = fallbackRegisteredDriver; + final boolean stillRegistered = DriverManager.drivers().anyMatch(d -> d == fallback); + if (!stillRegistered) { + DriverManager.registerDriver(fallback); } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java index ec27af321d..9df2bd8fc8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java @@ -53,7 +53,7 @@ public class InsertTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(InsertTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, InsertTest.class, TestSchemas.restaurant()); @Test void canInsertWithMultipleRecordTypes() throws SQLException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IntermingledTablesTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IntermingledTablesTest.java index 15f01e5922..104d77691c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IntermingledTablesTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IntermingledTablesTest.java @@ -55,11 +55,11 @@ CREATE TABLE t2 (group bigint, id string, val2 string, PRIMARY KEY(group, id)) @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(IntermingledTablesTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, IntermingledTablesTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/JoinWithLimitTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/JoinWithLimitTest.java index 7849a88605..7878f9601d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/JoinWithLimitTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/JoinWithLimitTest.java @@ -49,11 +49,11 @@ CREATE TABLE Q(qpk bigint, d D array, PRIMARY KEY(qpk)) @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(UniqueIndexTests.class, getTemplate_definition); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, UniqueIndexTests.class, getTemplate_definition); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(db::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, db::getConnectionUri) .withSchema(db.getSchemaName()); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OfflinePlanGenerationTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OfflinePlanGenerationTest.java index d3d93166fa..3578de699b 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OfflinePlanGenerationTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OfflinePlanGenerationTest.java @@ -58,12 +58,12 @@ class OfflinePlanGenerationTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, OfflinePlanGenerationTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java index ce8c92d5ae..6072e0db51 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java @@ -27,6 +27,7 @@ import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.TestSchemas; +import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Order; @@ -38,6 +39,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.net.URI; public class OptionScopeTest { @@ -51,7 +53,7 @@ public class OptionScopeTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(UniqueIndexTests.class, TestSchemas.books()); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, UniqueIndexTests.class, TestSchemas.books()); @Test public void optionTakenFromConnection() throws SQLException, RelationalException { @@ -69,7 +71,7 @@ public void optionTakenFromConnection() throws SQLException, RelationalException @Test public void optionTakenFromQuery() throws SQLException { - try (Connection conn = DriverManager.getConnection(db.getConnectionUri().toString())) { + try (Connection conn = relationalExtension.getDriver().connect(URI.create(db.getConnectionUri().toString()))) { conn.setSchema(db.getSchemaName()); try (Statement statement = conn.createStatement()) { Assertions.assertThat(statement.executeUpdate(INSERT_QUERY_DRY_RUN)).isOne(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/PlanGenerationStackTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/PlanGenerationStackTest.java index ac08ab221d..1eec5c9055 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/PlanGenerationStackTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/PlanGenerationStackTest.java @@ -63,11 +63,11 @@ public class PlanGenerationStackTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(PlanGenerationStackTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, PlanGenerationStackTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java index b10eb913b4..a09e54316d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java @@ -59,11 +59,11 @@ public class QueryLoggingTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(QueryLoggingTest.class, TestSchemas.restaurantWithCoveringIndex()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, QueryLoggingTest.class, TestSchemas.restaurantWithCoveringIndex()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); @@ -133,7 +133,7 @@ void testNoLogIfMissing() throws Exception { @Test void testRelationalConnectionOptionPreparedStatement() throws Exception { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.LOG_QUERY, true).build())) { conn.setSchema(database.getSchemaName()); try (PreparedStatement ps = conn.prepareStatement("SELECT name from restaurant where rest_no = ?")) { @@ -154,7 +154,7 @@ void testRelationalConnectionOptionPreparedStatement() throws Exception { @Test void testRelationalConnectionOptionExplicitlyDisabled() throws Exception { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.LOG_QUERY, false).build())) { conn.setSchema(database.getSchemaName()); try (PreparedStatement ps = conn.prepareStatement("SELECT name from restaurant where rest_no = ?")) { @@ -175,7 +175,7 @@ void testRelationalConnectionOptionExplicitlyDisabled() throws Exception { @Test void testRelationalConnectionSetLogOnThenOff() throws Exception { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.NONE)) { conn.setSchema(database.getSchemaName()); try (Statement stmt = conn.createStatement()) { @@ -208,7 +208,7 @@ void testRelationalConnectionSetLogOnThenOff() throws Exception { @Test void testRelationalConnectionSetLogIsOverriddenByQueryOption() throws Exception { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.NONE)) { conn.setSchema(database.getSchemaName()); conn.setOption(Options.Name.LOG_QUERY, false); @@ -226,7 +226,7 @@ void testRelationalConnectionSetLogIsOverriddenByQueryOption() throws Exception @Test void testRelationalConnectionSetLogIsOverriddenByExecuteContinuationQueryOption() throws Exception { insertRows(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.NONE)) { Continuation continuation; conn.setSchema(database.getSchemaName()); @@ -254,7 +254,7 @@ void testRelationalConnectionSetLogIsOverriddenByExecuteContinuationQueryOption( @ValueSource(booleans = {true, false}) void testRelationalConnectionSetLogWithExecuteContinuation(boolean setLogging) throws Exception { insertRows(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.NONE)) { Continuation continuation; conn.setSchema(database.getSchemaName()); @@ -298,7 +298,7 @@ void testLogQueryBecauseQueryIsSlow() throws Exception { resultSet.next(); } Assertions.assertThat(logAppender.getLogEvents()).isEmpty(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.LOG_SLOW_QUERY_THRESHOLD_MICROS, 1L).build())) { conn.setSchema(database.getSchemaName()); try (PreparedStatement ps = conn.prepareStatement("SELECT NAME FROM RESTAURANT")) { @@ -316,7 +316,7 @@ void testLogQueryBecauseQueryIsSlowRemovesLiterals() throws Exception { resultSet.next(); } Assertions.assertThat(logAppender.getLogEvents()).isEmpty(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.LOG_SLOW_QUERY_THRESHOLD_MICROS, 1L).build())) { conn.setSchema(database.getSchemaName()); try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM RESTAURANT WHERE \"NAME\" = 'restaurant 1'")) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java index 8e6d94fe6b..2be9a04b19 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java @@ -57,7 +57,7 @@ public class QueryPropertiesTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(QueryPropertiesTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, QueryPropertiesTest.class, TestSchemas.restaurant()); @Test void verifyExecuteAndScanPropertiesGivenQueryProperties() throws SQLException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeKeyTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeKeyTest.java index 21cd11b2cb..4eca57ee5d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeKeyTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeKeyTest.java @@ -43,7 +43,7 @@ public class RecordTypeKeyTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, RecordTypeKeyTest.class, """ CREATE TABLE restaurant_review (reviewer bigint, rating bigint, SINGLE ROW ONLY) @@ -54,7 +54,7 @@ CREATE TABLE restaurant_tag (tag string, weight bigint, PRIMARY KEY(tag)) @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTableSerDeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTableSerDeTest.java index 6236e5c082..52e4a321db 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTableSerDeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTableSerDeTest.java @@ -45,14 +45,14 @@ class RecordTypeTableSerDeTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(RecordTypeTableSerDeTest.class, + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, RecordTypeTableSerDeTest.class, """ CREATE TABLE t1(a bigint, b string, c bytes, d boolean, e integer, f float, g double, h uuid, PRIMARY KEY(a)) """); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(db::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, db::getConnectionUri) .withOptions(Options.NONE) .withSchema(db.getSchemaName()); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java index 7b7d08030c..1560981ff5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java @@ -82,7 +82,7 @@ CREATE TABLE B(pk integer, uuid_null uuid array, primary key(pk)) @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(RelationalArrayTest.class, SCHEMA_TEMPLATE); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, RelationalArrayTest.class, SCHEMA_TEMPLATE); public void insertQuery(@Nonnull String q) throws SQLException { try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalConnectionRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalConnectionRule.java index f35d8d2055..6e8a05564a 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalConnectionRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalConnectionRule.java @@ -36,9 +36,9 @@ import javax.annotation.Nonnull; import java.net.URI; import java.sql.Array; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Struct; +import java.util.Objects; import java.util.function.Supplier; public class RelationalConnectionRule implements BeforeEachCallback, AfterEachCallback, RelationalConnection { @@ -54,8 +54,13 @@ public RelationalConnectionRule(SqlSupplier driverSupplier, Su this.dbPathSupplier = dbPathSupplier; } - public RelationalConnectionRule(Supplier dbPathSupplier) { - this(() -> (RelationalDriver) DriverManager.getDriver(dbPathSupplier.get().toString()), + public RelationalConnectionRule(@Nonnull RelationalExtension extension, Supplier dbPathSupplier) { + // Use the extension's driver directly rather than DriverManager. The previous pattern + // (DriverManager.getDriver) raced under parallel JUnit execution: another test class's + // afterEach could deregister the only driver between this rule's beforeEach and the + // getDriver() call, surfacing as `SQLException: No suitable driver`. + this(() -> Objects.requireNonNull(extension.getDriver(), + "RelationalExtension has no active driver — its @BeforeEach must run before this rule's @BeforeEach."), dbPathSupplier); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java index 0945ccbad6..00ca8d0fa5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.relational.recordlayer; import com.apple.foundationdb.relational.api.EmbeddedRelationalEngine; +import com.apple.foundationdb.relational.api.RelationalDriver; import org.junit.jupiter.api.extension.Extension; import javax.annotation.Nullable; @@ -30,4 +31,11 @@ public interface RelationalExtension extends Extension { @Nullable EmbeddedRelationalEngine getEngine(); + /** + * @return the driver owned by this extension. May be {@code null} if the extension's + * {@code beforeEach} has not run yet, or has been cleaned up. + */ + @Nullable + RelationalDriver getDriver(); + } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ResultSetMetaDataTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ResultSetMetaDataTest.java index 2a86090bd8..3e89538477 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ResultSetMetaDataTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ResultSetMetaDataTest.java @@ -50,13 +50,13 @@ public abstract class ResultSetMetaDataTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, ResultSetMetaDataTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java index 089acf0dca..26d1c53efe 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java @@ -32,6 +32,7 @@ import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.TestSchemas; +import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Order; @@ -42,6 +43,7 @@ import java.sql.SQLException; import java.util.Collections; import java.util.List; +import java.net.URI; /** * Simple unit tests around direct-access insertion tests. @@ -53,12 +55,12 @@ public class SimpleDirectAccessInsertionTests { @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(SimpleDirectAccessInsertionTests.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, SimpleDirectAccessInsertionTests.class, TestSchemas.restaurant()); @Test void useScanContinuationInQueryShouldNotWork() throws Exception { insertRows(); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); // scan @@ -91,7 +93,7 @@ void useScanContinuationInQueryShouldNotWork() throws Exception { @Test void useQueryContinuationInDirectAccess() throws Exception { insertRows(); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -119,7 +121,7 @@ void useQueryContinuationInDirectAccess() throws Exception { @Test void insertNestedFields() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -149,7 +151,7 @@ void insertNestedFields() throws SQLException { @Test void insertWithExplicitNullFields() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -179,7 +181,7 @@ void insertMultipleTablesDontMix() throws SQLException { * tables are logically separated. */ - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -262,7 +264,7 @@ void insertMultipleTablesDontMix() throws SQLException { } private void insertRows() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java index 2334fed6c4..24ac9b8c52 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java @@ -53,12 +53,12 @@ create function f4 (in x bigint, in y string) @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(CaseSensitiveDbObjectsTest.class, + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, CaseSensitiveDbObjectsTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/StructDataMetadataTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/StructDataMetadataTest.java index 898e0313d0..c8152a4843 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/StructDataMetadataTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/StructDataMetadataTest.java @@ -76,11 +76,11 @@ CREATE TABLE t4(id bigint, n1 n1, n2 n2, PRIMARY KEY(id)) */ @RegisterExtension @Order(0) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(StructDataMetadataTest.class, TABLE_STRUCTURE); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, StructDataMetadataTest.class, TABLE_STRUCTURE); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java index 6b217bc715..bb47ab666a 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java @@ -25,6 +25,7 @@ import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.utils.ResultSetAssert; +import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -42,6 +43,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import java.net.URI; public class SystemCatalogQueryTest { @@ -71,7 +73,7 @@ public void teardown() throws Exception { } private static void runDdl(@Nonnull final String ddl) throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement()) { statement.executeUpdate(ddl); @@ -95,7 +97,7 @@ private static void dropDb(@Nonnull final String dbName) throws Exception { @Test @SuppressWarnings("checkstyle:Indentation") public void selectSchemasWorks() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); //we are selective here to make it easier to check the correctness of the row (otherwise we'd have to put //MetaData objects in for equality) @@ -116,7 +118,7 @@ public void selectSchemasWorks() throws SQLException { @Test public void selectSchemasWithPredicateAndProjectionWorks() throws SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT schema_name FROM \"SCHEMAS\" WHERE database_id = '/__SYS'")) { shouldBe(rs, Set.of( @@ -129,7 +131,7 @@ public void selectSchemasWithPredicateAndProjectionWorks() throws SQLException { @Disabled // TODO (SystemCatalogQueryTest has fragile tests) @Test public void selectDatabaseInfoWorks() throws SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT * FROM \"DATABASES\"")) { shouldBe(rs, Set.of( @@ -145,7 +147,7 @@ public void selectDatabaseInfoWorks() throws SQLException { @Disabled // TODO (SystemCatalogQueryTest has fragile tests) @Test public void selectDatabaseInfoWithPredicateAndProjectionWorks() throws RelationalException, SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT database_id FROM \"DATABASES\" WHERE database_id != '/__SYS'")) { shouldBe(rs, Set.of( diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableMetadataVersionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableMetadataVersionTest.java index 28de364e4b..c20cea4948 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableMetadataVersionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableMetadataVersionTest.java @@ -43,11 +43,11 @@ public class TableMetadataVersionTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(TableMetadataVersionTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, TableMetadataVersionTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule dbConn = new RelationalConnectionRule(database::getConnectionUri); + public final RelationalConnectionRule dbConn = new RelationalConnectionRule(relationalExtension, database::getConnectionUri); @Test void missingMetadataVersionFailsToLoadDuringGet() throws Exception { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java index 2c16d38aac..c4ac6ba846 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java @@ -55,11 +55,11 @@ public class TableTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(TableTest.class, TestSchemas.restaurantWithCoveringIndex()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, TableTest.class, TestSchemas.restaurantWithCoveringIndex()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java index 87eb4c0ced..a05654a828 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java @@ -54,11 +54,11 @@ public class TableWithEnumTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(TableWithEnumTest.class, TestSchemas.playingCard()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, TableWithEnumTest.class, TestSchemas.playingCard()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java index 1becdabc95..07747497ed 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java @@ -31,6 +31,7 @@ import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.RelationalAssertions; +import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Order; @@ -41,6 +42,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; +import java.net.URI; /** * A table with no primary key (but with a record-type key can contain only one row. @@ -52,12 +54,12 @@ public class TableWithNoPkTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(TableWithNoPkTest.class, + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, TableWithNoPkTest.class, "CREATE TABLE no_pk(a bigint, b bigint, SINGLE ROW ONLY)"); @Test void simpleTest() throws RelationalException, SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -81,7 +83,7 @@ void simpleTest() throws RelationalException, SQLException { @Test void twoInserts() throws RelationalException, SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -114,7 +116,7 @@ void twoInserts() throws RelationalException, SQLException { @Test void testDelete() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -138,7 +140,7 @@ void testDelete() throws Exception { @Test void testScan() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -160,7 +162,7 @@ void testScan() throws Exception { @Test void testQuery() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -190,7 +192,7 @@ void testQuery() throws Exception { @Test void testCreateTableWithNoSingleRowOnlyClause() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try (Statement statement = conn.createStatement()) { //create a schema diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseTest.java index b82c51d28b..a992ae11f5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseTest.java @@ -90,11 +90,11 @@ public class TransactionBoundDatabaseTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule dbRule = new SimpleDatabaseRule(TransactionBoundDatabaseTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule dbRule = new SimpleDatabaseRule(relational, TransactionBoundDatabaseTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connRule = new RelationalConnectionRule(dbRule::getConnectionUri) + public final RelationalConnectionRule connRule = new RelationalConnectionRule(relational, dbRule::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java index 058b81325f..b130b713ef 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java @@ -64,11 +64,11 @@ public class TransactionBoundDatabaseWithEnumTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule dbRule = new SimpleDatabaseRule(TransactionBoundDatabaseWithEnumTest.class, TestSchemas.playingCard()); + public final SimpleDatabaseRule dbRule = new SimpleDatabaseRule(relational, TransactionBoundDatabaseWithEnumTest.class, TestSchemas.playingCard()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connRule = new RelationalConnectionRule(dbRule::getConnectionUri) + public final RelationalConnectionRule connRule = new RelationalConnectionRule(relational, dbRule::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java index 07dfe99dcf..79fb3bfff5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java @@ -44,7 +44,7 @@ public class TransactionConfigTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(TransactionConfigTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relational, TransactionConfigTest.class, TestSchemas.restaurant()); @Disabled // TODO (Bug: sporadic failure in `testRecordInsertionWithTimeOutInConfig`) void testRecordInsertionWithTimeOutInConfig() throws RelationalException, SQLException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/UniqueIndexTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/UniqueIndexTests.java index 843c57248e..bed95ae023 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/UniqueIndexTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/UniqueIndexTests.java @@ -64,11 +64,11 @@ CREATE TABLE T5(t5_p bigint, t5_a bigint, t5_b bigint, t5_c bigint, t5_d bigint, @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(UniqueIndexTests.class, getTemplate_definition); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, UniqueIndexTests.class, getTemplate_definition); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(db::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, db::getConnectionUri) .withOptions(Options.NONE) .withSchema(db.getSchemaName()); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java index ee289d6e1c..979b3a7ba6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java @@ -52,7 +52,7 @@ public class VectorDirectAccessTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(VectorDirectAccessTest.class, + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, VectorDirectAccessTest.class, "CREATE TABLE V(PK INTEGER, V1 VECTOR(4, FLOAT), V2 VECTOR(3, HALF), V3 VECTOR(2, DOUBLE), PRIMARY KEY(PK))\n" + "CREATE TABLE VHNSW(PK INTEGER, ZONE STRING, VEC VECTOR(3, FLOAT), PRIMARY KEY(PK))\n" + "CREATE VIEW VHNSW_VIEW AS SELECT VEC, ZONE, PK FROM VHNSW\n" + diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java index 31a87787b7..0ffd863b27 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java @@ -43,6 +43,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.net.URI; /** * End-to-end unit tests for the database ddl language built over RecordLayer. @@ -52,14 +53,14 @@ public class DdlDatabaseTest { public static final EmbeddedRelationalExtension relational = new EmbeddedRelationalExtension(); @RegisterExtension - public final SchemaTemplateRule baseTemplate = new SchemaTemplateRule(DdlDatabaseTest.class.getSimpleName() + "_TEMPLATE", + public final SchemaTemplateRule baseTemplate = new SchemaTemplateRule(relational, DdlDatabaseTest.class.getSimpleName() + "_TEMPLATE", Options.none(), null, Collections.singleton(new TableDefinition("FOO_TBL", List.of("string", "double"), List.of("col1"))), Collections.singleton(new TypeDefinition("FOO_NESTED_TYPE", List.of("string", "bigint")))); @Test public void canCreateDatabase() throws Exception { try { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement()) { //create a database @@ -67,7 +68,7 @@ public void canCreateDatabase() throws Exception { statement.executeUpdate("CREATE SCHEMA /test/test_db/foo_schem with template \"" + baseTemplate.getSchemaTemplateName() + "\""); } } - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/TEST/TEST_DB").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/TEST/TEST_DB")).unwrap(RelationalConnection.class)) { conn.setSchema("FOO_SCHEM"); try (RelationalStatement statement = conn.createStatement()) { @@ -80,7 +81,7 @@ public void canCreateDatabase() throws Exception { } } } finally { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS?schema=CATALOG")) { + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS?schema=CATALOG"))) { try (final var statement = conn.createStatement()) { statement.execute("DROP DATABASE /test/test_db"); } @@ -92,7 +93,7 @@ public void canCreateDatabase() throws Exception { @Disabled("TODO") public void dropDatabaseRemovesFromList() throws Exception { final String listCommand = "SHOW DATABASES WITH PREFIX /test_db"; - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try (RelationalStatement statement = conn.createStatement()) { //create a database @@ -122,7 +123,7 @@ public void cannotCreateSchemaFromDroppedDatabase() throws Exception { * * This is a drop database test that doesn't require listing the database */ - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement()) { //create a database @@ -145,7 +146,7 @@ public void cannotCreateSchemaFromDroppedDatabase() throws Exception { @Test public void cannotCreateDatabaseIfExists() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); // assure that there is not a database with the same name from before try (Statement statement = conn.createStatement()) { @@ -168,7 +169,7 @@ public void cannotCreateDatabaseIfExists() throws Exception { @Test public void cannotCreateSchemaWithoutDatabase() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try (Statement statement = conn.createStatement()) { //create a database diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java index 78f3263ef0..dead2c4d49 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java @@ -49,6 +49,7 @@ import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Stream; +import java.net.URI; /** * End-to-end unit tests for Schema Template language in the RecordLayer. @@ -66,7 +67,7 @@ public static Stream columnTypePermutations() { } private void run(ThrowingConsumer operation) throws RelationalException, SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try (RelationalStatement statement = conn.createStatement()) { operation.accept(statement); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java index 76111c6200..97b0900299 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java @@ -52,18 +52,18 @@ public class DdlRecordLayerSchemaTest { @RegisterExtension @Order(1) - public final SchemaTemplateRule baseTemplate = new SchemaTemplateRule( + public final SchemaTemplateRule baseTemplate = new SchemaTemplateRule(relational, DdlRecordLayerSchemaTest.class.getSimpleName().toUpperCase(Locale.ROOT) + "_TEMPLATE", Options.none(), null, Collections.singleton(new TableDefinition("FOO_TBL", List.of("string", "double"), List.of("col0"))), Collections.singleton(new TypeDefinition("FOO_NESTED_TYPE", List.of("string", "bigint")))); @RegisterExtension @Order(2) - public final DatabaseRule db = new DatabaseRule(URI.create("/TEST/" + DdlRecordLayerSchemaTest.class.getSimpleName().toUpperCase(Locale.ROOT)), Options.none()); + public final DatabaseRule db = new DatabaseRule(relational, URI.create("/TEST/" + DdlRecordLayerSchemaTest.class.getSimpleName().toUpperCase(Locale.ROOT)), Options.none()); @Test void canCreateSchema() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement()) { //create a schema @@ -89,7 +89,7 @@ void canCreateSchema() throws Exception { @Test void canCreateSchemaTemplateWhenConnectedToNonCatalogSchema() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (Statement statement = conn.createStatement()) { //create a schema @@ -99,7 +99,7 @@ void canCreateSchemaTemplateWhenConnectedToNonCatalogSchema() throws Exception { } } //now create a new schema in the same db but using a different connection - try (final var conn = DriverManager.getConnection("jdbc:embed:" + db.getDbUri())) { + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:" + db.getDbUri()))) { conn.setSchema("TEST_SCHEMA"); try (Statement statement = conn.createStatement()) { //create a schema @@ -111,7 +111,7 @@ void canCreateSchemaTemplateWhenConnectedToNonCatalogSchema() throws Exception { @Test void cannotCreateSchemaTwice() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (Statement statement = conn.createStatement()) { @@ -127,7 +127,7 @@ void cannotCreateSchemaTwice() throws Exception { @Test void dropSchema() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { conn.setSchema("CATALOG"); try (final var statement = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/MetricsCollectionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/MetricsCollectionTest.java index 52f970689b..b6034380b1 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/MetricsCollectionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/MetricsCollectionTest.java @@ -37,7 +37,7 @@ public class MetricsCollectionTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(MetricsCollectionTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relational, MetricsCollectionTest.class, TestSchemas.restaurant()); @Test void canRecoverFDBLevelMetrics() { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CountQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CountQueryTest.java index 06960d591e..367f83a1f0 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CountQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CountQueryTest.java @@ -56,11 +56,11 @@ CREATE INDEX i3 AS SELECT count(a) FROM t1 GROUP BY b @RegisterExtension @Order(1) - final SimpleDatabaseRule database = new SimpleDatabaseRule(CountQueryTest.class, SCHEMA_TEMPLATE); + final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, CountQueryTest.class, SCHEMA_TEMPLATE); @RegisterExtension @Order(2) - final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri).withSchema(database.getSchemaName()); + final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri).withSchema(database.getSchemaName()); @RegisterExtension @Order(3) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java index 195993ef93..e2160db252 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java @@ -66,13 +66,13 @@ public class ExecutePropertyTests { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, ExecutePropertyTests.class, schemaTemplate); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); @@ -99,7 +99,7 @@ void hitLimitEveryRow(Options.Name optionName, Object optionValue, int expectedR statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11'), (12, '12'), (13, '13'), (14, '14'), (15, '15'), (16, '16')"); Continuation continuation = ContinuationImpl.BEGIN; long nextCorrectResult = 10L; - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(optionName, optionValue).build())) { conn.setSchema("TEST_SCHEMA"); while (!continuation.atEnd()) { @@ -137,7 +137,7 @@ void hitLimitEveryRow(Options.Name optionName, Object optionValue, int expectedR @Test public void multipleConnectionsDoNotAffectEachOthersLimit() throws Exception { statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11'), (12, '12'), (13, '13'), (14, '14'), (15, '15'), (16, '16')"); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn1 = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 2).build()); var conn2 = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 3).build())) { conn1.setSchema("TEST_SCHEMA"); @@ -165,7 +165,7 @@ public void multipleConnectionsDoNotAffectEachOthersLimit() throws Exception { @Test public void limitIsKeptAcrossMultipleQueriesWithinTheSameTransaction() throws Exception { statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11'), (12, '12')"); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 5).build())) { conn.setSchema("TEST_SCHEMA"); conn.setAutoCommit(false); @@ -192,7 +192,7 @@ public void limitIsKeptAcrossMultipleQueriesWithinTheSameTransaction() throws Ex @Test public void limitIsKeptAcrossMultipleQueriesWithinTheSameTransactionSecondQueryFailsRightAway() throws Exception { statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11'), (12, '12')"); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 1).build())) { conn.setSchema("TEST_SCHEMA"); conn.setAutoCommit(false); @@ -214,7 +214,7 @@ public void limitIsKeptAcrossMultipleQueriesWithinTheSameTransactionSecondQueryF @Test public void limitIsResetWithNewTransaction() throws Exception { statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11')"); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 5).build())) { conn.setSchema("TEST_SCHEMA"); conn.setAutoCommit(false); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ForceContinuationQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ForceContinuationQueryTests.java index 849c1cd32b..d2d04cfc75 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ForceContinuationQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ForceContinuationQueryTests.java @@ -67,11 +67,11 @@ create index t3_i6 as select sum(col1) from t3 group by col2 @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(UniqueIndexTests.class, getTemplate_definition); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, UniqueIndexTests.class, getTemplate_definition); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(db::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, db::getConnectionUri) .withSchema(db.getSchemaName()); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TransactionBoundQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TransactionBoundQueryTest.java index d430de7b52..e52621c666 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TransactionBoundQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TransactionBoundQueryTest.java @@ -64,7 +64,6 @@ import javax.annotation.Nonnull; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.Map; import java.util.function.Consumer; @@ -85,7 +84,7 @@ CREATE TABLE t3(id bigint, a bigint, e bigint, f bigint, PRIMARY KEY(id)) final EmbeddedRelationalExtension embeddedExtension = new EmbeddedRelationalExtension(); @RegisterExtension @Order(1) - final SimpleDatabaseRule databaseRule = new SimpleDatabaseRule(TransactionBoundQueryTest.class, SCHEMA_TEMPLATE); + final SimpleDatabaseRule databaseRule = new SimpleDatabaseRule(embeddedExtension, TransactionBoundQueryTest.class, SCHEMA_TEMPLATE); @Nonnull private static Options engineOptions() throws SQLException { @@ -98,7 +97,8 @@ private static Options engineOptions() throws SQLException { @Nonnull private EmbeddedRelationalConnection connectEmbedded() throws SQLException { - Connection connection = DriverManager.getConnection(databaseRule.getConnectionUri().toString()); + Connection connection = embeddedExtension.getDriver() + .connect(databaseRule.getConnectionUri(), Options.NONE); connection.setSchema(databaseRule.getSchemaName()); return connection.unwrap(EmbeddedRelationalConnection.class); } @@ -128,23 +128,20 @@ private EmbeddedRelationalConnection connectTransactionBound(@Nonnull FDBRecordC .setContext(context) .open(); - final var originalDriver = embeddedExtension.getDriver(); - DriverManager.deregisterDriver(originalDriver); - var newDriver = new EmbeddedRelationalDriver(new TransactionBoundEmbeddedRelationalEngine(engineOptions())); - DriverManager.registerDriver(newDriver); - final var driver = (EmbeddedRelationalDriver) DriverManager.getDriver(databaseRule.getConnectionUri().toString()); + // Use the bound driver directly instead of going through DriverManager. + // Previously this test deregistered the extension's driver, registered its own, did + // a getDriver() lookup, then restored the original. Under parallel JUnit execution + // that dance both raced with other tests and depended on whatever DriverManager + // happened to hold — neither is necessary because the test already has both driver + // objects in scope. + final var newDriver = new EmbeddedRelationalDriver(new TransactionBoundEmbeddedRelationalEngine(engineOptions())); final var connectionOptions = Options.none(); - try { - final var schemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaData, databaseRule.getSchemaTemplateName(), - metaData.getVersion()); - final var transactionBoundConnection = driver.connect(databaseRule.getConnectionUri(), - new RecordStoreAndRecordContextTransaction(newStore, context, schemaTemplate), connectionOptions); - transactionBoundConnection.setSchema(databaseRule.getSchemaName()); - return transactionBoundConnection.unwrap(EmbeddedRelationalConnection.class); - } finally { - DriverManager.deregisterDriver(newDriver); - DriverManager.registerDriver(originalDriver); - } + final var schemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaData, databaseRule.getSchemaTemplateName(), + metaData.getVersion()); + final var transactionBoundConnection = newDriver.connect(databaseRule.getConnectionUri(), + new RecordStoreAndRecordContextTransaction(newStore, context, schemaTemplate), connectionOptions); + transactionBoundConnection.setSchema(databaseRule.getSchemaName()); + return transactionBoundConnection.unwrap(EmbeddedRelationalConnection.class); } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java index cb37157b60..1556447020 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java @@ -50,6 +50,7 @@ import java.util.Arrays; import java.util.List; import java.util.function.Function; +import java.net.URI; public class UpdateTest { @@ -66,7 +67,7 @@ CREATE TABLE RestaurantReviewer (id bigint, name string, email string, stats Rev @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(UpdateTest.class, schemaTemplate, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, UpdateTest.class, schemaTemplate, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(4) @@ -167,7 +168,7 @@ void updateArrayFieldVerifyCacheTest() throws Exception { } public void insertRecords(int numRecords) throws RelationalException, SQLException { - try (final var con = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var con = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { con.setSchema(database.getSchemaName()); final var builder = new StringBuilder("INSERT INTO RestaurantReviewer(id) VALUES"); for (int i = 0; i < numRecords; i++) { @@ -193,7 +194,7 @@ private static RelationalPreparedStatement prepareUpdate(RelationalConnection co } private void verifyUpdates(String updatedField, Object expectedValue, int updatedUpTill) throws SQLException { - try (final var con = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (final var con = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { con.setSchema(database.getSchemaName()); final var statement = con.prepareStatement("SELECT id, " + updatedField + " from RestaurantReviewer WHERE id >= 0"); try (final var resultSet = statement.executeQuery()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ConstraintValidityTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ConstraintValidityTests.java index 983db41099..9635964e51 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ConstraintValidityTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ConstraintValidityTests.java @@ -75,11 +75,11 @@ public class ConstraintValidityTests { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(ConstraintValidityTests.class, TestSchemas.score()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, ConstraintValidityTests.class, TestSchemas.score()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @Nonnull diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/RelationalPlanCacheTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/RelationalPlanCacheTests.java index cb843ca8a2..7492324ba8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/RelationalPlanCacheTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/RelationalPlanCacheTests.java @@ -94,11 +94,11 @@ public class RelationalPlanCacheTests { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(RelationalPlanCacheTests.class, TestSchemas.books()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, RelationalPlanCacheTests.class, TestSchemas.books()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @Nonnull diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/functions/IncarnationTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/functions/IncarnationTest.java index 1ecf643405..e3388557c9 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/functions/IncarnationTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/functions/IncarnationTest.java @@ -57,7 +57,7 @@ public class IncarnationTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, IncarnationTest.class, "CREATE TABLE my_record (key string, incarnation integer, data string, PRIMARY KEY(key))"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java index dd4a2213c8..6175abcb2f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java @@ -28,7 +28,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.URI; -import java.sql.DriverManager; import java.sql.SQLException; /** Utility for interacting with easily connections/statements. */ @@ -38,10 +37,6 @@ public final class ConnectionUtils { @Nonnull private final SqlFunction getConnection; - public ConnectionUtils() { - getConnection = databaseName -> DriverManager.getConnection("jdbc:embed:" + databaseName).unwrap(RelationalConnection.class); - } - public ConnectionUtils(@Nonnull RelationalDriver driver) { getConnection = databaseName -> driver.connect(URI.create("jdbc:embed:" + databaseName)).unwrap(RelationalConnection.class); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java index 43b7b66131..73ed08fe2d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java @@ -21,6 +21,8 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalDriver; +import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import com.apple.foundationdb.relational.recordlayer.Utils; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; @@ -31,19 +33,25 @@ import javax.annotation.Nonnull; import java.net.URI; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.util.Objects; public class DatabaseRule implements BeforeEachCallback, BeforeAllCallback, AfterEachCallback, AfterAllCallback { + @Nonnull + private final RelationalExtension extension; + @Nonnull private final URI databasePath; @Nonnull private final Options options; - public DatabaseRule(@Nonnull final URI databasePath, @Nonnull final Options options) { + public DatabaseRule(@Nonnull final RelationalExtension extension, + @Nonnull final URI databasePath, + @Nonnull final Options options) { + this.extension = extension; this.databasePath = databasePath; this.options = options; } @@ -68,9 +76,15 @@ public void beforeEach(ExtensionContext context) throws SQLException { setup(); } + @Nonnull + private RelationalDriver driver() { + return Objects.requireNonNull(extension.getDriver(), + "RelationalExtension has no active driver — its @BeforeEach must run before this rule's @BeforeEach."); + } + private void setup() throws SQLException { CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { Utils.setConnectionOptions(connection, options); connection.setSchema("CATALOG"); try (Statement statement = connection.createStatement()) { @@ -83,7 +97,7 @@ private void setup() throws SQLException { private void tearDown() throws SQLException { CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { Utils.setConnectionOptions(connection, options); connection.setSchema("CATALOG"); try (Statement statement = connection.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java index 6f2eb9ea3f..5eced2d78d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java @@ -22,6 +22,7 @@ import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; +import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import com.apple.foundationdb.relational.recordlayer.Utils; import com.apple.foundationdb.relational.util.Assert; @@ -32,8 +33,8 @@ import javax.annotation.Nullable; import java.net.URI; import java.net.URISyntaxException; -import java.sql.DriverManager; import java.sql.SQLException; +import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; public class Ddl implements AutoCloseable { @@ -60,9 +61,9 @@ public Ddl(@Nonnull final RelationalExtension relationalExtension, final String templateName = dbPath.getPath().substring(dbPath.getPath().lastIndexOf("/") + 1); this.relationalExtension = relationalExtension; - this.templateRule = new SchemaTemplateRule(templateName + "_TEMPLATE", options, schemaTemplateOptions, templateDefinition); - this.databaseRule = new DatabaseRule(dbPath, options); - this.schemaRule = new SchemaRule(schemaName, dbPath, templateRule.getSchemaTemplateName(), options); + this.templateRule = new SchemaTemplateRule(relationalExtension, templateName + "_TEMPLATE", options, schemaTemplateOptions, templateDefinition); + this.databaseRule = new DatabaseRule(relationalExtension, dbPath, options); + this.schemaRule = new SchemaRule(relationalExtension, schemaName, dbPath, templateRule.getSchemaTemplateName(), options); this.extensionContext = extensionContext; try { @@ -96,7 +97,10 @@ public Ddl(@Nonnull final RelationalExtension relationalExtension, throw e; } - this.connection = DriverManager.getConnection("jdbc:embed://" + databaseRule.getDbUri().toString()).unwrap(RelationalConnection.class); + final RelationalDriver driver = Objects.requireNonNull(relationalExtension.getDriver(), + "RelationalExtension has no active driver — its @BeforeEach must have run first."); + this.connection = driver + .connect(URI.create("jdbc:embed://" + databaseRule.getDbUri().toString())).unwrap(RelationalConnection.class); Utils.setConnectionOptions(connection, options); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java index 40ad551a05..89aad6a066 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java @@ -21,7 +21,9 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalDriver; +import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import com.apple.foundationdb.relational.recordlayer.Utils; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -30,12 +32,15 @@ import javax.annotation.Nonnull; import java.net.URI; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; +import java.util.Objects; public class SchemaRule implements BeforeEachCallback, AfterEachCallback { + @Nonnull + private final RelationalExtension extension; + @Nonnull private final String schemaName; @@ -48,8 +53,12 @@ public class SchemaRule implements BeforeEachCallback, AfterEachCallback { @Nonnull private final Options connectionOptions; - public SchemaRule(@Nonnull final String schemaName, @Nonnull final URI dbUri, @Nonnull final String templateName, + public SchemaRule(@Nonnull final RelationalExtension extension, + @Nonnull final String schemaName, + @Nonnull final URI dbUri, + @Nonnull final String templateName, @Nonnull final Options connectionOptions) { + this.extension = extension; this.schemaName = schemaName; this.dbUri = dbUri; this.templateName = templateName; @@ -71,9 +80,15 @@ public String getSchemaName() { return schemaName; } + @Nonnull + private RelationalDriver driver() { + return Objects.requireNonNull(extension.getDriver(), + "RelationalExtension has no active driver — its @BeforeEach must run before this rule's @BeforeEach."); + } + private void setup() throws Exception { CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { connection.setSchema("CATALOG"); Utils.setConnectionOptions(connection, connectionOptions); try (Statement statement = connection.createStatement()) { @@ -85,7 +100,7 @@ private void setup() throws Exception { private void tearDown() throws SQLException { CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { connection.setSchema("CATALOG"); Utils.setConnectionOptions(connection, connectionOptions); try (Statement statement = connection.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java index c3572ee8af..414fc886d8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java @@ -21,6 +21,8 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalDriver; +import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import com.apple.foundationdb.relational.recordlayer.Utils; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -28,19 +30,27 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.net.URI; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Collection; import java.util.Locale; +import java.util.Objects; import java.util.stream.Collectors; /** * Manages the lifecycle of a single SchemaTemplate within a unit test. + *

+ * Holds a reference to the surrounding {@link RelationalExtension} so it can issue catalog DDL + * through that extension's driver — avoiding the global {@link java.sql.DriverManager} state and + * the parallel-test race that used to come with it. */ public class SchemaTemplateRule implements BeforeEachCallback, AfterEachCallback { + @Nonnull + private final RelationalExtension extension; + @Nonnull private final String templateName; @@ -56,11 +66,13 @@ public class SchemaTemplateRule implements BeforeEachCallback, AfterEachCallback @Nonnull private final TypeCreator tableCreator; - private SchemaTemplateRule(@Nonnull final String templateName, + private SchemaTemplateRule(@Nonnull final RelationalExtension extension, + @Nonnull final String templateName, @Nonnull final Options connectionOptions, @Nullable final SchemaTemplateOptions schemaTemplateOptions, @Nonnull final TypeCreator typeCreator, @Nonnull final TypeCreator tableCreator) { + this.extension = extension; this.templateName = templateName; this.connectionOptions = connectionOptions; this.schemaTemplateOptions = schemaTemplateOptions; @@ -68,21 +80,23 @@ private SchemaTemplateRule(@Nonnull final String templateName, this.tableCreator = tableCreator; } - public SchemaTemplateRule(@Nonnull final String templateName, + public SchemaTemplateRule(@Nonnull final RelationalExtension extension, + @Nonnull final String templateName, @Nonnull final Options connectionOptions, @Nullable final SchemaTemplateOptions schemaTemplateOptions, @Nonnull final Collection tables, @Nonnull final Collection types) { - this(templateName, connectionOptions, schemaTemplateOptions, + this(extension, templateName, connectionOptions, schemaTemplateOptions, new CreatorFromDefinition("TYPE AS STRUCT", types), new CreatorFromDefinition("TABLE", tables)); } - public SchemaTemplateRule(@Nonnull final String templateName, + public SchemaTemplateRule(@Nonnull final RelationalExtension extension, + @Nonnull final String templateName, @Nonnull final Options connectionOptions, @Nullable final SchemaTemplateOptions schemaTemplateOptions, @Nonnull final String templateDefinition) { - this(templateName, connectionOptions, schemaTemplateOptions, + this(extension, templateName, connectionOptions, schemaTemplateOptions, new CreatorFromString(templateDefinition), () -> ""); } @@ -91,12 +105,18 @@ public String getSchemaTemplateName() { return templateName; } + @Nonnull + private RelationalDriver driver() { + return Objects.requireNonNull(extension.getDriver(), + "RelationalExtension has no active driver — its @BeforeEach must run before this rule's @BeforeEach."); + } + @Override public void afterEach(ExtensionContext context) throws SQLException { final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE IF EXISTS \"").append(templateName).append("\""); CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { connection.setSchema("CATALOG"); Utils.setConnectionOptions(connection, connectionOptions); try (Statement statement = connection.createStatement()) { @@ -111,7 +131,7 @@ public void beforeEach(ExtensionContext context) throws SQLException { final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE IF EXISTS\"").append(templateName).append("\""); CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { connection.setSchema("CATALOG"); Utils.setConnectionOptions(connection, connectionOptions); try (Statement statement = connection.createStatement()) { @@ -128,7 +148,7 @@ public void beforeEach(ExtensionContext context) throws SQLException { } CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { connection.setSchema("CATALOG"); Utils.setConnectionOptions(connection, connectionOptions); try (Statement statement = connection.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java index 297b490ddb..7ef64c1feb 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -51,7 +52,8 @@ public class SimpleDatabaseRule implements BeforeEachCallback, AfterEachCallback @Nonnull private final SchemaRule schemaRule; - public SimpleDatabaseRule(@Nonnull Class testClass, + public SimpleDatabaseRule(@Nonnull RelationalExtension extension, + @Nonnull Class testClass, @Nonnull String templateDefinition, @Nonnull Options connectionOptions, @Nullable SchemaTemplateRule.SchemaTemplateOptions templateOptions) { @@ -66,26 +68,29 @@ public SimpleDatabaseRule(@Nonnull Class testClass, final var dbPath = URI.create("/TEST/" + testClass.getSimpleName() + "_" + uniqueSuffix); final var templateName = dbPath.getPath().substring(dbPath.getPath().lastIndexOf("/") + 1); - this.templateRule = new SchemaTemplateRule(templateName + "_TEMPLATE", Options.none(), templateOptions, templateDefinition); - this.databaseRule = new DatabaseRule(dbPath, connectionOptions); - this.schemaRule = new SchemaRule(schemaName, dbPath, templateRule.getSchemaTemplateName(), connectionOptions); + this.templateRule = new SchemaTemplateRule(extension, templateName + "_TEMPLATE", Options.none(), templateOptions, templateDefinition); + this.databaseRule = new DatabaseRule(extension, dbPath, connectionOptions); + this.schemaRule = new SchemaRule(extension, schemaName, dbPath, templateRule.getSchemaTemplateName(), connectionOptions); } - public SimpleDatabaseRule(@Nonnull final Class testClass, + public SimpleDatabaseRule(@Nonnull final RelationalExtension extension, + @Nonnull final Class testClass, @Nonnull final String templateDefinition, @Nonnull final Options options) { - this(testClass, templateDefinition, options, null); + this(extension, testClass, templateDefinition, options, null); } - public SimpleDatabaseRule(@Nonnull final Class testClass, + public SimpleDatabaseRule(@Nonnull final RelationalExtension extension, + @Nonnull final Class testClass, @Nonnull final String templateDefinition, @Nullable SchemaTemplateRule.SchemaTemplateOptions templateOptions) { - this(testClass, templateDefinition, Options.none(), templateOptions); + this(extension, testClass, templateDefinition, Options.none(), templateOptions); } - public SimpleDatabaseRule(@Nonnull final Class testClass, + public SimpleDatabaseRule(@Nonnull final RelationalExtension extension, + @Nonnull final Class testClass, @Nonnull final String templateDefinition) { - this(testClass, templateDefinition, Options.none(), null); + this(extension, testClass, templateDefinition, Options.none(), null); } @Override From e5f75e106efa041f95bdfde97568dadbcf429cd7 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 12:50:29 -0400 Subject: [PATCH 41/74] Change some additional tests to use random databases --- .../relational/recordlayer/CaseSensitivityTest.java | 2 +- .../recordlayer/metric/RecordLayerMetricCollectorTest.java | 2 +- .../recordlayer/query/LargeRecordLayerSchemaTest.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java index dc7a9c2339..f8f2175042 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java @@ -63,7 +63,7 @@ public CaseSensitivityTest() { @Test void selectFromCaseInsensitiveTable() throws Exception { final String schema = "CREATE TABLE tbl1 (id bigint, value bigint, PRIMARY KEY(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/CaseSensitivity")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { for (String tableName : List.of("tbl1", "TBL1", "TbL1", "\"TBL1\"")) { Assertions.assertDoesNotThrow(() -> statement.execute("SELECT * FROM " + tableName)); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollectorTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollectorTest.java index 0c4b38059e..1e3e615763 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollectorTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollectorTest.java @@ -108,7 +108,7 @@ private Continuation executeSimpleSelectAndReturnContinuation(EmbeddedRelational } private void setupAndExecuteWithConnection(Consumer execute) throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/METRIC_COLLECTOR_TESTS")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection().unwrap(EmbeddedRelationalConnection.class); try (var statement = connection.createStatement()) { for (int i = 0; i < 10; i++) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java index b936b62845..8df7e74c06 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java @@ -56,7 +56,7 @@ void canCreateColumns(int colCount) throws Exception { StringBuilder template = new StringBuilder("CREATE TABLE T1("); template.append(IntStream.range(0, colCount).mapToObj(LargeRecordLayerSchemaTest::column).map(c -> c + " bigint").collect(Collectors.joining(","))); template.append(", PRIMARY KEY(").append(column(0)).append("))"); - try (var ddl = Ddl.builder().database(URI.create("/TEST/" + LargeRecordLayerSchemaTest.class.getSimpleName())).relationalExtension(relationalExtension).schemaTemplate(template.toString()).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(template.toString()).build()) { try (RelationalConnection conn = ddl.setSchemaAndGetConnection()) { try (var statement = conn.createStatement()) { RelationalStructBuilder builder = EmbeddedRelationalStruct.newBuilder(); @@ -84,7 +84,7 @@ void tooManyColumns() { template.append(IntStream.range(0, colCount).mapToObj(LargeRecordLayerSchemaTest::column).map(c -> c + " bigint").collect(Collectors.joining(","))); template.append(", PRIMARY KEY(").append(column(0)).append("))"); RelationalAssertions.assertThrowsSqlException(() -> - Ddl.builder().database(URI.create("/TEST/" + LargeRecordLayerSchemaTest.class.getSimpleName())).relationalExtension(relationalExtension).schemaTemplate(template.toString()).build()) + Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(template.toString()).build()) .hasErrorCode(ErrorCode.TOO_MANY_COLUMNS); } From 7922b008ebf5181bca1bd8e8967961aa03816089 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 13:48:55 -0400 Subject: [PATCH 42/74] Update remaining tests to stop using registered driver --- .../relational/SqlInsertTest.java | 12 +-- .../relational/api/DriverManagerTest.java | 19 +++-- .../api/RelationalConnectionTest.java | 24 +++--- .../cases/DirectPrimaryKeyScanTest.java | 1 - .../autotest/cases/KnownPkGetTest.java | 1 - .../relational/jdbc/JDBCEmbedDriverTest.java | 80 +++---------------- .../CaseSensitiveDbObjectsTest.java | 6 +- .../recordlayer/CaseSensitivityTest.java | 1 - .../relational/recordlayer/CursorTest.java | 10 +-- .../EmbeddedRelationalExtension.java | 52 ++---------- .../relational/recordlayer/InsertTest.java | 16 ++-- .../recordlayer/OptionScopeTest.java | 5 +- .../recordlayer/QueryLoggingTest.java | 1 - .../recordlayer/QueryPropertiesTest.java | 3 +- .../recordlayer/RelationalArrayTest.java | 46 +++++------ .../SimpleDirectAccessInsertionTests.java | 1 - .../recordlayer/SystemCatalogQueryTest.java | 1 - .../recordlayer/TableWithNoPkTest.java | 1 - .../recordlayer/TransactionConfigTest.java | 4 +- .../recordlayer/VectorDirectAccessTest.java | 26 +++--- .../recordlayer/ddl/DdlDatabaseTest.java | 1 - .../ddl/DdlRecordLayerSchemaTemplateTest.java | 1 - .../ddl/DdlRecordLayerSchemaTest.java | 1 - .../query/ExecutePropertyTests.java | 1 - .../recordlayer/query/UpdateTest.java | 3 +- 25 files changed, 108 insertions(+), 209 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java index c1ae43577b..3ad776d668 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java @@ -41,8 +41,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.sql.DriverManager; import java.util.Map; +import java.net.URI; public class SqlInsertTest { @@ -63,7 +63,7 @@ CREATE TABLE with_loc (rest_no bigint, name string, loc location, primary key(re @ParameterizedTest @ValueSource(booleans = {true, false}) void canInsertUsingSqlSyntaxAndSingleQuotes(boolean useNamed) throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (RelationalStatement stmt = conn.createStatement()) { @@ -83,7 +83,7 @@ void canInsertUsingSqlSyntaxAndSingleQuotes(boolean useNamed) throws Exception { @Test void canInsertStructUnnamed() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (RelationalStatement stmt = conn.createStatement()) { @@ -103,7 +103,7 @@ void canInsertStructUnnamed() throws Exception { @Test void canInsertStructNamed() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (RelationalStatement stmt = conn.createStatement()) { @@ -123,7 +123,7 @@ void canInsertStructNamed() throws Exception { @Test void cannotInsertWithInsertRuleDisabled() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { // Turn off the planner rule that allows for inserts to be planned conn.setOption(Options.Name.DISABLED_PLANNER_RULES, ImmutableSet.of(ImplementInsertRule.class.getSimpleName())); conn.setSchema(database.getSchemaName()); @@ -143,7 +143,7 @@ void cannotInsertWithInsertRuleDisabled() throws Exception { @Test @Disabled() void canInsertStructWithStructFieldsNamed() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (RelationalStatement stmt = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java index f8f4a7d5d3..7113ec0633 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java @@ -25,8 +25,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Execution; -import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.parallel.Isolated; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -36,11 +35,17 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; -// Runs SAME_THREAD because @BeforeEach nukes every driver registered with java.sql.DriverManager -// (which is JVM-global). Allowing this class to run concurrently with anything else that touches -// DriverManager — i.e. most of the relational-core tests via EmbeddedRelationalExtension — would -// race and pull the driver out from under those tests. -@Execution(ExecutionMode.SAME_THREAD) +/** + * This is the only test in the relational-core test suite that interacts with + * {@link DriverManager} directly. Every other test gets its driver via + * {@code relationalExtension.getDriver()}. + *

+ * Marked {@link Isolated} because {@code @BeforeEach} clears the entire JVM-wide DriverManager + * registry. Without isolation, running this concurrently with any other test would race and pull + * the driver out from under it. {@link Isolated} tells JUnit Jupiter to suspend all other tests + * while this class runs. + */ +@Isolated class DriverManagerTest { RelationalDriver driver1 = Mockito.mock(RelationalDriver.class); RelationalDriver driver2 = Mockito.mock(RelationalDriver.class); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java index ba708364bd..0a5aba8f5f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java @@ -29,8 +29,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import java.net.URI; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; class RelationalConnectionTest { @@ -41,25 +41,25 @@ class RelationalConnectionTest { @Test void wrongScheme() { - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("foo")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("foo"))) .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("foo:foo")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("foo:foo"))) .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:foo")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:foo"))) .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed"))) .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed:/i_am_not_a_database")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:/i_am_not_a_database"))) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE); } @Test void missingLeadingSlash() { - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed:i_am_not_a_database")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:i_am_not_a_database"))) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE) .containsInMessage("") .doesNotContainInMessage("") @@ -69,7 +69,7 @@ void missingLeadingSlash() { @Test void setWrongSchema() throws SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { RelationalAssertions.assertThrowsSqlException(() -> conn.setSchema("foo")) .hasErrorCode(ErrorCode.UNDEFINED_SCHEMA); } @@ -77,26 +77,26 @@ void setWrongSchema() throws SQLException { @Test void canConnectDirectlyToSchema() throws SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS?schema=CATALOG")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS?schema=CATALOG"))) { Assertions.assertThat(conn.getSchema()).isEqualTo("CATALOG"); } } @Test void connectDirectlyToNonexistentDatabaseBlowsUp() { - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed:/notADatabase?schema=CATALOG")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:/notADatabase?schema=CATALOG"))) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE); } @Test void connectDirectlyToNonexistentSchemaBlowsUp() { - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed:/__SYS?schema=noSuchSchema")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS?schema=noSuchSchema"))) .hasErrorCode(ErrorCode.UNDEFINED_SCHEMA); } @Test void setIsolationLevel() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { // Default isolation level Assertions.assertThat(conn.getTransactionIsolation()).isEqualTo(Connection.TRANSACTION_SERIALIZABLE); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java index 78048fd45e..710b55f29a 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java @@ -46,7 +46,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import java.net.URI; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java index 6f20bf1119..9df702c5c0 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java @@ -46,7 +46,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import java.net.URI; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java index 9924ae453e..52958f8018 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java @@ -20,38 +20,21 @@ package com.apple.foundationdb.relational.jdbc; -import com.apple.foundationdb.record.provider.foundationdb.APIVersion; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; -import com.apple.foundationdb.test.FDBTestEnvironment; -import com.apple.foundationdb.relational.api.EmbeddedRelationalDriver; -import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalDriver; -import com.apple.foundationdb.relational.api.catalog.StoreCatalog; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; -import com.apple.foundationdb.relational.recordlayer.DirectFdbConnection; -import com.apple.foundationdb.relational.recordlayer.RecordLayerConfig; -import com.apple.foundationdb.relational.recordlayer.RecordLayerEngine; +import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; -import com.apple.foundationdb.relational.recordlayer.catalog.StoreCatalogProvider; -import com.apple.foundationdb.relational.recordlayer.ddl.RecordLayerMetadataOperationsFactory; -import com.apple.foundationdb.relational.recordlayer.query.cache.RelationalPlanCache; import com.apple.foundationdb.relational.util.BuildVersion; -import com.codahale.metrics.MetricRegistry; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.Driver; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.sql.Types; -import java.util.Collections; /** * Run some simple Statement updates/executes against the JDBC Embed JDBC Driver. @@ -61,54 +44,15 @@ */ public class JDBCEmbedDriverTest { - private static RelationalDriver driver; - - @BeforeAll - public static void beforeAll() throws SQLException, RelationalException { - RelationalKeyspaceProvider.instance().registerDomainIfNotExists("FRL"); - - RecordLayerConfig rlCfg = RecordLayerConfig.getDefault(); - //here we are extending the StorageCluster so that we can track which internal Databases were - // connected to and we can validate that they were all closed properly - - // This needs to be done prior to the first call to factory.getDatabase() - FDBDatabaseFactory.instance().setAPIVersion(APIVersion.API_VERSION_7_1); - - final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(FDBTestEnvironment.randomClusterFile()); - StoreCatalog storeCatalog; - try (var txn = new DirectFdbConnection(database).getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, RelationalKeyspaceProvider.instance().getKeySpace()); - txn.commit(); - } - - RecordLayerMetadataOperationsFactory ddlFactory = RecordLayerMetadataOperationsFactory.defaultFactory() - .setBaseKeySpace(RelationalKeyspaceProvider.instance().getKeySpace()) - .setRlConfig(rlCfg) - .setStoreCatalog(storeCatalog) - .build(); - driver = new EmbeddedRelationalDriver(RecordLayerEngine.makeEngine( - rlCfg, - Collections.singletonList(database), - RelationalKeyspaceProvider.instance().getKeySpace(), - storeCatalog, - new MetricRegistry(), - ddlFactory, - RelationalPlanCache.buildWithDefaults())); - DriverManager.registerDriver(driver); //register the engine driver - } - - static Driver getDriver() throws SQLException { - // Use ANY valid URl to get hold of the driver. When we 'connect' we'll - // more specific about where we want to connect to. - return DriverManager.getDriver("jdbc:embed:" + SYSDBPATH); - } + @RegisterExtension + @Order(0) + static final EmbeddedRelationalExtension relationalExtension = new EmbeddedRelationalExtension(); private static final String SYSDBPATH = "/" + RelationalKeyspaceProvider.SYS; private static final String TESTDB = "/FRL/jdbc_test_db"; - @AfterAll - public static void afterAll() throws SQLException { - DriverManager.deregisterDriver(driver); + private static RelationalDriver getDriver() { + return relationalExtension.getDriver(); } @Test @@ -117,17 +61,17 @@ public void testGetPropertyInfo() throws SQLException { } @Test - public void testGetMajorVersion() throws SQLException, RelationalException { + public void testGetMajorVersion() throws Exception { Assertions.assertEquals(getDriver().getMajorVersion(), BuildVersion.getInstance().getMajorVersion()); } @Test - public void testGetMinorVersion() throws SQLException, RelationalException { + public void testGetMinorVersion() throws Exception { Assertions.assertEquals(getDriver().getMinorVersion(), BuildVersion.getInstance().getMinorVersion()); } @Test - public void testJDBCCompliant() throws SQLException { + public void testJDBCCompliant() { Assertions.assertFalse(getDriver().jdbcCompliant()); } @@ -139,6 +83,8 @@ public void testGetParentLogger() { @Test public void simpleStatement() throws SQLException { + // Register the FRL domain for this test's CREATE DATABASE call below. + RelationalKeyspaceProvider.instance().registerDomainIfNotExists("FRL"); var jdbcStr = "jdbc:embed:" + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; try (final var connection = getDriver().connect(jdbcStr, null)) { try (Statement statement = connection.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java index b9f03250b6..f0e6869e4c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java @@ -37,8 +37,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.sql.DriverManager; import java.sql.SQLException; +import java.net.URI; /** * Test case-sensitive db object connection option. @@ -153,8 +153,8 @@ void planIsProperlyCachedAndReusedAcrossCaseOptionVariation() throws SQLExceptio Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("select 'id' from 't1' where 'group' = ?"); Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("planCache=\"miss\""); - try (RelationalConnection caseInsensitiveConn = DriverManager.getConnection(database.getConnectionUri() - .toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection caseInsensitiveConn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri() + .toString())).unwrap(RelationalConnection.class)) { caseInsensitiveConn.setSchema("TEST_SCHEMA"); try (RelationalStatement caseInsensitiveStatement = caseInsensitiveConn.createStatement()) { try (RelationalResultSet resultSet = caseInsensitiveStatement.executeQuery( diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java index f8f2175042..9ba2cb4b0d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java @@ -42,7 +42,6 @@ import org.junit.jupiter.params.provider.ValueSource; import java.net.URI; -import java.sql.DriverManager; import java.sql.Statement; import java.util.ArrayList; import java.util.List; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java index 7e205b1edc..5c24464b6a 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java @@ -41,11 +41,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; +import java.net.URI; public class CursorTest { @RegisterExtension @@ -199,7 +199,7 @@ public void continuationWithScanRowLimit() throws SQLException, RelationalExcept Continuation continuation; int numRowsReturned = 0; // 1. Iterate over and count the rows returned before the scan rows limit is hit - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (final var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 3).build())) { conn.setSchema(database.getSchemaName()); try (final var resultSet = conn.createStatement().executeQuery("select * from RESTAURANT")) { @@ -216,7 +216,7 @@ public void continuationWithScanRowLimit() throws SQLException, RelationalExcept } } // 2. Further count the rows in other execution without limits and see if total number of rows is 10 - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (final var preparedStatement = conn.prepareStatement("EXECUTE CONTINUATION ?param")) { preparedStatement.setBytes("param", continuation.serialize()); @@ -241,7 +241,7 @@ public void continuationWithScanRowLimit() throws SQLException, RelationalExcept private void insertRecordsAndTest(int numRecords, BiConsumer, RelationalConnection> test) throws SQLException, RelationalException { final var records = insertAndReturnRecords(numRecords); - try (final var con = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (final var con = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { con.setSchema(database.getSchemaName()); test.accept(records, con); } @@ -249,7 +249,7 @@ private void insertRecordsAndTest(int numRecords, private List insertAndReturnRecords(int numRecords) throws SQLException { List records; - try (final var con = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (final var con = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { con.setSchema(database.getSchemaName()); final var statement = con.createStatement(); records = Utils.generateRestaurantRecords(numRecords); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java index 4577d5cfcb..bd6c765906 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java @@ -45,23 +45,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.Closeable; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.Collections; public class EmbeddedRelationalExtension implements RelationalExtension, BeforeEachCallback, AfterEachCallback { - /** - * One driver kept registered with {@link DriverManager} for the JVM's lifetime. A handful of - * test bodies still call {@code DriverManager.getConnection("jdbc:embed:...")} directly; they - * need at least one driver in the registry. Registered idempotently on the first extension - * setup, never deregistered, so it can never disappear mid-test the way the per-extension - * driver used to. Test helpers that go through this extension (Schema/Database/SchemaTemplate - * rules, {@code Ddl}, {@code RelationalConnectionRule}) instead use the per-extension - * {@link #getDriver() driver} directly and never touch DriverManager. - */ - private static volatile RelationalDriver fallbackRegisteredDriver; - @Nonnull private final KeySpace keySpace; @@ -79,31 +67,30 @@ public class EmbeddedRelationalExtension implements RelationalExtension, BeforeE private StoreCatalog storeCatalog; private FDBDatabase database; private final String clusterFile; - private final boolean register; public EmbeddedRelationalExtension() { this(Options.none()); } public EmbeddedRelationalExtension(@Nonnull final Options options) { - this(FDBTestEnvironment.randomClusterFile(), true, options); + this(FDBTestEnvironment.randomClusterFile(), options); } - public EmbeddedRelationalExtension(final String clusterFile, final boolean register, final Options options) { + public EmbeddedRelationalExtension(final String clusterFile, final Options options) { final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); keyspaceProvider.registerDomainIfNotExists("TEST"); this.keySpace = keyspaceProvider.getKeySpace(); this.storeTimer = new MetricRegistry(); this.clusterFile = clusterFile; - this.register = register; this.options = options; } @Override public void afterEach(ExtensionContext ignored) throws Exception { - // Drop the per-instance driver reference. We do NOT deregister from DriverManager — - // that's what caused the parallel-test race we used to hit (one class's afterEach pulled - // the driver out from under another class's mid-flight test). + // Drop the per-instance driver reference. Nothing to do with java.sql.DriverManager — + // the extension never registers its driver there. Tests should use this extension's + // {@link #getDriver()} (via field injection into the rules and {@code Ddl}), not + // {@code DriverManager.getConnection}. driver = null; } @@ -125,31 +112,6 @@ private void setup() throws RelationalException, SQLException { // most likely touch the catalog, and could affect mixed-mode tests engine = makeEngine(database, FormatVersion.getDefaultFormatVersion()); driver = new EmbeddedRelationalDriver(engine); - if (register) { - // Keep ONE driver permanently registered for the small number of test bodies that - // still call DriverManager.getConnection("jdbc:embed:...") directly. The fallback - // is a SEPARATE driver instance (not the per-extension `driver` above) so that - // tests which deregister/re-register their own driver — e.g. TransactionBoundQueryTest - // — can never knock the fallback out from under other concurrent tests. - ensureFallbackDriverRegistered(engine); - } - } - - private static synchronized void ensureFallbackDriverRegistered(@Nonnull final EmbeddedRelationalEngine engineToWrap) throws SQLException { - if (fallbackRegisteredDriver == null) { - // Brand-new driver instance wrapping the first extension's engine. No test ever - // gets a reference to this object (extensions return their own `driver` field - // instead), so deregistering "the extension driver" can't pull this one out. - fallbackRegisteredDriver = new EmbeddedRelationalDriver(engineToWrap); - } - // Check on every call whether the fallback is still in DriverManager's registry — - // DriverManagerTest's @BeforeEach deliberately nukes every registered driver, so we may - // need to re-add ours each time a test that depends on it sets up. - final RelationalDriver fallback = fallbackRegisteredDriver; - final boolean stillRegistered = DriverManager.drivers().anyMatch(d -> d == fallback); - if (!stillRegistered) { - DriverManager.registerDriver(fallback); - } } public EmbeddedRelationalDriver getDriver(@Nonnull final FormatVersion formatVersion) throws SQLException { @@ -214,7 +176,7 @@ public EmbeddedRelationalExtension extensionForOtherCluster() throws SQLExceptio final String otherClusterFile = FDBTestEnvironment.allClusterFiles().stream() .filter(clusterFile -> !clusterFile.equals(database.getClusterFile())) .findAny().orElseThrow(); - final EmbeddedRelationalExtension embeddedRelationalExtension = new EmbeddedRelationalExtension(otherClusterFile, false, options); + final EmbeddedRelationalExtension embeddedRelationalExtension = new EmbeddedRelationalExtension(otherClusterFile, options); embeddedRelationalExtension.setup(); return embeddedRelationalExtension; } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java index 9df2bd8fc8..c7462a8209 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java @@ -41,10 +41,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.List; import java.util.Map; +import java.net.URI; public class InsertTest { @RegisterExtension @@ -60,7 +60,7 @@ void canInsertWithMultipleRecordTypes() throws SQLException { /* * We want to make sure that we don't accidentally pick up data from different tables */ - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { long id = System.currentTimeMillis(); @@ -150,7 +150,7 @@ void cannotInsertWithIncorrectTypeForRecord() throws SQLException { /* * We want to make sure that we don't accidentally pick up data from different tables */ - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { long id = System.currentTimeMillis(); @@ -164,7 +164,7 @@ final var record = EmbeddedRelationalStruct.newBuilder().addLong("REST_NO", id). @Test void canDifferentiateNullAndDefaultValue() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { long id = System.currentTimeMillis(); @@ -199,7 +199,7 @@ void canDifferentiateNullAndDefaultValue() throws SQLException { @Test void cannotInsertDuplicatePrimaryKey() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { final var struct = EmbeddedRelationalStruct.newBuilder().addLong("REST_NO", 0).build(); @@ -224,7 +224,7 @@ void canInsertNullableArray() throws SQLException { DataType.StructType.Field.from("PRICE", DataType.Primitives.NULLABLE_FLOAT.type(), 1) ), false), false); - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { @@ -282,7 +282,7 @@ void canInsertNullableArray() throws SQLException { @Test void replaceOnInsert() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct record = EmbeddedRelationalStruct.newBuilder() @@ -310,7 +310,7 @@ record = EmbeddedRelationalStruct.newBuilder() @Test void replaceOnFirstInsert() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct record = EmbeddedRelationalStruct.newBuilder().addLong("REST_NO", 0).build(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java index 6072e0db51..d003632e08 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; @@ -57,7 +56,7 @@ public class OptionScopeTest { @Test public void optionTakenFromConnection() throws SQLException, RelationalException { - final var driver = (RelationalDriver) DriverManager.getDriver(db.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (Connection conn = driver.connect(db.getConnectionUri(), Options.builder().withOption(Options.Name.DRY_RUN, true).build())) { conn.setSchema(db.getSchemaName()); try (Statement statement = conn.createStatement()) { @@ -84,7 +83,7 @@ public void optionTakenFromQuery() throws SQLException { @Test public void optionSetInConnectionButOverriddenInQuery() throws SQLException, RelationalException { - final var driver = (RelationalDriver) DriverManager.getDriver(db.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (Connection conn = driver.connect(db.getConnectionUri(), Options.builder().withOption(Options.Name.DRY_RUN, false).build())) { conn.setSchema(db.getSchemaName()); try (Statement statement = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java index a09e54316d..36ee68dee1 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java @@ -42,7 +42,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java index 2be9a04b19..28fee66ea8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java @@ -45,7 +45,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -93,7 +92,7 @@ void scanWithLimit() throws RelationalException, SQLException { } List testScan(Options options, long firstRestNo) throws RelationalException, SQLException { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), options)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java index 1560981ff5..8abba09392 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java @@ -39,7 +39,6 @@ import org.junit.jupiter.params.provider.MethodSource; import javax.annotation.Nonnull; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; @@ -47,6 +46,7 @@ import java.util.List; import java.util.UUID; import java.util.stream.Stream; +import java.net.URI; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -85,7 +85,7 @@ CREATE TABLE B(pk integer, uuid_null uuid array, primary key(pk)) public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, RelationalArrayTest.class, SCHEMA_TEMPLATE); public void insertQuery(@Nonnull String q) throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); try (final var s = conn.createStatement()) { @@ -158,10 +158,10 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { "?string_not_null, ?bytes_null, ?bytes_not_null, ?struct_null, ?struct_not_null, ?uuid_null, " + "?uuid_not_null)"; - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement(statement))) { + try (final var ps = conn.prepareStatement(statement)) { ps.setInt("pk", 1); ps.setArray("boolean_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); @@ -185,10 +185,10 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { } } - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement(statement))) { + try (final var ps = conn.prepareStatement(statement)) { ps.setInt("pk", 2); ps.setNull("boolean_null", Types.ARRAY); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); @@ -212,10 +212,10 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { } } - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement(statement))) { + try (final var ps = conn.prepareStatement(statement)) { ps.setInt("pk", 2); ps.setArray("boolean_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setNull("boolean_not_null", Types.ARRAY); @@ -253,10 +253,10 @@ void testInsertEmptyArrayViaQueryPreparedStatement() throws SQLException { DataType.StructType.Field.from("B", DataType.StringType.nullable(), 2)), false)).build(); - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement(statement))) { + try (final var ps = conn.prepareStatement(statement)) { ps.setInt("pk", 1); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder(DataType.BooleanType.notNullable()).build()); ps.setArray("integer_not_null", EmbeddedRelationalArray.newBuilder(DataType.IntegerType.notNullable()).build()); @@ -270,7 +270,7 @@ void testInsertEmptyArrayViaQueryPreparedStatement() throws SQLException { Assertions.assertEquals(1, ps.executeUpdate()); } - try (final var ps = (RelationalPreparedStatement) conn.prepareStatement("SELECT * from T where pk = 1")) { + try (final var ps = conn.prepareStatement("SELECT * from T where pk = 1")) { ResultSetAssert.assertThat(ps.executeQuery()) .hasNextRow() .hasColumn("boolean_not_null", List.of()) @@ -288,10 +288,10 @@ void testInsertEmptyArrayViaQueryPreparedStatement() throws SQLException { @Test void testMoreParametersThanColumns() throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk) VALUES (?pk, ?boolean_null, ?boolean_not_null)"))) { + try (final var ps = conn.prepareStatement("INSERT INTO T (pk) VALUES (?pk, ?boolean_null, ?boolean_not_null)")) { ps.setInt("pk", 1); ps.setArray("boolean_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); @@ -303,10 +303,10 @@ void testMoreParametersThanColumns() throws SQLException { @Test void testNonNullViolation() throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk) VALUES (?pk)"))) { + try (final var ps = conn.prepareStatement("INSERT INTO T (pk) VALUES (?pk)")) { ps.setInt("pk", 1); RelationalAssertions.assertThrowsSqlException(ps::executeUpdate).containsInMessage("violates not-null constraint") .hasErrorCode(ErrorCode.NOT_NULL_VIOLATION); @@ -316,13 +316,13 @@ void testNonNullViolation() throws SQLException { @Test void testNullFieldsGetAutomaticallyFilled() throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + + try (final var ps = conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + "integer_not_null, bigint_not_null, float_not_null, double_not_null, string_not_null, bytes_not_null, " + "struct_not_null, uuid_not_null) VALUES (?pk, ?boolean_not_null, ?integer_not_null, ?bigint_not_null, " + - "?float_not_null, ?double_not_null, ?string_not_null, ?bytes_not_null, ?struct_not_null, ?uuid_not_null)"))) { + "?float_not_null, ?double_not_null, ?string_not_null, ?bytes_not_null, ?struct_not_null, ?uuid_not_null)")) { ps.setInt("pk", 2); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("integer_not_null", EmbeddedRelationalArray.newBuilder().addAll(11, 22).build()); @@ -336,7 +336,7 @@ void testNullFieldsGetAutomaticallyFilled() throws SQLException { assertEquals(1, ps.executeUpdate()); } - try (final var ps = (RelationalPreparedStatement) conn.prepareStatement("SELECT * from T where pk = 2")) { + try (final var ps = conn.prepareStatement("SELECT * from T where pk = 2")) { ResultSetAssert.assertThat(ps.executeQuery()) .hasNextRow() .hasColumn("boolean_null", null) @@ -354,13 +354,13 @@ void testNullFieldsGetAutomaticallyFilled() throws SQLException { @Test void testColumnRequiresValue() throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + + try (final var ps = conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + "integer_not_null, bigint_not_null, float_not_null, double_not_null, string_not_null, bytes_not_null, " + "struct_not_null) VALUES (?pk, ?boolean_not_null, ?integer_not_null, ?bigint_not_null, ?float_not_null, " + - "?double_not_null, ?string_not_null)"))) { + "?double_not_null, ?string_not_null)")) { ps.setInt("pk", 2); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("integer_not_null", EmbeddedRelationalArray.newBuilder().addAll(11, 22).build()); @@ -405,7 +405,7 @@ void testArrays(int nullArrayIdx, int nonNullArrayIdx, List filledArray, private void testArrays(int nullArrayIdx, int nonNullArrayIdx, List nullArrayElements, List nonNullArrayElements, int sqlType, int pk) throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); try (final var s = conn.createStatement()) { try (final var rs = s.executeQuery("SELECT * from T where pk = " + pk)) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java index 26d1c53efe..78e6284db2 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java @@ -39,7 +39,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.Collections; import java.util.List; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java index bb47ab666a..f4dc8f9820 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java @@ -37,7 +37,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java index 07747497ed..5f386f5ab4 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java @@ -38,7 +38,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java index 79fb3bfff5..6f7c35c0a6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java @@ -34,8 +34,8 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; +import java.net.URI; public class TransactionConfigTest { @RegisterExtension @@ -48,7 +48,7 @@ public class TransactionConfigTest { @Disabled // TODO (Bug: sporadic failure in `testRecordInsertionWithTimeOutInConfig`) void testRecordInsertionWithTimeOutInConfig() throws RelationalException, SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relational.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); conn.setOption(Options.Name.TRANSACTION_TIMEOUT, 1L); try (RelationalStatement s = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java index 979b3a7ba6..15316d95be 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java @@ -40,10 +40,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.net.URI; public class VectorDirectAccessTest { @RegisterExtension @@ -60,7 +60,7 @@ public class VectorDirectAccessTest { @Test void insertNulls() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).build(); @@ -80,7 +80,7 @@ void insertNulls() throws SQLException { @Test void partialInsert() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).addObject("V1", new FloatRealVector(new float[]{1f, 2f, 3f, 4f})).build(); @@ -100,7 +100,7 @@ void partialInsert() throws SQLException { @Test void fullInsert() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0) @@ -124,7 +124,7 @@ void fullInsert() throws SQLException { @Test void insertWrongDimensionFails() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).addObject("V1", new FloatRealVector(new float[] {1f, 2f, 3f, 4f, 5f})).build(); @@ -137,7 +137,7 @@ void insertWrongDimensionFails() throws SQLException { @Test void insertWrongPrecisionFails() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).addObject("V1", new DoubleRealVector(new double[] {1d, 2d, 3d, 4d})).build(); @@ -150,7 +150,7 @@ void insertWrongPrecisionFails() throws SQLException { @Test void insertWrongTypeFails() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).addInt("V1", 42).build(); @@ -163,7 +163,7 @@ void insertWrongTypeFails() throws SQLException { @Test void deleteVectorRecord() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 10) @@ -185,7 +185,7 @@ void deleteVectorRecord() throws SQLException { @Test void deleteNonExistentVectorRecord() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { int deleted = s.executeDelete("V", List.of(new KeySet().setKeyColumn("PK", 999))); @@ -196,7 +196,7 @@ void deleteNonExistentVectorRecord() throws SQLException { @Test void deleteOneOfMultipleVectorRecords() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec1 = EmbeddedRelationalStruct.newBuilder().addInt("PK", 20) @@ -226,7 +226,7 @@ void deleteOneOfMultipleVectorRecords() throws SQLException { @Test void deleteMultipleVectorRecords() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec1 = EmbeddedRelationalStruct.newBuilder().addInt("PK", 30) @@ -264,7 +264,7 @@ void deleteMultipleVectorRecords() throws SQLException { @Test void deleteVectorRecordWithNullVectors() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 40).build(); @@ -282,7 +282,7 @@ void deleteVectorRecordWithNullVectors() throws SQLException { @Test void deleteVectorMaintainsHnswIndex() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { // Insert 3 vectors into VHNSW, all in zone "z1" diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java index 0ffd863b27..68176d5b28 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java @@ -37,7 +37,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java index dead2c4d49..0ac05776d0 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java @@ -41,7 +41,6 @@ import org.junit.jupiter.params.provider.MethodSource; import java.sql.Array; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashSet; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java index 97b0900299..46540e1f20 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java @@ -38,7 +38,6 @@ import java.net.URI; import java.sql.Array; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Collections; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java index e2160db252..a3009e57c2 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java @@ -40,7 +40,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.sql.DriverManager; import java.util.List; public class ExecutePropertyTests { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java index 1556447020..dafd7f18e3 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java @@ -45,7 +45,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.opentest4j.AssertionFailedError; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.Arrays; import java.util.List; @@ -225,7 +224,7 @@ private Pair updateWithScanRowLimit(final String fieldToU Options options) throws SQLException, RelationalException { var continuation = continuationAndNumUpdated.getLeft(); var updatedUpTill = continuationAndNumUpdated.getRight(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (final var con = (EmbeddedRelationalConnection) driver.connect(database.getConnectionUri(), options)) { con.setSchema(database.getSchemaName()); final var statement = prepareUpdate(con, fieldToUpdate, updateValue.apply(con), continuation); From d6e1caf9700acbf9f1f2f01f12af2013ee086eb2 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 15:32:04 -0400 Subject: [PATCH 43/74] Add ResourceLock for PlanGenerator log tests --- .../relational/recordlayer/CaseSensitiveDbObjectsTest.java | 5 +++++ .../relational/recordlayer/QueryLoggingTest.java | 5 +++++ .../relational/recordlayer/SqlFunctionsTest.java | 5 +++++ .../relational/recordlayer/query/PreparedStatementTests.java | 5 +++++ .../relational/recordlayer/query/TemporaryFunctionTests.java | 5 +++++ .../relational/recordlayer/query/UpdateTest.java | 5 +++++ .../query/cache/ValueSpecificConstraintTests.java | 5 +++++ 7 files changed, 35 insertions(+) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java index f0e6869e4c..254fde388f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; import javax.annotation.Nonnull; import java.sql.SQLException; @@ -43,6 +44,10 @@ /** * Test case-sensitive db object connection option. */ +// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test +// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that +// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. +@ResourceLock("PlanGeneratorLogger") public class CaseSensitiveDbObjectsTest { @Nonnull private static final String SCHEMA_TEMPLATE = diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java index 36ee68dee1..e9b0e47c20 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java @@ -39,6 +39,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -51,6 +52,10 @@ /** * Testing basic query logging: plan, time, cache hits, etc. */ +// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test +// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that +// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. +@ResourceLock("PlanGeneratorLogger") public class QueryLoggingTest { @RegisterExtension @Order(0) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java index 24ac9b8c52..b7081f9c60 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java @@ -29,9 +29,14 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; import javax.annotation.Nonnull; +// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test +// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that +// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. +@ResourceLock("PlanGeneratorLogger") public class SqlFunctionsTest { @Nonnull private static final String SCHEMA_TEMPLATE = diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java index abb58036b8..d92b28923f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java @@ -42,6 +42,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -61,6 +62,10 @@ import java.util.function.BiConsumer; import java.util.stream.Stream; +// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test +// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that +// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. +@ResourceLock("PlanGeneratorLogger") public class PreparedStatementTests { private static final String schemaTemplate = diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java index 9f5edaa675..a5165c3a96 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java @@ -57,6 +57,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; @@ -72,6 +73,10 @@ * This is for testing different aspects of temporary SQL functions. This test suite can migrate to YAML once we have * support for multi-statement transactions in YAML. */ +// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test +// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that +// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. +@ResourceLock("PlanGeneratorLogger") public class TemporaryFunctionTests { @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java index dafd7f18e3..b7244b3aa0 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java @@ -43,6 +43,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; import org.opentest4j.AssertionFailedError; import java.sql.SQLException; @@ -51,6 +52,10 @@ import java.util.function.Function; import java.net.URI; +// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test +// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that +// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. +@ResourceLock("PlanGeneratorLogger") public class UpdateTest { private static final String schemaTemplate = diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java index 38700814f7..076a58ff2d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java @@ -37,12 +37,17 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceLock; import javax.annotation.Nonnull; import java.net.URI; import java.util.Map; import java.util.TreeMap; +// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test +// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that +// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. +@ResourceLock("PlanGeneratorLogger") public class ValueSpecificConstraintTests { @RegisterExtension From ec1cc3ed0b330d5ccad32bffad560ba60646f7ee Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 16:12:27 -0400 Subject: [PATCH 44/74] Extend and simplify catalog synchronization The catalog tests have to wipe and recreate the catalog, so those must be isolated from all other relational tests. --- .../recordlayer/CaseSensitivityTest.java | 47 ++++---- .../recordlayer/SystemCatalogQueryTest.java | 8 +- .../RecordLayerStoreCatalogImplTest.java | 7 ++ ...reCatalogWithNoTemplateOperationsTest.java | 7 ++ .../ddl/DdlRecordLayerSchemaTemplateTest.java | 24 ++-- .../relational/utils/CatalogOperations.java | 111 +++++++++++++++++- .../relational/utils/ConnectionUtils.java | 34 +++++- .../relational/utils/DatabaseRule.java | 38 ++---- .../relational/utils/SchemaRule.java | 38 ++---- .../relational/utils/SchemaTemplateRule.java | 51 ++------ 10 files changed, 221 insertions(+), 144 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java index 9ba2cb4b0d..92ef9489c5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.Ddl; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.RelationalAssertions; @@ -77,8 +78,7 @@ void selectFromCaseInsensitiveTable() throws Exception { @Test public void databaseWithSameUpperName() throws Exception { - try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a database statement.executeUpdate("CREATE DATABASE /test/upper"); @@ -91,7 +91,8 @@ public void databaseWithSameUpperName() throws Exception { statement.executeUpdate("DROP DATABASE /test/upper"); } } - } + }); + } private String quote(String dbObject, boolean quote) { @@ -106,8 +107,7 @@ private String quote(String dbObject, boolean quote) { @ValueSource(booleans = {true, false}) public void variousDatabases(boolean quoted) throws Exception { List databases = List.of("/TEST/ABC1", "/TEST/def2", "/TEST/Ghi3", "/TEST/jKL4"); - try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try { try (final var statement = conn.createStatement()) { // Quoted databases @@ -135,15 +135,15 @@ public void variousDatabases(boolean quoted) throws Exception { } } } - } + }); + } @ParameterizedTest @ValueSource(booleans = {true, false}) public void variousSchemas(boolean quoted) throws Exception { List schemas = List.of("ABC2", "def3", "Ghi4", "jKL5"); - try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try { try (final var statement = conn.createStatement()) { statement.executeUpdate("DROP DATABASE if exists /test/various_schemas"); @@ -168,7 +168,8 @@ public void variousSchemas(boolean quoted) throws Exception { statement.executeUpdate("DROP SCHEMA TEMPLATE temp_various_schemas"); } } - } + }); + } @ParameterizedTest @@ -179,8 +180,8 @@ public void variousSchemas(boolean quoted) throws Exception { "SchemaTemplateCatalog))") public void variousStructs(boolean quoted) throws Exception { List structs = List.of(/*"ABC1",*/ "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { for (String struct : structs) { @@ -197,15 +198,16 @@ public void variousStructs(boolean quoted) throws Exception { } } } - } + }); + } @ParameterizedTest @ValueSource(booleans = {true, false}) public void variousTables(boolean quoted) throws Exception { List tables = List.of("ABC1", "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { statement.executeUpdate("DROP DATABASE if exists /test/various_tables_db"); @@ -238,15 +240,16 @@ public void variousTables(boolean quoted) throws Exception { } } } - } + }); + } @ParameterizedTest @ValueSource(booleans = {true, false}) public void variousColumns(boolean quoted) throws Exception { List columns = List.of("ABC1", "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { statement.executeUpdate("CREATE DATABASE /test/various_columns_db"); @@ -277,7 +280,8 @@ public void variousColumns(boolean quoted) throws Exception { } } } - } + }); + } @Test @@ -286,8 +290,8 @@ public void overload() throws Exception { List schemas = List.of("schema", "SCHEMA", "Schema", "ScHeMa"); List tables = List.of("table", "TABLE", "Table", "TaBlE"); List columns = List.of("column", "COLUMN", "Column", "CoLuMn"); - try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { for (String schema : schemas) { @@ -357,6 +361,7 @@ public void overload() throws Exception { } } } - } + }); + } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java index f4dc8f9820..438f8a56a5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; @@ -72,12 +73,7 @@ public void teardown() throws Exception { } private static void runDdl(@Nonnull final String ddl) throws Exception { - try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); - try (final var statement = conn.createStatement()) { - statement.executeUpdate(ddl); - } - } + CatalogOperations.runDdl(relationalExtension.getDriver(), ddl); } private static void createDb(@Nonnull final String dbName) throws Exception { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java index 1adbea789b..30a111aff1 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java @@ -38,6 +38,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.api.Test; import java.net.URI; @@ -45,6 +46,12 @@ import static org.assertj.core.api.Assertions.assertThat; +// Marked @Isolated because @AfterEach calls FDBRecordStore.deleteStore on the SYS catalog +// (/__SYS/CATALOG) — the very record store every other test depends on. Running concurrently +// with any sibling test would wipe the catalog out from under it, surfacing as +// RecordStoreDoesNotExistException in the other test's @BeforeEach. @Isolated tells JUnit to +// suspend all other tests while this class runs. +@Isolated public class RecordLayerStoreCatalogImplTest extends RecordLayerStoreCatalogTestBase { @BeforeEach diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java index a36f6bbc87..0f20d6e6c6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java @@ -37,9 +37,16 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.net.URI; +// Marked @Isolated because @AfterEach calls FDBRecordStore.deleteStore on the SYS catalog +// (/__SYS/CATALOG) — the very record store every other test depends on. Running concurrently +// with any sibling test would wipe the catalog out from under it, surfacing as +// RecordStoreDoesNotExistException in the other test's @BeforeEach. @Isolated tells JUnit to +// suspend all other tests while this class runs. +@Isolated public class RecordLayerStoreCatalogWithNoTemplateOperationsTest extends RecordLayerStoreCatalogTestBase { @BeforeEach diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java index 0ac05776d0..6d5a7088ea 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.apple.foundationdb.relational.util.Assert; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.DdlPermutationGenerator; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.RelationalAssertions; @@ -66,16 +67,21 @@ public static Stream columnTypePermutations() { } private void run(ThrowingConsumer operation) throws RelationalException, SQLException { - try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); - try (RelationalStatement statement = conn.createStatement()) { - operation.accept(statement); - } catch (RelationalException | SQLException | RuntimeException err) { - throw err; - } catch (Throwable throwable) { - Assertions.fail("unexpected error type", throwable); + // Serialise via the JVM-wide catalog lock — `operation` typically issues catalog DDL + // (CREATE/DROP SCHEMA TEMPLATE etc.) against /__SYS/CATALOG. The lock prevents racing + // commits with other test classes; the retry handles residual SQLSTATE 40001 conflicts. + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { + conn.setSchema("CATALOG"); + try (RelationalStatement statement = conn.createStatement()) { + operation.accept(statement); + } catch (SQLException | RuntimeException err) { + throw err; + } catch (Throwable throwable) { + Assertions.fail("unexpected error type", throwable); + } } - } + }); } @Test diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java index 8863a7b105..9e038ef0a8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -20,11 +20,18 @@ package com.apple.foundationdb.relational.utils; +import com.apple.foundationdb.record.provider.foundationdb.RecordStoreDoesNotExistException; +import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.recordlayer.Utils; import javax.annotation.Nonnull; +import java.net.URI; +import java.sql.Connection; import java.sql.SQLException; +import java.sql.Statement; /** * Test-only synchronization helpers for catalog-mutating DDL (CREATE/DROP DATABASE, @@ -38,6 +45,11 @@ * retries any residual conflicts caused by metadata writes the lock doesn't cover * (cluster-global version-stamp updates, etc.). *

+ * The wrapper also retries on {@link RecordStoreDoesNotExistException}: under concurrent + * extension setup, a {@link SchemaRule}'s {@code @BeforeEach} can race the very first call to + * {@code /__SYS} and observe the record store before another thread's bootstrap has fully + * propagated. The retry covers that window. + *

* Wrap CREATE/DROP DDL in {@code @BeforeEach}/{@code @AfterEach} hooks with this helper. Drop * statements should use {@code DROP ... IF EXISTS} so a retry after a partial success doesn't * trip an "unknown ..." error. @@ -53,9 +65,71 @@ public final class CatalogOperations { private static final int MAX_ATTEMPTS = 5; + /** + * URI of the system catalog database every {@link #runDdl} invocation connects against. + */ + private static final URI SYS_CATALOG_URI = URI.create("jdbc:embed:/__SYS"); + private CatalogOperations() { } + /** + * Runs one or more catalog-mutating DDL statements under the JVM-wide catalog lock with + * retry on transient races. Each call opens a fresh connection to {@code /__SYS}, sets the + * schema to {@code CATALOG}, applies {@code connectionOptions}, and runs every statement in + * order. Drop statements should use {@code DROP ... IF EXISTS} so a retry after a partial + * success doesn't trip an "unknown ..." error. + *

+ * This is the preferred entry point for test code that needs to issue catalog DDL — prefer + * it over calling {@link #runLockedWithRetry(ThrowingRunnable)} directly, which only exists + * for callers that need to issue catalog operations on an existing connection or compose + * multiple steps that must share a transaction. + */ + public static void runDdl(@Nonnull final RelationalDriver driver, + @Nonnull final Options connectionOptions, + @Nonnull final String... statements) throws SQLException { + runLockedWithRetry(() -> { + try (Connection connection = driver.connect(SYS_CATALOG_URI)) { + connection.setSchema("CATALOG"); + Utils.setConnectionOptions(connection, connectionOptions); + try (Statement statement = connection.createStatement()) { + for (final String ddl : statements) { + statement.executeUpdate(ddl); + } + } + } + }); + } + + /** + * Convenience overload of {@link #runDdl(RelationalDriver, Options, String...)} with + * {@link Options#NONE}. + */ + public static void runDdl(@Nonnull final RelationalDriver driver, + @Nonnull final String... statements) throws SQLException { + runDdl(driver, Options.NONE, statements); + } + + /** + * Runs {@code action} on an open connection to {@code /__SYS/CATALOG} under the JVM-wide + * catalog lock with retry. Use this for tests that interleave DDL and queries against the + * catalog and need to share a single connection across both. + *

+ * Because the whole body runs under the lock + retry, the {@code action} should be + * idempotent on retry — typically achieved by using {@code DROP ... IF EXISTS} for cleanup + * and choosing test data names that don't collide across reruns. If the {@code action} + * throws a non-retriable {@link SQLException}, it propagates after no retry. + */ + public static void runOnCatalog(@Nonnull final RelationalDriver driver, + @Nonnull final ThrowingConnectionConsumer action) throws SQLException { + runLockedWithRetry(() -> { + try (Connection connection = driver.connect(SYS_CATALOG_URI)) { + connection.setSchema("CATALOG"); + action.accept(connection); + } + }); + } + /** * Functional interface for actions that may throw {@link SQLException}, since most * catalog-mutating DDL runs through JDBC. @@ -65,6 +139,16 @@ public interface ThrowingRunnable { void run() throws SQLException; } + /** + * Functional interface for catalog actions that need access to the open + * {@link Connection}. Used by {@link #runOnCatalog} for tests that interleave DDL with + * queries against the catalog. + */ + @FunctionalInterface + public interface ThrowingConnectionConsumer { + void accept(@Nonnull Connection connection) throws SQLException; + } + /** * Functional interface for catalog actions that may throw {@link RelationalException} * (used by extensions that talk to the catalog via the lower-level Transaction API rather @@ -91,7 +175,7 @@ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) th action.run(); return; } catch (SQLException e) { - if (attempt >= MAX_ATTEMPTS || !isTransactionConflict(e)) { + if (attempt >= MAX_ATTEMPTS || !isRetriable(e)) { throw e; } sleepBeforeRetry(attempt, e); @@ -102,8 +186,9 @@ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) th /** * {@link RelationalException}-throwing variant of {@link #runLockedWithRetry(ThrowingRunnable)}. - * Same lock and retry policy; detects conflicts via either {@link SQLException} SQLSTATE - * 40001 or {@link RelationalException} carrying {@link ErrorCode#SERIALIZATION_FAILURE}. + * Same lock and retry policy; detects retriable failures via either {@link SQLException} + * SQLSTATE 40001 or {@link RelationalException} carrying {@link ErrorCode#SERIALIZATION_FAILURE}, + * plus {@link RecordStoreDoesNotExistException}. * Named distinctly because Java can't disambiguate lambdas between the two * functional-interface overloads (their abstract methods differ only in their throws clause). */ @@ -114,7 +199,7 @@ public static void runLockedWithRelationalRetry(@Nonnull final RelationalThrowin action.run(); return; } catch (RelationalException e) { - if (attempt >= MAX_ATTEMPTS || !isTransactionConflict(e)) { + if (attempt >= MAX_ATTEMPTS || !isRetriable(e)) { throw e; } sleepBeforeRetry(attempt, e); @@ -132,7 +217,20 @@ private static void sleepBeforeRetry(final int attempt, @N } } - private static boolean isTransactionConflict(@Nonnull final Throwable t) { + /** + * Decides whether a thrown exception is worth retrying. We retry on: + *

    + *
  • SQLSTATE 40001 / {@link ErrorCode#SERIALIZATION_FAILURE} — FDB transaction conflicts + * (commits that lost the optimistic-concurrency race against a sibling test). + *
  • {@link RecordStoreDoesNotExistException} — a transient visibility race during the + * first concurrent extension setup, where one thread's bootstrap commit hasn't fully + * propagated by the time another thread connects to {@code /__SYS}. The committed + * state catches up within a few milliseconds, so the small retry loop covers it. + *
+ * Walks the exception causal chain so wrapped variants (e.g. {@code ContextualSQLException} + * wrapping a {@code RelationalException} wrapping a {@code RecordCoreException}) are caught. + */ + private static boolean isRetriable(@Nonnull final Throwable t) { Throwable cursor = t; while (cursor != null) { if (cursor instanceof SQLException @@ -143,6 +241,9 @@ private static boolean isTransactionConflict(@Nonnull final Throwable t) { && ((RelationalException) cursor).getErrorCode() == ErrorCode.SERIALIZATION_FAILURE) { return true; } + if (cursor instanceof RecordStoreDoesNotExistException) { + return true; + } cursor = cursor.getCause(); } return false; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java index 6175abcb2f..6b638e3db6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java @@ -42,12 +42,31 @@ public ConnectionUtils(@Nonnull RelationalDriver driver) { } public void runAgainstCatalog(@Nonnull final SQLConsumer action) throws SQLException, RelationalException { - runAgainstConnection(SYS_DATABASE, CATALOG_SCHEMA, action); + // Catalog operations go through the JVM-wide catalog lock + retry. See {@link CatalogOperations}. + CatalogOperations.runLockedWithRetry(() -> { + try { + runAgainstConnection(SYS_DATABASE, CATALOG_SCHEMA, action); + } catch (RelationalException e) { + // CatalogOperations.runLockedWithRetry only accepts SQLException; surface this as one. + throw e.toSqlException(); + } + }); } @Nullable public R getFromCatalog(@Nonnull final SQLFunction action) throws SQLException, RelationalException { - return getFromConnection(SYS_DATABASE, CATALOG_SCHEMA, action); + // Read-only catalog access — wrap in the catalog lock anyway since concurrent reads alongside + // sibling writes can race on the FDB read-version protocol under load. + @SuppressWarnings("unchecked") + final R[] result = (R[]) new Object[1]; + CatalogOperations.runLockedWithRetry(() -> { + try { + result[0] = getFromConnection(SYS_DATABASE, CATALOG_SCHEMA, action); + } catch (RelationalException e) { + throw e.toSqlException(); + } + }); + return result[0]; } @Nullable @@ -70,7 +89,16 @@ public void runAgainstConnection(@Nonnull final String databaseName, } public void runCatalogStatement(@Nonnull final SQLConsumer action) throws SQLException, RelationalException { - runStatement(SYS_DATABASE, CATALOG_SCHEMA, action); + // Catalog DDL — wrap in {@link CatalogOperations#runLockedWithRetry} so all CREATE/DROP + // DATABASE/SCHEMA TEMPLATE/SCHEMA operations across the test suite are serialised on a + // JVM-wide lock and transparently retried on transient races (SQLSTATE 40001, etc.). + CatalogOperations.runLockedWithRetry(() -> { + try { + runStatement(SYS_DATABASE, CATALOG_SCHEMA, action); + } catch (RelationalException e) { + throw e.toSqlException(); + } + }); } public void runStatement(@Nonnull final String databaseName, diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java index 73ed08fe2d..d26ffaa580 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java @@ -21,9 +21,7 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.recordlayer.RelationalExtension; -import com.apple.foundationdb.relational.recordlayer.Utils; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -32,9 +30,7 @@ import javax.annotation.Nonnull; import java.net.URI; -import java.sql.Connection; import java.sql.SQLException; -import java.sql.Statement; import java.util.Objects; public class DatabaseRule implements BeforeEachCallback, BeforeAllCallback, AfterEachCallback, AfterAllCallback { @@ -76,35 +72,19 @@ public void beforeEach(ExtensionContext context) throws SQLException { setup(); } - @Nonnull - private RelationalDriver driver() { - return Objects.requireNonNull(extension.getDriver(), - "RelationalExtension has no active driver — its @BeforeEach must run before this rule's @BeforeEach."); - } - private void setup() throws SQLException { - CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { - Utils.setConnectionOptions(connection, options); - connection.setSchema("CATALOG"); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); - statement.executeUpdate("CREATE DATABASE \"" + databasePath.getPath() + "\""); - } - } - }); + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + options, + "DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\"", + "CREATE DATABASE \"" + databasePath.getPath() + "\""); } private void tearDown() throws SQLException { - CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { - Utils.setConnectionOptions(connection, options); - connection.setSchema("CATALOG"); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); - } - } - }); + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + options, + "DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); } @Nonnull diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java index 89aad6a066..63631e0d0f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java @@ -21,19 +21,15 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.recordlayer.RelationalExtension; -import com.apple.foundationdb.relational.recordlayer.Utils; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import javax.annotation.Nonnull; import java.net.URI; -import java.sql.Connection; import java.sql.SQLException; -import java.sql.Statement; import java.util.Objects; public class SchemaRule implements BeforeEachCallback, AfterEachCallback { @@ -80,33 +76,17 @@ public String getSchemaName() { return schemaName; } - @Nonnull - private RelationalDriver driver() { - return Objects.requireNonNull(extension.getDriver(), - "RelationalExtension has no active driver — its @BeforeEach must run before this rule's @BeforeEach."); - } - - private void setup() throws Exception { - CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("CREATE SCHEMA \"" + dbUri.getPath() + "/" + schemaName + "\" WITH TEMPLATE \"" + templateName + "\""); - } - } - }); + private void setup() throws SQLException { + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + connectionOptions, + "CREATE SCHEMA \"" + dbUri.getPath() + "/" + schemaName + "\" WITH TEMPLATE \"" + templateName + "\""); } private void tearDown() throws SQLException { - CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP SCHEMA IF EXISTS \"" + dbUri.getPath() + "/" + schemaName + "\""); - } - } - }); + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + connectionOptions, + "DROP SCHEMA IF EXISTS \"" + dbUri.getPath() + "/" + schemaName + "\""); } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java index 414fc886d8..fefbd76e83 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java @@ -21,19 +21,14 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.recordlayer.RelationalExtension; -import com.apple.foundationdb.relational.recordlayer.Utils; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.net.URI; -import java.sql.Connection; import java.sql.SQLException; -import java.sql.Statement; import java.util.Collection; import java.util.Locale; import java.util.Objects; @@ -105,40 +100,16 @@ public String getSchemaTemplateName() { return templateName; } - @Nonnull - private RelationalDriver driver() { - return Objects.requireNonNull(extension.getDriver(), - "RelationalExtension has no active driver — its @BeforeEach must run before this rule's @BeforeEach."); - } - @Override public void afterEach(ExtensionContext context) throws SQLException { - final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE IF EXISTS \"").append(templateName).append("\""); - - CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(dropStatement.toString()); - } - } - }); + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + connectionOptions, + "DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); } @Override public void beforeEach(ExtensionContext context) throws SQLException { - final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE IF EXISTS\"").append(templateName).append("\""); - - CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(dropStatement.toString()); - } - } - }); final StringBuilder createStatement = new StringBuilder("CREATE SCHEMA TEMPLATE \"").append(templateName).append("\" "); createStatement.append(typeCreator.getTypeDefinition()); createStatement.append(tableCreator.getTypeDefinition()); @@ -147,15 +118,11 @@ public void beforeEach(ExtensionContext context) throws SQLException { createStatement.append(schemaTemplateOptions.getOptionsString()); } - CatalogOperations.runLockedWithRetry(() -> { - try (Connection connection = driver().connect(URI.create("jdbc:embed:/__SYS"))) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(createStatement.toString()); - } - } - }); + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + connectionOptions, + "DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\"", + createStatement.toString()); } public static final class SchemaTemplateOptions { From 931f3af78e2376899c6b31e4858cbc202e703a2e Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 16:13:49 -0400 Subject: [PATCH 45/74] Add TODO for cleanup --- .../apple/foundationdb/relational/utils/CatalogOperations.java | 1 + 1 file changed, 1 insertion(+) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java index 9e038ef0a8..a976b7c708 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -175,6 +175,7 @@ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) th action.run(); return; } catch (SQLException e) { + // TODO these retries were added when other code didn't pass through here, can it be removed now if (attempt >= MAX_ATTEMPTS || !isRetriable(e)) { throw e; } From df8d3dfb0d34d4c550efa8a8e50dda34966507e3 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 29 Jun 2026 16:37:45 -0400 Subject: [PATCH 46/74] Isolate all the tests that use LogAppenderRule Tests that assert about logs must be run by themselves, because the log is shared. --- .../recordlayer/CaseSensitiveDbObjectsTest.java | 13 ++++++++----- .../relational/recordlayer/LogAppenderRule.java | 10 ++++++++++ .../relational/recordlayer/QueryLoggingTest.java | 13 ++++++++----- .../relational/recordlayer/SqlFunctionsTest.java | 13 ++++++++----- .../recordlayer/query/PreparedStatementTests.java | 13 ++++++++----- .../recordlayer/query/TemporaryFunctionTests.java | 13 ++++++++----- .../relational/recordlayer/query/UpdateTest.java | 13 ++++++++----- .../query/cache/ValueSpecificConstraintTests.java | 13 ++++++++----- 8 files changed, 66 insertions(+), 35 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java index 254fde388f..1ca8df5cee 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; import java.sql.SQLException; @@ -44,10 +44,13 @@ /** * Test case-sensitive db object connection option. */ -// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test -// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that -// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. -@ResourceLock("PlanGeneratorLogger") +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class CaseSensitiveDbObjectsTest { @Nonnull private static final String SCHEMA_TEMPLATE = diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/LogAppenderRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/LogAppenderRule.java index 3270134982..72729750b1 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/LogAppenderRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/LogAppenderRule.java @@ -36,6 +36,16 @@ import java.util.ArrayList; import java.util.List; +/** + * Test rule that captures Log4j events emitted on a target logger for the duration of a test. + *

+ * The appender is attached to a JVM-global logger, so the rule cannot meaningfully separate its + * own events from events emitted by concurrently-running sibling tests — and a naive thread-id + * filter doesn't help either, because the relational engine dispatches work onto async pools + * (FDB callbacks, CompletableFuture stages, etc.). Test classes that use this rule and assert + * on captured log content should therefore mark themselves {@code @Isolated} so JUnit suspends + * all other tests while they run. See {@code QueryLoggingTest} for an example. + */ public class LogAppenderRule implements BeforeEachCallback, AfterEachCallback, AutoCloseable { private LogAppender logAppender; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java index e9b0e47c20..0d49109675 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java @@ -39,7 +39,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -52,10 +52,13 @@ /** * Testing basic query logging: plan, time, cache hits, etc. */ -// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test -// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that -// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. -@ResourceLock("PlanGeneratorLogger") +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class QueryLoggingTest { @RegisterExtension @Order(0) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java index b7081f9c60..a316fb650e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java @@ -29,14 +29,17 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; -// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test -// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that -// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. -@ResourceLock("PlanGeneratorLogger") +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class SqlFunctionsTest { @Nonnull private static final String SCHEMA_TEMPLATE = diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java index d92b28923f..22c9430974 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java @@ -42,7 +42,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -62,10 +62,13 @@ import java.util.function.BiConsumer; import java.util.stream.Stream; -// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test -// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that -// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. -@ResourceLock("PlanGeneratorLogger") +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class PreparedStatementTests { private static final String schemaTemplate = diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java index a5165c3a96..50718ff7f3 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java @@ -57,7 +57,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; @@ -73,10 +73,13 @@ * This is for testing different aspects of temporary SQL functions. This test suite can migrate to YAML once we have * support for multi-statement transactions in YAML. */ -// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test -// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that -// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. -@ResourceLock("PlanGeneratorLogger") +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class TemporaryFunctionTests { @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java index b7244b3aa0..3529bc7175 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java @@ -43,7 +43,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Isolated; import org.opentest4j.AssertionFailedError; import java.sql.SQLException; @@ -52,10 +52,13 @@ import java.util.function.Function; import java.net.URI; -// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test -// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that -// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. -@ResourceLock("PlanGeneratorLogger") +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class UpdateTest { private static final String schemaTemplate = diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java index 076a58ff2d..1c2989182e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java @@ -37,17 +37,20 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import org.junit.jupiter.api.parallel.ResourceLock; +import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; import java.net.URI; import java.util.Map; import java.util.TreeMap; -// Shares Log4j 'PlanGeneratorLogger' state (the global PlanGenerator logger) with sibling test -// classes via {@link LogAppenderRule}. @ResourceLock serializes us against them so that -// concurrent setLevel/addAppender/getLogs calls don't cross-pollinate or drop messages. -@ResourceLock("PlanGeneratorLogger") +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class ValueSpecificConstraintTests { @RegisterExtension From 66a05c60f86972efaef2762c898876da8ec81df4 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 30 Jun 2026 11:21:16 -0400 Subject: [PATCH 47/74] Wrap more catalog operations in the synchronization This ends up being a lot, it may make more sense to fix the production behavior so that deleting a schema doesn't cause problems. Or, change our tests so that they don't clean up anything until after all tests are done (or ever). --- .../relational/jdbc/JDBCEmbedDriverTest.java | 10 ++- .../recordlayer/SystemCatalogQueryTest.java | 25 ++++---- .../catalog/CatalogMetaDataProviderTest.java | 61 ++++++++++++------- .../recordlayer/ddl/DdlDatabaseTest.java | 39 ++++++------ .../ddl/DdlRecordLayerSchemaTemplateTest.java | 7 ++- .../ddl/DdlRecordLayerSchemaTest.java | 25 ++++---- 6 files changed, 97 insertions(+), 70 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java index 52958f8018..83383568fa 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.util.BuildVersion; +import com.apple.foundationdb.relational.utils.CatalogOperations; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Order; @@ -85,8 +86,11 @@ public void testGetParentLogger() { public void simpleStatement() throws SQLException { // Register the FRL domain for this test's CREATE DATABASE call below. RelationalKeyspaceProvider.instance().registerDomainIfNotExists("FRL"); - var jdbcStr = "jdbc:embed:" + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; - try (final var connection = getDriver().connect(jdbcStr, null)) { + // Catalog mutations + selects from system tables, all serialised + retried via + // CatalogOperations so we don't race other test classes' /__SYS work. + // Not all of this is DDL, but this is easier, and not worth trying to + // better parallelize our tests + CatalogOperations.runOnCatalog(getDriver(), connection -> { try (Statement statement = connection.createStatement()) { // Make this better... currently returns zero how ever many rows we touch. Assertions.assertEquals(0, statement.executeUpdate("Drop database if exists \"" + TESTDB + "\"")); @@ -129,7 +133,7 @@ public void simpleStatement() throws SQLException { statement.executeUpdate("Drop schema template if exists test_template"); } } - } + }); } private void checkSelectStarFromDatabasesResultSet(ResultSet resultSet) throws SQLException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java index 438f8a56a5..7f02eff0f8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java @@ -92,8 +92,8 @@ private static void dropDb(@Nonnull final String dbName) throws Exception { @Test @SuppressWarnings("checkstyle:Indentation") public void selectSchemasWorks() throws SQLException { - try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); //we are selective here to make it easier to check the correctness of the row (otherwise we'd have to put //MetaData objects in for equality) try (RelationalStatement statement = conn.createStatement(); @@ -108,26 +108,26 @@ public void selectSchemasWorks() throws SQLException { new Object[]{"CATALOG", "/__SYS"} )); } - } + }); + } @Test public void selectSchemasWithPredicateAndProjectionWorks() throws SQLException { - try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT schema_name FROM \"SCHEMAS\" WHERE database_id = '/__SYS'")) { shouldBe(rs, Set.of( List.of("CATALOG") )); } - } + }); + } @Disabled // TODO (SystemCatalogQueryTest has fragile tests) @Test public void selectDatabaseInfoWorks() throws SQLException { - try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT * FROM \"DATABASES\"")) { shouldBe(rs, Set.of( List.of("/TEST/DB1"), @@ -136,14 +136,14 @@ public void selectDatabaseInfoWorks() throws SQLException { List.of("/__SYS") )); } - } + }); + } @Disabled // TODO (SystemCatalogQueryTest has fragile tests) @Test public void selectDatabaseInfoWithPredicateAndProjectionWorks() throws RelationalException, SQLException { - try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT database_id FROM \"DATABASES\" WHERE database_id != '/__SYS'")) { shouldBe(rs, Set.of( List.of("/TEST/DB1"), @@ -151,7 +151,8 @@ public void selectDatabaseInfoWithPredicateAndProjectionWorks() throws Relationa List.of("/TEST/DB3") )); } - } + }); + } private static void shouldBe(@Nonnull final ResultSet resultSet, @Nonnull final Set> expected) throws SQLException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/CatalogMetaDataProviderTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/CatalogMetaDataProviderTest.java index 8d37d56cc4..8797c6856d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/CatalogMetaDataProviderTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/CatalogMetaDataProviderTest.java @@ -36,6 +36,7 @@ import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerColumn; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerTable; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.DescriptorAssert; import com.google.protobuf.Descriptors; @@ -55,38 +56,52 @@ void canLoadMetaDataFromStore() throws RelationalException, Descriptors.Descript //now create a RecordStore in that Catalog FDBDatabaseFactory factory = FDBDatabaseFactory.instance(); FdbConnection fdbConn = new DirectFdbConnection(factory.getDatabase(FDBTestEnvironment.randomClusterFile())); - StoreCatalog storeCatalog; - try (Transaction txn = fdbConn.getTransactionManager().createTransaction(Options.NONE)) { - //create the Catalog RecordStore - storeCatalog = StoreCatalogProvider.getCatalog(txn, RelationalKeyspaceProvider.instance().getKeySpace()); - txn.commit(); - } + // Use a holder so we can populate from inside the runLockedWithRelationalRetry lambda. + final StoreCatalog[] storeCatalogHolder = new StoreCatalog[1]; + // Catalog bootstrap: writes the SYS catalog metadata. Serialise + retry against + // concurrent extension setups (which also bootstrap their own StoreCatalog instances). + CatalogOperations.runLockedWithRelationalRetry(() -> { + try (Transaction txn = fdbConn.getTransactionManager().createTransaction(Options.NONE)) { + //create the Catalog RecordStore + storeCatalogHolder[0] = StoreCatalogProvider.getCatalog(txn, RelationalKeyspaceProvider.instance().getKeySpace()); + txn.commit(); + } + }); + final StoreCatalog storeCatalog = storeCatalogHolder[0]; URI dbUri = URI.create("/testdb"); String schemaName = "TEST_SCHEMA" + System.currentTimeMillis(); RecordLayerSchemaTemplate schemaTemplate = createSchemaTemplate(); - try (Transaction txn = fdbConn.getTransactionManager().createTransaction(Options.NONE)) { - //write template into template catalog - storeCatalog.getSchemaTemplateCatalog().createTemplate(txn, schemaTemplate); - //write schema info to the store - Schema schema = schemaTemplate.generateSchema(dbUri.getPath(), schemaName); - storeCatalog.createDatabase(txn, dbUri); - storeCatalog.saveSchema(txn, schema, false); + // Catalog writes (schema template / database / schema rows) — also under the lock. + CatalogOperations.runLockedWithRelationalRetry(() -> { + try (Transaction txn = fdbConn.getTransactionManager().createTransaction(Options.NONE)) { + //write template into template catalog + storeCatalog.getSchemaTemplateCatalog().createTemplate(txn, schemaTemplate); + //write schema info to the store + Schema schema = schemaTemplate.generateSchema(dbUri.getPath(), schemaName); + storeCatalog.createDatabase(txn, dbUri); + storeCatalog.saveSchema(txn, schema, false); - CatalogMetaDataProvider metaDataProvider = new CatalogMetaDataProvider(storeCatalog, dbUri, schemaName, txn); - final RecordMetaData recordMetaData = metaDataProvider.getRecordMetaData(); - final Descriptors.FileDescriptor descriptor = recordMetaData.getRecordsDescriptor(); + CatalogMetaDataProvider metaDataProvider = new CatalogMetaDataProvider(storeCatalog, dbUri, schemaName, txn); + final RecordMetaData recordMetaData = metaDataProvider.getRecordMetaData(); + final Descriptors.FileDescriptor descriptor = recordMetaData.getRecordsDescriptor(); - Descriptors.FileDescriptor expected = Descriptors.FileDescriptor.buildFrom( - schemaTemplate.toRecordMetadata().getRecordsDescriptor().toProto(), // not sure this is correct - new Descriptors.FileDescriptor[]{RecordMetaDataProto.getDescriptor()}); + final Descriptors.FileDescriptor expected; + try { + expected = Descriptors.FileDescriptor.buildFrom( + schemaTemplate.toRecordMetadata().getRecordsDescriptor().toProto(), + new Descriptors.FileDescriptor[]{RecordMetaDataProto.getDescriptor()}); + } catch (Descriptors.DescriptorValidationException dve) { + throw new RelationalException("buildFrom failed", null, dve); + } - for (Descriptors.Descriptor message : descriptor.getMessageTypes()) { - new DescriptorAssert(message).as("Incorrect descriptor for type %s", message.getName()) - .isContainedIn(expected.getMessageTypes()); + for (Descriptors.Descriptor message : descriptor.getMessageTypes()) { + new DescriptorAssert(message).as("Incorrect descriptor for type %s", message.getName()) + .isContainedIn(expected.getMessageTypes()); + } } - } + }); } private RecordLayerSchemaTemplate createSchemaTemplate() throws RelationalException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java index 68176d5b28..4f479b518f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java @@ -26,6 +26,7 @@ import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SchemaTemplateRule; import com.apple.foundationdb.relational.utils.TableDefinition; @@ -59,14 +60,14 @@ public class DdlDatabaseTest { @Test public void canCreateDatabase() throws Exception { try { - try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a database statement.executeUpdate("CREATE DATABASE /test/test_db"); statement.executeUpdate("CREATE SCHEMA /test/test_db/foo_schem with template \"" + baseTemplate.getSchemaTemplateName() + "\""); } - } + }); + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/TEST/TEST_DB")).unwrap(RelationalConnection.class)) { conn.setSchema("FOO_SCHEM"); try (RelationalStatement statement = conn.createStatement()) { @@ -80,11 +81,12 @@ public void canCreateDatabase() throws Exception { } } } finally { - try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS?schema=CATALOG"))) { + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { statement.execute("DROP DATABASE /test/test_db"); } - } + }); + } } @@ -92,8 +94,8 @@ public void canCreateDatabase() throws Exception { @Disabled("TODO") public void dropDatabaseRemovesFromList() throws Exception { final String listCommand = "SHOW DATABASES WITH PREFIX /test_db"; - try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try (RelationalStatement statement = conn.createStatement()) { //create a database statement.executeUpdate("CREATE DATABASE /test_db"); @@ -111,7 +113,8 @@ public void dropDatabaseRemovesFromList() throws Exception { ResultSetAssert.assertThat(rs).isEmpty(); } } - } + }); + } @Test @@ -122,8 +125,7 @@ public void cannotCreateSchemaFromDroppedDatabase() throws Exception { * * This is a drop database test that doesn't require listing the database */ - try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a database statement.executeUpdate("CREATE DATABASE /test/test_db"); @@ -140,13 +142,14 @@ public void cannotCreateSchemaFromDroppedDatabase() throws Exception { .extracting("SQLState") .isEqualTo(ErrorCode.UNDEFINED_DATABASE.getErrorCode()); } - } + }); + } @Test public void cannotCreateDatabaseIfExists() throws Exception { - try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); // assure that there is not a database with the same name from before try (Statement statement = conn.createStatement()) { statement.executeUpdate("DROP DATABASE if exists /test/test_db"); @@ -163,19 +166,21 @@ public void cannotCreateDatabaseIfExists() throws Exception { statement.executeUpdate("DROP DATABASE /test/test_db"); } } - } + }); + } @Test public void cannotCreateSchemaWithoutDatabase() throws Exception { - try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try (Statement statement = conn.createStatement()) { //create a database RelationalAssertions.assertThrowsSqlException(() -> statement.executeUpdate("CREATE SCHEMA /database_that_does_not_exist/schema_that_cannot_be_created with template " + baseTemplate.getSchemaTemplateName())) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE); } - } + }); + } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java index 6d5a7088ea..eaf29dc579 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java @@ -71,8 +71,8 @@ private void run(ThrowingConsumer operation) throws // (CREATE/DROP SCHEMA TEMPLATE etc.) against /__SYS/CATALOG. The lock prevents racing // commits with other test classes; the retry handles residual SQLSTATE 40001 conflicts. CatalogOperations.runLockedWithRetry(() -> { - try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try (RelationalStatement statement = conn.createStatement()) { operation.accept(statement); } catch (SQLException | RuntimeException err) { @@ -80,7 +80,8 @@ private void run(ThrowingConsumer operation) throws } catch (Throwable throwable) { Assertions.fail("unexpected error type", throwable); } - } + }); + }); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java index 46540e1f20..ab02bfcb57 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.DatabaseRule; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SchemaTemplateRule; @@ -62,8 +63,7 @@ public class DdlRecordLayerSchemaTest { @Test void canCreateSchema() throws Exception { - try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a schema final String createStatement = "CREATE SCHEMA " + db.getDbUri() + "/TEST_SCHEMA WITH TEMPLATE " + baseTemplate.getSchemaTemplateName(); @@ -83,20 +83,21 @@ void canCreateSchema() throws Exception { } } } - } + }); + } @Test void canCreateSchemaTemplateWhenConnectedToNonCatalogSchema() throws Exception { - try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (Statement statement = conn.createStatement()) { //create a schema final String createStatement = "CREATE SCHEMA " + db.getDbUri() + "/TEST_SCHEMA WITH TEMPLATE " + baseTemplate.getSchemaTemplateName(); statement.executeUpdate(createStatement); } - } + }); + //now create a new schema in the same db but using a different connection try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:" + db.getDbUri()))) { conn.setSchema("TEST_SCHEMA"); @@ -110,8 +111,7 @@ void canCreateSchemaTemplateWhenConnectedToNonCatalogSchema() throws Exception { @Test void cannotCreateSchemaTwice() throws Exception { - try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (Statement statement = conn.createStatement()) { //create a schema @@ -121,13 +121,13 @@ void cannotCreateSchemaTwice() throws Exception { .hasErrorCode(ErrorCode.SCHEMA_ALREADY_EXISTS); } - } + }); + } @Test void dropSchema() throws Exception { - try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a schema @@ -158,6 +158,7 @@ void dropSchema() throws Exception { RelationalAssertions.assertThrowsSqlException(() -> statement.executeQuery("DESCRIBE SCHEMA " + db.getDbUri() + "/TEST_SCHEMA")) .hasErrorCode(ErrorCode.UNDEFINED_SCHEMA); } - } + }); + } } From a7bd4104f642e4086a38d8b8d99c57828b7b3dbe Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 30 Jun 2026 11:45:16 -0400 Subject: [PATCH 48/74] Update RelationalConnectionTest to not depend on registered driver The switch to not registering the driver caused it to directly call connect() on the driver, but the contract for that is different, namely, the driver is supposed to return null for something it doesn't understand, and the DriverManager converts that to an exception. Update the test to expect the actual contract. --- .../api/RelationalConnectionTest.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java index 0a5aba8f5f..e5a4d71b97 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java @@ -40,19 +40,20 @@ class RelationalConnectionTest { public final EmbeddedRelationalExtension relationalExtension = new EmbeddedRelationalExtension(); @Test - void wrongScheme() { - RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("foo"))) - .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - - RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("foo:foo"))) - .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - - RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:foo"))) - .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - - RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed"))) - .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - + void wrongScheme() throws SQLException { + // The JDBC Driver contract says connect() returns null for URLs the driver doesn't + // accept (so DriverManager can chain to another driver). Calling driver.connect() with + // no-scheme / wrong-scheme URLs directly therefore returns null rather than throwing — + // which is correct per-spec. (Going through DriverManager.getConnection() would convert + // the null into a "No suitable driver found" SQLException; we deliberately avoid that + // here because DriverManagerTest is the single test that exercises DriverManager.) + Assertions.assertThat(relationalExtension.getDriver().connect(URI.create("foo"))).isNull(); + Assertions.assertThat(relationalExtension.getDriver().connect(URI.create("foo:foo"))).isNull(); + Assertions.assertThat(relationalExtension.getDriver().connect(URI.create("jdbc:foo"))).isNull(); + Assertions.assertThat(relationalExtension.getDriver().connect(URI.create("jdbc:embed"))).isNull(); + + // The URL IS accepted (it starts with "jdbc:embed:"), but the path resolves to a + // database that doesn't exist. The driver throws here rather than returning null. RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:/i_am_not_a_database"))) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE); } From 78fc46b009c706252704a260cf669ab3687904b3 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 30 Jun 2026 12:30:26 -0400 Subject: [PATCH 49/74] Make DdlDatabaseTest.canCreateDatabase safe for parallel testing --- .../recordlayer/ddl/DdlDatabaseTest.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java index 4f479b518f..3585070113 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java @@ -41,8 +41,11 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; import java.net.URI; /** @@ -59,31 +62,43 @@ public class DdlDatabaseTest { @Test public void canCreateDatabase() throws Exception { + // Use a unique-per-run database path so the SHOW DATABASES assertion below can't be + // satisfied by a sibling test's leftover database with the same name. The path is + // lower-cased here because the engine normalises identifiers to upper-case, and we want + // to verify the normalised form in the assertion (see jdbcConnectPath / showName below). + final String uniqueSuffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + final String dbPathLower = "/test/test_db_" + uniqueSuffix; + final String dbPathUpper = "/TEST/TEST_DB_" + uniqueSuffix.toUpperCase(Locale.ROOT); try { CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a database - statement.executeUpdate("CREATE DATABASE /test/test_db"); - statement.executeUpdate("CREATE SCHEMA /test/test_db/foo_schem with template \"" + baseTemplate.getSchemaTemplateName() + "\""); + statement.executeUpdate("CREATE DATABASE " + dbPathLower); + statement.executeUpdate("CREATE SCHEMA " + dbPathLower + "/foo_schem with template \"" + baseTemplate.getSchemaTemplateName() + "\""); } }); - try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:/TEST/TEST_DB")).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:" + dbPathUpper)).unwrap(RelationalConnection.class)) { conn.setSchema("FOO_SCHEM"); try (RelationalStatement statement = conn.createStatement()) { - //look to see if it's in the list - Set databases = Set.of("/TEST/TEST_DB", "/__SYS"); + // Assert that our database appears in SHOW DATABASES. We can't use a + // meetsForAllRows-style "every row must be in {our DB, __SYS}" check because + // under parallel execution the catalog also holds databases from + // concurrently-running test classes. Just verify ours is in the list. try (RelationalResultSet rs = statement.executeQuery("SHOW DATABASES")) { - ResultSetAssert.assertThat(rs) - .meetsForAllRows(ResultSetAssert.perRowCondition(resultSet -> databases.contains(resultSet.getString(1)), "Should be a valid database")); + Set seen = new HashSet<>(); + while (rs.next()) { + seen.add(rs.getString(1)); + } + Assertions.assertThat(seen).contains(dbPathUpper); } } } } finally { CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { - statement.execute("DROP DATABASE /test/test_db"); + statement.execute("DROP DATABASE " + dbPathLower); } }); From 81acb5311536e483a6de6007aa8d0d31830ba36b Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 30 Jun 2026 16:10:17 -0400 Subject: [PATCH 50/74] Fix unused imports / indentation / javadocs --- .../relational/recordlayer/CaseSensitivityTest.java | 9 ++++----- .../relational/recordlayer/CursorTest.java | 1 - .../recordlayer/DeleteRangeNoMetadataKeyTest.java | 1 - .../relational/recordlayer/DeleteRangeTest.java | 1 - .../relational/recordlayer/NullsInResultSetTest.java | 1 - .../relational/recordlayer/OptionScopeTest.java | 2 -- .../relational/recordlayer/QueryLoggingTest.java | 1 - .../relational/recordlayer/QueryPropertiesTest.java | 1 - .../relational/recordlayer/RelationalArrayTest.java | 1 - .../relational/recordlayer/RelationalExtension.java | 2 ++ .../recordlayer/SimpleDirectAccessInsertionTests.java | 1 - .../relational/recordlayer/SystemCatalogQueryTest.java | 2 -- .../foundationdb/relational/recordlayer/TableTest.java | 1 - .../relational/recordlayer/TableWithNoPkTest.java | 1 - .../relational/recordlayer/ddl/DdlDatabaseTest.java | 6 +++--- .../ddl/DdlRecordLayerSchemaTemplateTest.java | 3 +-- .../metric/RecordLayerMetricCollectorTest.java | 1 - .../recordlayer/query/CaseSensitivityQueryTests.java | 1 - .../recordlayer/query/ExecutePropertyTests.java | 1 - .../relational/recordlayer/query/ExplainTests.java | 1 - .../recordlayer/query/GroupByQueryTests.java | 1 - .../recordlayer/query/LargeRecordLayerSchemaTest.java | 1 - .../recordlayer/query/PreparedStatementTests.java | 1 - .../recordlayer/query/QueryWithContinuationTest.java | 1 - .../recordlayer/query/StandardQueryTests.java | 1 - .../relational/recordlayer/query/UpdateTest.java | 1 - .../recordlayer/query/V2PlanGeneratorTests.java | 1 - .../query/cache/ValueSpecificConstraintTests.java | 1 - .../recordlayer/structuredsql/SqlVisitorTests.java | 1 - .../structuredsql/StatementBuilderTests.java | 1 - .../relational/utils/CatalogOperations.java | 4 ++++ .../com/apple/foundationdb/relational/server/FRL.java | 10 ++++++---- 32 files changed, 20 insertions(+), 42 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java index 92ef9489c5..ed9b79e0d5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java @@ -32,7 +32,6 @@ import com.apple.foundationdb.relational.utils.Ddl; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.RelationalAssertions; -import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; @@ -181,7 +180,7 @@ public void variousSchemas(boolean quoted) throws Exception { public void variousStructs(boolean quoted) throws Exception { List structs = List.of(/*"ABC1",*/ "def2", "Ghi3", "jKL4"); CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { - RelationalConnection conn = connection.unwrap(RelationalConnection.class); + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { for (String struct : structs) { @@ -207,7 +206,7 @@ public void variousStructs(boolean quoted) throws Exception { public void variousTables(boolean quoted) throws Exception { List tables = List.of("ABC1", "def2", "Ghi3", "jKL4"); CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { - RelationalConnection conn = connection.unwrap(RelationalConnection.class); + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { statement.executeUpdate("DROP DATABASE if exists /test/various_tables_db"); @@ -249,7 +248,7 @@ public void variousTables(boolean quoted) throws Exception { public void variousColumns(boolean quoted) throws Exception { List columns = List.of("ABC1", "def2", "Ghi3", "jKL4"); CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { - RelationalConnection conn = connection.unwrap(RelationalConnection.class); + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { statement.executeUpdate("CREATE DATABASE /test/various_columns_db"); @@ -291,7 +290,7 @@ public void overload() throws Exception { List tables = List.of("table", "TABLE", "Table", "TaBlE"); List columns = List.of("column", "COLUMN", "Column", "CoLuMn"); CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { - RelationalConnection conn = connection.unwrap(RelationalConnection.class); + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { for (String schema : schemas) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java index 5c24464b6a..8af55bfc88 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java @@ -26,7 +26,6 @@ import com.apple.foundationdb.relational.api.Row; import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalConnection; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.RelationalStruct; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java index b3d086f622..51c1e79f05 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java @@ -38,7 +38,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.sql.SQLException; import java.util.List; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java index cb9cadfda7..193224bd05 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java @@ -37,7 +37,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.util.List; /** diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java index 9a359d3a24..1d8e24bd91 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.nio.charset.StandardCharsets; public class NullsInResultSetTest { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java index d003632e08..efaf108712 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java @@ -21,13 +21,11 @@ package com.apple.foundationdb.relational.recordlayer; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.TestSchemas; -import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Order; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java index 0d49109675..71d693326f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java @@ -25,7 +25,6 @@ import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalPreparedStatement; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.recordlayer.query.AstNormalizer; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java index 28fee66ea8..e95ffcb7b8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java @@ -31,7 +31,6 @@ import com.apple.foundationdb.relational.api.KeySet; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.RelationalException; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java index 8abba09392..3be3c3fdad 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java @@ -22,7 +22,6 @@ import com.apple.foundationdb.relational.api.EmbeddedRelationalArray; import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; -import com.apple.foundationdb.relational.api.RelationalPreparedStatement; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.metadata.DataType; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java index 00ca8d0fa5..6a96619bac 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java @@ -32,6 +32,8 @@ public interface RelationalExtension extends Extension { EmbeddedRelationalEngine getEngine(); /** + * Get the driver owned by this extension. + * * @return the driver owned by this extension. May be {@code null} if the extension's * {@code beforeEach} has not run yet, or has been cleaned up. */ diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java index 78e6284db2..1e8bf1c33f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java @@ -32,7 +32,6 @@ import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.TestSchemas; -import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Order; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java index 7f02eff0f8..1b269755aa 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java @@ -26,7 +26,6 @@ import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.ResultSetAssert; -import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -43,7 +42,6 @@ import java.util.List; import java.util.Locale; import java.util.Set; -import java.net.URI; public class SystemCatalogQueryTest { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java index c4ac6ba846..27a33a9bfd 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java @@ -38,7 +38,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java index 5f386f5ab4..453c03be4b 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java @@ -31,7 +31,6 @@ import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SimpleDatabaseRule; import com.apple.foundationdb.relational.utils.RelationalAssertions; -import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Order; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java index 3585070113..77eb8d2cb5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java @@ -110,7 +110,7 @@ public void canCreateDatabase() throws Exception { public void dropDatabaseRemovesFromList() throws Exception { final String listCommand = "SHOW DATABASES WITH PREFIX /test_db"; CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { - RelationalConnection conn = connection.unwrap(RelationalConnection.class); + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try (RelationalStatement statement = conn.createStatement()) { //create a database statement.executeUpdate("CREATE DATABASE /test_db"); @@ -164,7 +164,7 @@ public void cannotCreateSchemaFromDroppedDatabase() throws Exception { @Test public void cannotCreateDatabaseIfExists() throws Exception { CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { - RelationalConnection conn = connection.unwrap(RelationalConnection.class); + RelationalConnection conn = connection.unwrap(RelationalConnection.class); // assure that there is not a database with the same name from before try (Statement statement = conn.createStatement()) { statement.executeUpdate("DROP DATABASE if exists /test/test_db"); @@ -188,7 +188,7 @@ public void cannotCreateDatabaseIfExists() throws Exception { @Test public void cannotCreateSchemaWithoutDatabase() throws Exception { CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { - RelationalConnection conn = connection.unwrap(RelationalConnection.class); + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try (Statement statement = conn.createStatement()) { //create a database RelationalAssertions.assertThrowsSqlException(() -> diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java index eaf29dc579..bca88e1379 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java @@ -49,7 +49,6 @@ import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Stream; -import java.net.URI; /** * End-to-end unit tests for Schema Template language in the RecordLayer. @@ -72,7 +71,7 @@ private void run(ThrowingConsumer operation) throws // commits with other test classes; the retry handles residual SQLSTATE 40001 conflicts. CatalogOperations.runLockedWithRetry(() -> { CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { - RelationalConnection conn = connection.unwrap(RelationalConnection.class); + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try (RelationalStatement statement = conn.createStatement()) { operation.accept(statement); } catch (SQLException | RuntimeException err) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollectorTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollectorTest.java index 1e3e615763..a95017d58f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollectorTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/RecordLayerMetricCollectorTest.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.net.URI; import java.sql.SQLException; import java.util.function.Consumer; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java index ef853acaa7..0ed9e763ff 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java @@ -33,7 +33,6 @@ import org.junit.jupiter.params.provider.ValueSource; import javax.annotation.Nonnull; -import java.net.URI; import java.sql.SQLException; public class CaseSensitivityQueryTests { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java index a3009e57c2..b056adcdc9 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java @@ -22,7 +22,6 @@ import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.exceptions.ContextualSQLException; import com.apple.foundationdb.relational.recordlayer.ContinuationImpl; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java index 01b827ebea..34c5bb03a5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java @@ -38,7 +38,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.sql.SQLException; import java.sql.Types; import java.util.Collections; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java index a2dae22a42..552a498c8c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java @@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import static com.apple.foundationdb.relational.recordlayer.query.QueryTestUtils.insertT1Record; import static com.apple.foundationdb.relational.recordlayer.query.QueryTestUtils.insertT1RecordColAIsNull; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java index 8df7e74c06..62dc390c8d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java @@ -36,7 +36,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.net.URI; import java.sql.ResultSet; import java.util.stream.Collectors; import java.util.stream.IntStream; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java index 22c9430974..1904ee4568 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java @@ -47,7 +47,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.sql.Array; import java.sql.Connection; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/QueryWithContinuationTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/QueryWithContinuationTest.java index 9702bc3e3c..4c8d42e89c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/QueryWithContinuationTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/QueryWithContinuationTest.java @@ -41,7 +41,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.sql.SQLException; import java.util.Objects; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/StandardQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/StandardQueryTests.java index b93695a04c..efeb0ba23f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/StandardQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/StandardQueryTests.java @@ -59,7 +59,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.sql.Array; import java.sql.Connection; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java index 3529bc7175..bafe8ddf6d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java @@ -25,7 +25,6 @@ import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalPreparedStatement; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.recordlayer.ContinuationImpl; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java index f6bc74ebe7..8f635909c4 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; public class V2PlanGeneratorTests { @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java index 1c2989182e..471be6cdb4 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java @@ -40,7 +40,6 @@ import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; -import java.net.URI; import java.util.Map; import java.util.TreeMap; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java index 32e29e674a..d7a866ef0b 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java @@ -37,7 +37,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.net.URI; import java.util.List; import java.util.Map; import java.util.Optional; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java index c03e94a75f..0e574b016e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java @@ -35,7 +35,6 @@ import org.junit.jupiter.params.provider.MethodSource; import javax.annotation.Nonnull; -import java.net.URI; import java.util.List; import java.util.Set; import java.util.stream.Stream; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java index a976b7c708..26a9d209fc 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -192,6 +192,10 @@ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) th * plus {@link RecordStoreDoesNotExistException}. * Named distinctly because Java can't disambiguate lambdas between the two * functional-interface overloads (their abstract methods differ only in their throws clause). + * + * @param action the catalog operation to run + * @throws RelationalException if the action ultimately fails (after retries, or with a + * non-retriable error) */ public static void runLockedWithRelationalRetry(@Nonnull final RelationalThrowingRunnable action) throws RelationalException { synchronized (CATALOG_LOCK) { diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index 199a77b468..6df75f9f87 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -93,21 +93,23 @@ public class FRL implements AutoCloseable { */ private static final Object CATALOG_LOCK = new Object(); + private final FdbConnection fdbDatabase; + private final RelationalDriver driver; + private boolean registeredJDBCEmbedDriver; + /** * Returns the JVM-wide monitor used to serialize catalog-mutating DDL. Hold this when issuing * CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, or any other operation that writes the * cluster-global catalog metadata, to avoid SQLSTATE 40001 conflicts with concurrent * {@link #FRL(Options, String, boolean)} construction or other DDL on the same JVM. + * + * @return the JVM-wide catalog-mutation monitor */ @Nonnull public static Object catalogLock() { return CATALOG_LOCK; } - private final FdbConnection fdbDatabase; - private final RelationalDriver driver; - private boolean registeredJDBCEmbedDriver; - public FRL() throws RelationalException { this(Options.NONE, null); } From af53c51b5d93484b2d9e0f37fcf4ebdce38e72e6 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 30 Jun 2026 16:11:05 -0400 Subject: [PATCH 51/74] Sleep outside of retry for locked catalog operations --- .../relational/utils/CatalogOperations.java | 19 ++++++++----- .../yamltests/block/SetupBlock.java | 27 ++++++++++++------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java index 26a9d209fc..acb93b737c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -169,8 +169,9 @@ public interface RelationalThrowingRunnable { * non-retriable error) */ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) throws SQLException { - synchronized (CATALOG_LOCK) { - for (int attempt = 1; ; attempt++) { + for (int attempt = 1; ; attempt++) { + final SQLException failure; + synchronized (CATALOG_LOCK) { try { action.run(); return; @@ -179,9 +180,13 @@ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) th if (attempt >= MAX_ATTEMPTS || !isRetriable(e)) { throw e; } - sleepBeforeRetry(attempt, e); + failure = e; } } + // Backoff with the lock RELEASED so other catalog ops can make progress between + // retries. Sleeping under the monitor triggers SpotBugs SWL_SLEEP_WITH_LOCK_HELD + // and unnecessarily serialises unrelated work. + sleepBeforeRetry(attempt, failure); } } @@ -198,8 +203,9 @@ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) th * non-retriable error) */ public static void runLockedWithRelationalRetry(@Nonnull final RelationalThrowingRunnable action) throws RelationalException { - synchronized (CATALOG_LOCK) { - for (int attempt = 1; ; attempt++) { + for (int attempt = 1; ; attempt++) { + final RelationalException failure; + synchronized (CATALOG_LOCK) { try { action.run(); return; @@ -207,9 +213,10 @@ public static void runLockedWithRelationalRetry(@Nonnull final RelationalThrowin if (attempt >= MAX_ATTEMPTS || !isRetriable(e)) { throw e; } - sleepBeforeRetry(attempt, e); + failure = e; } } + sleepBeforeRetry(attempt, failure); } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java index 5193c7c554..25eebe7a01 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java @@ -94,9 +94,10 @@ public void execute() { * blocks that include catalog-mutating DDL are expected to follow the same convention. */ private static void runLockedWithRetry(@Nonnull Runnable runnable) { - synchronized (FRL.catalogLock()) { - final int maxAttempts = 5; - for (int attempt = 1; ; attempt++) { + final int maxAttempts = 5; + for (int attempt = 1; ; attempt++) { + final Throwable failure; + synchronized (FRL.catalogLock()) { try { runnable.run(); return; @@ -104,14 +105,22 @@ private static void runLockedWithRetry(@Nonnull Runnable runnable) { if (attempt >= maxAttempts || !isTransactionConflict(e)) { throw e; } - try { - Thread.sleep(10L * attempt); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - throw e; - } + failure = e; } } + // Sleep with the lock RELEASED — keeping it held during backoff would block other + // catalog ops unnecessarily and triggers SpotBugs SWL_SLEEP_WITH_LOCK_HELD. The + // retry races other catalog ops on the next iteration, which is what we want. + try { + Thread.sleep(10L * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + // Wrap the interrupt as the cause to satisfy PMD's PreserveStackTrace; attach + // the original retriable failure as suppressed so its stack trace survives too. + final RuntimeException wrapped = new RuntimeException(ie); + wrapped.addSuppressed(failure); + throw wrapped; + } } } From 74868837a40c018be53714d342edc1be41781213 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 2 Jul 2026 14:46:00 -0400 Subject: [PATCH 52/74] Move CatalogOperations to testFixtures for reuse in other modules --- .../relational/utils/CatalogOperations.java | 85 +++++++++++++++---- 1 file changed, 70 insertions(+), 15 deletions(-) rename fdb-relational-core/src/{test => testFixtures}/java/com/apple/foundationdb/relational/utils/CatalogOperations.java (74%) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java similarity index 74% rename from fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java rename to fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java index acb93b737c..aca99e6579 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/CatalogOperations.java +++ b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -22,10 +22,10 @@ import com.apple.foundationdb.record.provider.foundationdb.RecordStoreDoesNotExistException; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalConnection; import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; -import com.apple.foundationdb.relational.recordlayer.Utils; import javax.annotation.Nonnull; import java.net.URI; @@ -46,10 +46,13 @@ * (cluster-global version-stamp updates, etc.). *

* The wrapper also retries on {@link RecordStoreDoesNotExistException}: under concurrent - * extension setup, a {@link SchemaRule}'s {@code @BeforeEach} can race the very first call to + * extension setup, a {@code SchemaRule}'s {@code @BeforeEach} can race the very first call to * {@code /__SYS} and observe the record store before another thread's bootstrap has fully * propagated. The retry covers that window. *

+ * Lives in {@code testFixtures} so downstream modules (fdb-relational-jdbc, etc.) can serialize + * their catalog DDL against the same JVM-wide monitor. + *

* Wrap CREATE/DROP DDL in {@code @BeforeEach}/{@code @AfterEach} hooks with this helper. Drop * statements should use {@code DROP ... IF EXISTS} so a retry after a partial success doesn't * trip an "unknown ..." error. @@ -57,16 +60,19 @@ public final class CatalogOperations { /** - * JVM-wide monitor guarding catalog-mutating DDL issued from test rules. Local to this - * fdb-relational-core test classpath; FRL construction in fdb-relational-server has its own - * separate {@code FRL.catalogLock()} and is also retry-guarded. + * JVM-wide monitor guarding catalog-mutating DDL issued from test rules. FRL construction + * in fdb-relational-server has its own separate {@code FRL.catalogLock()} and is also + * retry-guarded — a separate lock is fine because FRL bootstrap is a one-time-per-JVM step + * that runs before any tests contend for this monitor. */ private static final Object CATALOG_LOCK = new Object(); private static final int MAX_ATTEMPTS = 5; /** - * URI of the system catalog database every {@link #runDdl} invocation connects against. + * URI of the system catalog database every {@link #runDdl} invocation connects against + * when going through a {@link RelationalDriver}. The {@code jdbc:embed:} scheme is only + * meaningful for {@link com.apple.foundationdb.relational.api.EmbeddedRelationalDriver}. */ private static final URI SYS_CATALOG_URI = URI.create("jdbc:embed:/__SYS"); @@ -75,15 +81,21 @@ private CatalogOperations() { /** * Runs one or more catalog-mutating DDL statements under the JVM-wide catalog lock with - * retry on transient races. Each call opens a fresh connection to {@code /__SYS}, sets the - * schema to {@code CATALOG}, applies {@code connectionOptions}, and runs every statement in - * order. Drop statements should use {@code DROP ... IF EXISTS} so a retry after a partial - * success doesn't trip an "unknown ..." error. + * retry on transient races. Each call opens a fresh connection to {@code /__SYS} via the + * given driver, sets the schema to {@code CATALOG}, applies {@code connectionOptions}, and + * runs every statement in order. Drop statements should use {@code DROP ... IF EXISTS} so + * a retry after a partial success doesn't trip an "unknown ..." error. *

- * This is the preferred entry point for test code that needs to issue catalog DDL — prefer - * it over calling {@link #runLockedWithRetry(ThrowingRunnable)} directly, which only exists - * for callers that need to issue catalog operations on an existing connection or compose - * multiple steps that must share a transaction. + * This is the preferred entry point for tests that already hold a {@link RelationalDriver} + * reference (e.g. via {@code EmbeddedRelationalExtension.getDriver()}) — prefer it over + * calling {@link #runLockedWithRetry(ThrowingRunnable)} directly, which only exists for + * callers that need to compose multiple steps on an existing connection. + * + * @param driver the driver to use for the {@code /__SYS} connection + * @param connectionOptions options to apply to the connection before running the statements + * @param statements DDL statements to run in order + * @throws SQLException if the operation ultimately fails (after retries, or with a + * non-retriable error) */ public static void runDdl(@Nonnull final RelationalDriver driver, @Nonnull final Options connectionOptions, @@ -91,7 +103,7 @@ public static void runDdl(@Nonnull final RelationalDriver driver, runLockedWithRetry(() -> { try (Connection connection = driver.connect(SYS_CATALOG_URI)) { connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); + applyConnectionOptions(connection, connectionOptions); try (Statement statement = connection.createStatement()) { for (final String ddl : statements) { statement.executeUpdate(ddl); @@ -104,6 +116,10 @@ public static void runDdl(@Nonnull final RelationalDriver driver, /** * Convenience overload of {@link #runDdl(RelationalDriver, Options, String...)} with * {@link Options#NONE}. + * + * @param driver the driver to use for the {@code /__SYS} connection + * @param statements DDL statements to run in order + * @throws SQLException if the operation ultimately fails */ public static void runDdl(@Nonnull final RelationalDriver driver, @Nonnull final String... statements) throws SQLException { @@ -119,6 +135,10 @@ public static void runDdl(@Nonnull final RelationalDriver driver, * idempotent on retry — typically achieved by using {@code DROP ... IF EXISTS} for cleanup * and choosing test data names that don't collide across reruns. If the {@code action} * throws a non-retriable {@link SQLException}, it propagates after no retry. + * + * @param driver the driver to use for the {@code /__SYS} connection + * @param action the body to run against the open catalog connection + * @throws SQLException if the action ultimately fails */ public static void runOnCatalog(@Nonnull final RelationalDriver driver, @Nonnull final ThrowingConnectionConsumer action) throws SQLException { @@ -130,12 +150,35 @@ public static void runOnCatalog(@Nonnull final RelationalDriver driver, }); } + /** + * Applies each entry of {@code options} to {@code connection} via + * {@link RelationalConnection#setOption(Options.Name, Object)}. Inlined here (rather than + * imported from a test-source utility) so that this class can live in {@code testFixtures} + * and be consumed from downstream modules. + * + * @param connection the connection to configure + * @param options options to set (may be {@link Options#NONE} for a no-op) + * @throws SQLException if any {@code setOption} call fails + */ + private static void applyConnectionOptions(@Nonnull final Connection connection, + @Nonnull final Options options) throws SQLException { + final RelationalConnection relational = connection.unwrap(RelationalConnection.class); + for (final var entry : options.entries()) { + relational.setOption(entry.getKey(), entry.getValue()); + } + } + /** * Functional interface for actions that may throw {@link SQLException}, since most * catalog-mutating DDL runs through JDBC. */ @FunctionalInterface public interface ThrowingRunnable { + /** + * Runs the catalog action. + * + * @throws SQLException if the action fails + */ void run() throws SQLException; } @@ -146,6 +189,12 @@ public interface ThrowingRunnable { */ @FunctionalInterface public interface ThrowingConnectionConsumer { + /** + * Runs the catalog action against the given connection. + * + * @param connection the open connection to {@code /__SYS/CATALOG} + * @throws SQLException if the action fails + */ void accept(@Nonnull Connection connection) throws SQLException; } @@ -156,6 +205,11 @@ public interface ThrowingConnectionConsumer { */ @FunctionalInterface public interface RelationalThrowingRunnable { + /** + * Runs the catalog action. + * + * @throws RelationalException if the action fails + */ void run() throws RelationalException; } @@ -225,6 +279,7 @@ private static void sleepBeforeRetry(final int attempt, @N Thread.sleep(10L * attempt); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); + pending.addSuppressed(ie); throw pending; } } From 3bfae81b3e8b0be81c94f415eb20c01521ec9421 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 2 Jul 2026 15:01:42 -0400 Subject: [PATCH 53/74] Make fdb-relational-jdbc tests safe for parallel execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each test class in fdb-relational-jdbc was writing to a hardcoded database path (e.g. /FRL/jdbc_test_db, /FRL/SimpleDirectAccessInsertionTests) on the shared FDB cluster with no serialization around the catalog DDL, so parallel Gradle workers — and even parallel classes in one JVM — would collide on the same rows in /__SYS/CATALOG and either see stale state or hit SQLSTATE 40001 serialization conflicts. Refactor JDBCAutoCommitTest, JDBCSimpleStatementTest, JDBCParameterizedQueryComparisonTest, and SimpleDirectAccessInsertionTests to the same shape used elsewhere in the tree: * Per-instance database and schema-template names generated from a random 64-bit suffix in the constructor, so no two test instances ever pick the same path. * Setup and teardown wrapped in CatalogOperations.runLockedWithRetry so every CREATE/DROP participates in the JVM-wide catalog monitor and gets retried on SQLSTATE 40001. * SELECT-from-databases assertions filtered to just the test's own DB + /__SYS, so a sibling class's in-flight database doesn't skew the row count or ordering. * Cleanup uses IF EXISTS variants (and DROP SCHEMA TEMPLATE only, since DROP DATABASE cascades and the DDL parser silently ignores DROP SCHEMA IF EXISTS). --- .../relational/jdbc/JDBCAutoCommitTest.java | 65 ++++++++++----- .../JDBCParameterizedQueryComparisonTest.java | 56 ++++++++----- .../jdbc/JDBCSimpleStatementTest.java | 77 ++++++++++++------ .../SimpleDirectAccessInsertionTests.java | 79 +++++++++++-------- 4 files changed, 182 insertions(+), 95 deletions(-) diff --git a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCAutoCommitTest.java b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCAutoCommitTest.java index 79a153e8b4..3b9ba330fe 100644 --- a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCAutoCommitTest.java +++ b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCAutoCommitTest.java @@ -25,6 +25,7 @@ import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; +import com.apple.foundationdb.relational.utils.CatalogOperations; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -35,6 +36,7 @@ import java.io.IOException; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.concurrent.ThreadLocalRandom; /** * Run tests with AutoCommit=OFF against a remote Relational DB. @@ -42,9 +44,24 @@ */ public class JDBCAutoCommitTest { private static final String SYSDBPATH = "/" + RelationalKeyspaceProvider.SYS; - private static final String TESTDB = "/FRL/jdbc_test_db"; public static final String TEST_SCHEMA = "test_schema"; + // Each test instance gets its own database + schema-template names so that parallel + // test runs (both within this module and across modules that share the FDB cluster) + // can't collide on the catalog. Catalog DDL that sets the database up runs through + // {@link CatalogOperations}, which serialises and retries against SQLSTATE 40001 + // conflicts JVM-wide (see its javadoc for the contract). + private final String testDb; + private final String schemaTemplate; + + public JDBCAutoCommitTest() { + // 16 random hex chars = 64 bits of entropy — plenty to avoid collisions in any + // realistic test run. Prefix stays "jdbc_test_db_" for grep-ability in FDB traces. + final String suffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + this.testDb = "/FRL/jdbc_test_db_" + suffix; + this.schemaTemplate = "test_template_" + suffix; + } + @BeforeAll public static void beforeAll() throws Exception { // Load driver. @@ -399,7 +416,7 @@ protected String getSysDbUri() { } protected String getTestDbUri() { - return "jdbc:relational://" + getHostPort() + TESTDB + "?schema=" + TEST_SCHEMA; + return "jdbc:relational://" + getHostPort() + testDb + "?schema=" + TEST_SCHEMA; } protected String getHostPort() { @@ -428,28 +445,40 @@ private static void insert2ndRow(final RelationalStatement statement) throws SQL Assertions.assertEquals(1, res); } + /** + * Wraps the catalog-mutating DDL in {@link CatalogOperations#runOnCatalog} so that this + * test's {@code CREATE DATABASE}/{@code CREATE SCHEMA TEMPLATE} commits serialise against + * other test classes' catalog work on the same JVM and retry on SQLSTATE 40001. The unique + * {@link #testDb}/{@link #schemaTemplate} names generated per test instance guarantee we + * don't collide with a sibling class's leftover state. + */ private void createDatabase() throws SQLException { - try (RelationalConnection connection = DriverManager.getConnection(getSysDbUri()) - .unwrap(RelationalConnection.class)) { - try (RelationalStatement statement = connection.createStatement()) { - statement.executeUpdate("Drop database if exists \"" + TESTDB + "\""); - statement.executeUpdate("Drop schema template if exists test_template"); - statement.executeUpdate("CREATE SCHEMA TEMPLATE test_template " + - "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))"); - statement.executeUpdate("create database \"" + TESTDB + "\""); - statement.executeUpdate("create schema \"" + TESTDB + "/test_schema\" with template test_template"); - Assertions.assertNull(statement.getWarnings()); + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection connection = DriverManager.getConnection(getSysDbUri()) + .unwrap(RelationalConnection.class)) { + try (RelationalStatement statement = connection.createStatement()) { + statement.executeUpdate("Drop database if exists \"" + testDb + "\""); + statement.executeUpdate("Drop schema template if exists " + schemaTemplate); + statement.executeUpdate("CREATE SCHEMA TEMPLATE " + schemaTemplate + " " + + "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))"); + statement.executeUpdate("create database \"" + testDb + "\""); + statement.executeUpdate("create schema \"" + testDb + "/test_schema\" with template " + schemaTemplate); + Assertions.assertNull(statement.getWarnings()); + } } - } + }); } private void cleanup() throws SQLException { - try (RelationalConnection connection = DriverManager.getConnection(getSysDbUri()) - .unwrap(RelationalConnection.class)) { - try (RelationalStatement statement = connection.createStatement()) { - statement.executeUpdate("Drop database \"" + TESTDB + "\""); + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection connection = DriverManager.getConnection(getSysDbUri()) + .unwrap(RelationalConnection.class)) { + try (RelationalStatement statement = connection.createStatement()) { + statement.executeUpdate("Drop database if exists \"" + testDb + "\""); + statement.executeUpdate("Drop schema template if exists " + schemaTemplate); + } } - } + }); } diff --git a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCParameterizedQueryComparisonTest.java b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCParameterizedQueryComparisonTest.java index 2c95bd4f1f..3c8a4d1fda 100644 --- a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCParameterizedQueryComparisonTest.java +++ b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCParameterizedQueryComparisonTest.java @@ -27,6 +27,7 @@ import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.server.InProcessRelationalServer; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.test.ParameterizedTestUtils; import com.apple.test.RandomizedTestUtils; @@ -366,21 +367,29 @@ public void setUp() throws SQLException { dbPath = "/FRL/parameters_" + uuid; templateName = "template_" + uuid; - try (RelationalConnection conn = getJdbcCatalogConnection()) { - try (RelationalStatement stmt = conn.createStatement()) { - stmt.executeUpdate("DROP DATABASE IF EXISTS \"" + dbPath + "\""); - stmt.executeUpdate("CREATE DATABASE \"" + dbPath + "\""); + // Serialise + retry catalog DDL against the JVM-wide monitor so parallel test classes + // don't race on the shared /__SYS catalog. + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection conn = getJdbcCatalogConnection()) { + try (RelationalStatement stmt = conn.createStatement()) { + stmt.executeUpdate("DROP DATABASE IF EXISTS \"" + dbPath + "\""); + stmt.executeUpdate("CREATE DATABASE \"" + dbPath + "\""); + } } - } + }); } @AfterEach public void tearDown() { - try (RelationalConnection conn = getJdbcCatalogConnection()) { - try (RelationalStatement stmt = conn.createStatement()) { - stmt.executeUpdate("DROP DATABASE \"" + dbPath + "\""); - stmt.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); - } + try { + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection conn = getJdbcCatalogConnection()) { + try (RelationalStatement stmt = conn.createStatement()) { + stmt.executeUpdate("DROP DATABASE IF EXISTS \"" + dbPath + "\""); + stmt.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); + } + } + }); } catch (Exception e) { // best-effort cleanup } @@ -507,16 +516,25 @@ private Connection getConnection(boolean useJdbc) throws SQLException { } private void createSchema(@Nonnull String columnDdl, @Nonnull String extraTypeDdl) throws SQLException { - try (RelationalConnection conn = getJdbcCatalogConnection()) { - try (RelationalStatement stmt = conn.createStatement()) { - String createTemplate = "CREATE SCHEMA TEMPLATE \"" + templateName + "\" " + - extraTypeDdl + " " + - "CREATE TABLE test_table (pk bigint, val " + columnDdl + ", PRIMARY KEY(pk))"; - stmt.executeUpdate(createTemplate); - stmt.executeUpdate("CREATE SCHEMA \"" + dbPath + "/" + SCHEMA_NAME + - "\" WITH TEMPLATE \"" + templateName + "\""); + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection conn = getJdbcCatalogConnection()) { + try (RelationalStatement stmt = conn.createStatement()) { + // Drop the template first: parametrised tests reuse the same fixture, so a + // sibling invocation may already have created a template with this name. + // (The schema itself is per-database and @BeforeEach recreates the database + // fresh each time, so no matching DROP SCHEMA is needed — and the DDL + // parser accepts but does not honour DROP SCHEMA IF EXISTS anyway.) + // TODO double check if this is necessary + stmt.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); + String createTemplate = "CREATE SCHEMA TEMPLATE \"" + templateName + "\" " + + extraTypeDdl + " " + + "CREATE TABLE test_table (pk bigint, val " + columnDdl + ", PRIMARY KEY(pk))"; + stmt.executeUpdate(createTemplate); + stmt.executeUpdate("CREATE SCHEMA \"" + dbPath + "/" + SCHEMA_NAME + + "\" WITH TEMPLATE \"" + templateName + "\""); + } } - } + }); } @FunctionalInterface diff --git a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCSimpleStatementTest.java b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCSimpleStatementTest.java index 9f055a227a..ec5b37b551 100644 --- a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCSimpleStatementTest.java +++ b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCSimpleStatementTest.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.server.ServerTestUtil; import com.apple.foundationdb.relational.server.RelationalServer; +import com.apple.foundationdb.relational.utils.CatalogOperations; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -37,16 +38,28 @@ import java.io.IOException; import java.sql.SQLException; import java.sql.Types; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; /** * Run some simple Statement updates/executes against a remote Relational DB. */ public class JDBCSimpleStatementTest { private static final String SYSDBPATH = "/" + RelationalKeyspaceProvider.SYS; - private static final String TESTDB = "/FRL/jdbc_test_db"; private static RelationalServer relationalServer; + // Per-instance DB and template names so parallel test classes don't collide on the catalog. + private final String testDb; + private final String schemaTemplate; + + public JDBCSimpleStatementTest() { + final String suffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + this.testDb = "/FRL/jdbc_simple_test_db_" + suffix; + this.schemaTemplate = "test_template_" + suffix; + } + /** * Load our JDBCDriver via ServiceLoader so available to test. */ @@ -72,18 +85,24 @@ public void simpleStatement() throws SQLException, IOException { var jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) .unwrap(RelationalConnection.class)) { + // Catalog setup runs under the JVM-wide catalog lock (via CatalogOperations) so we + // don't race with other test classes doing their own CREATE/DROP DATABASE on the + // same FDB cluster. + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalStatement setupStmt = connection.createStatement()) { + Assertions.assertEquals(0, setupStmt.executeUpdate("Drop database if exists \"" + testDb + "\"")); + Assertions.assertEquals(0, setupStmt.executeUpdate("Drop schema template if exists " + schemaTemplate)); + Assertions.assertEquals(0, + setupStmt.executeUpdate("CREATE SCHEMA TEMPLATE " + schemaTemplate + " " + + "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))")); + Assertions.assertEquals(0, setupStmt.executeUpdate("create database \"" + testDb + "\"")); + Assertions.assertEquals(0, setupStmt.executeUpdate("create schema \"" + testDb + + "/test_schema\" with template " + schemaTemplate)); + } + }); try (RelationalStatement statement = connection.createStatement()) { // Exercise some methods to up our test coverage metrics Assertions.assertEquals(connection, statement.getConnection()); - // Make this better... currently returns zero how ever many rows we touch. - Assertions.assertEquals(0, statement.executeUpdate("Drop database if exists \"" + TESTDB + "\"")); - Assertions.assertEquals(0, statement.executeUpdate("Drop schema template if exists test_template")); - Assertions.assertEquals(0, - statement.executeUpdate("CREATE SCHEMA TEMPLATE test_template " + - "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))")); - Assertions.assertEquals(0, statement.executeUpdate("create database \"" + TESTDB + "\"")); - Assertions.assertEquals(0, statement.executeUpdate("create schema \"" + TESTDB + - "/test_schema\" with template test_template")); // Call some of the statement methods for the sake of exercising coverage. Assertions.assertNull(statement.getWarnings()); // Does nothing. @@ -91,11 +110,14 @@ public void simpleStatement() throws SQLException, IOException { // Cancel currently does nothing. statement.cancel(); Assertions.assertFalse(statement.isClosed()); - try (RelationalResultSet resultSet = statement.executeQuery("select * from databases")) { + // Filter to just our own database + SYS so other concurrent test classes' + // leftover DBs don't pollute the assertion. + try (RelationalResultSet resultSet = statement.executeQuery( + "select * from databases where database_id in ('" + testDb + "', '" + SYSDBPATH + "')")) { checkSelectStarFromDatabasesResultSet(resultSet); } try (RelationalPreparedStatement preparedStatement = - connection.prepareStatement("select * from databases")) { + connection.prepareStatement("select * from databases where database_id in ('" + testDb + "', '" + SYSDBPATH + "')")) { try (RelationalResultSet resultSet = preparedStatement.executeQuery()) { checkSelectStarFromDatabasesResultSet(resultSet); } @@ -116,14 +138,17 @@ public void simpleStatement() throws SQLException, IOException { } } } finally { - try (RelationalStatement statement = connection.createStatement()) { - statement.executeUpdate("Drop database \"" + TESTDB + "\""); - } + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalStatement cleanupStmt = connection.createStatement()) { + cleanupStmt.executeUpdate("Drop database if exists \"" + testDb + "\""); + cleanupStmt.executeUpdate("Drop schema template if exists " + schemaTemplate); + } + }); } } } - private static void checkSelectStarFromDatabasesResultSet(RelationalResultSet resultSet) throws SQLException { + private void checkSelectStarFromDatabasesResultSet(RelationalResultSet resultSet) throws SQLException { Assertions.assertNotNull(resultSet); Assertions.assertTrue(resultSet.isWrapperFor(RelationalResultSetFacade.class)); // Exercise some metadata methods to get our jacoco coverage up. @@ -133,15 +158,17 @@ private static void checkSelectStarFromDatabasesResultSet(RelationalResultSet re // Label == name for now. Assertions.assertEquals(columnName, resultSet.getMetaData().getColumnLabel(1)); Assertions.assertEquals(Types.VARCHAR, resultSet.getMetaData().getColumnType(1)); - Assertions.assertTrue(resultSet.next()); - Assertions.assertEquals(TESTDB, resultSet.getString(1)); - Assertions.assertEquals(TESTDB, resultSet.getString(columnName)); - // This should work too. - Assertions.assertEquals(TESTDB, resultSet.getString(columnName.toLowerCase())); - Assertions.assertTrue(resultSet.next()); - Assertions.assertEquals(SYSDBPATH, resultSet.getString(1)); - Assertions.assertEquals(SYSDBPATH, resultSet.getString(columnName)); - Assertions.assertFalse(resultSet.next()); + // The result-set ordering isn't guaranteed relative to our test's DB and /__SYS, so + // collect into a set and assert set equality. Also exercises getString(columnName) and + // its lower-cased variant on each row. + final Set ids = new HashSet<>(); + while (resultSet.next()) { + final String id = resultSet.getString(1); + Assertions.assertEquals(id, resultSet.getString(columnName)); + Assertions.assertEquals(id, resultSet.getString(columnName.toLowerCase())); + ids.add(id); + } + Assertions.assertEquals(Set.of(testDb, SYSDBPATH), ids); resultSet.clearWarnings(); // Does nothing. // For now they are empty. Assertions.assertNull(resultSet.getWarnings()); diff --git a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/SimpleDirectAccessInsertionTests.java b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/SimpleDirectAccessInsertionTests.java index 6f219321b7..b21e828090 100644 --- a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/SimpleDirectAccessInsertionTests.java +++ b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/SimpleDirectAccessInsertionTests.java @@ -29,6 +29,7 @@ import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.server.ServerTestUtil; import com.apple.foundationdb.relational.server.RelationalServer; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.TestSchemas; @@ -45,6 +46,7 @@ import java.sql.Statement; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; /** * Simple unit tests around direct-access insertion tests. @@ -56,12 +58,20 @@ public class SimpleDirectAccessInsertionTests { private static RelationalServer relationalServer; private static final String SCHEMA_NAME = "TEST_SCHEMA"; private static final String SYSDBPATH = "/" + RelationalKeyspaceProvider.SYS; - private static URI databasePath; - private static String templateName; + // Per-instance DB and template names so parallel test classes on the shared FDB cluster + // can't collide on the catalog (see JDBCAutoCommitTest for the same pattern). + private final URI databasePath; + private final String templateName; private static final String RESTAURANT = "RESTAURANT"; private static final String REVIEWER = "RESTAURANT_REVIEWER"; + public SimpleDirectAccessInsertionTests() { + final String suffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + this.databasePath = URI.create("/FRL/SimpleDirectAccessInsertionTests_" + suffix); + this.templateName = "SimpleDirectAccessInsertionTests_" + suffix + "_TEMPLATE"; + } + /** * Load our JDBCDriver via ServiceLoader so available to test. */ @@ -70,10 +80,6 @@ public static void beforeAll() throws SQLException, IOException { // Load driver. JDBCRelationalDriverTest.getDriver(); relationalServer = ServerTestUtil.createAndStartRelationalServer(GrpcConstants.DEFAULT_SERVER_PORT); - // Copied from Simple DatabaseRule Constructor. - databasePath = URI.create("/FRL/" + SimpleDirectAccessInsertionTests.class.getSimpleName()); - templateName = databasePath.getPath().substring(databasePath.getPath().lastIndexOf("/") + 1) + - "_TEMPLATE"; } @AfterAll @@ -89,39 +95,46 @@ public static void afterAll() throws IOException, SQLException { @BeforeEach public void beforeEach() throws SQLException { // Here we do what is done inside in the test extension SimpleDatabaseRule... before and after each test. - String jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; - try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) - .unwrap(RelationalConnection.class)) { - try (Statement statement = connection.createStatement()) { - String createStatement = "CREATE SCHEMA TEMPLATE \"" + templateName + "\" " + - TestSchemas.restaurant(); - statement.executeUpdate(createStatement); - } - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("CREATE DATABASE \"" + databasePath.getPath() + "\""); - } - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("CREATE SCHEMA \"" + databasePath.getPath() + "/" + SCHEMA_NAME + - "\" WITH TEMPLATE \"" + templateName + "\""); + // Serialise the catalog DDL against every other test in this JVM via the JVM-wide + // monitor + retry-on-conflict machinery. + CatalogOperations.runLockedWithRetry(() -> { + String jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; + try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) + .unwrap(RelationalConnection.class)) { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); + statement.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); + } + try (Statement statement = connection.createStatement()) { + String createStatement = "CREATE SCHEMA TEMPLATE \"" + templateName + "\" " + + TestSchemas.restaurant(); + statement.executeUpdate(createStatement); + } + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("CREATE DATABASE \"" + databasePath.getPath() + "\""); + } + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("CREATE SCHEMA \"" + databasePath.getPath() + "/" + SCHEMA_NAME + + "\" WITH TEMPLATE \"" + templateName + "\""); + } } - } + }); } @AfterEach public void afterEach() throws SQLException { - String jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; - try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) - .unwrap(RelationalConnection.class)) { - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP SCHEMA \"" + databasePath.getPath() + "/" + SCHEMA_NAME + "\""); - } - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP DATABASE \"" + databasePath.getPath() + "\""); - } - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP SCHEMA TEMPLATE \"" + templateName + "\""); + CatalogOperations.runLockedWithRetry(() -> { + String jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; + try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) + .unwrap(RelationalConnection.class)) { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); + } + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); + } } - } + }); } @Test From 782a20e82f798affcb67aca38d93e38f7b4223ac Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 6 Jul 2026 10:09:20 -0400 Subject: [PATCH 54/74] Make fdb-relational-server tests safe for parallel execution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RelationalServerTest.simpleJDBCServiceClientOperation(ManagedChannel) was using a hardcoded /FRL/server_test_db path and issuing catalog DDL directly over gRPC with no serialization. Running it alongside the fdb-relational-jdbc tests on the same FDB cluster hit SQLSTATE 40001 conflicts on /__SYS/CATALOG. Refactor the helper to match the pattern now used by the jdbc tests: * Per-invocation random suffix for the database and schema-template names. * Setup and cleanup wrapped in CatalogOperations.runLockedWithRetry, with IF EXISTS defensively dropping any prior state on the way in. * The "select * from databases" assertion filters to just this test's DB + /__SYS so a sibling class's leftover database can't skew the row count. * Cleanup moved into a finally so a mid-test failure still leaves the catalog clean. The helper now throws SQLException, so its callers — RelationalServerTest's simpleJDBCServiceClientOperation()/testMetrics() and InProcessRelationalServerTest's override — declare it too. The testMetrics grpc-call-count assertion is bumped from 7.0 to 8.0 to account for the extra DROP SCHEMA TEMPLATE IF EXISTS now issued during setup (5 setup + 1 execute + 2 cleanup). --- .../fdb-relational-server.gradle | 1 + .../server/InProcessRelationalServerTest.java | 3 +- .../server/RelationalServerTest.java | 48 +++++++++++++------ 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/fdb-relational-server/fdb-relational-server.gradle b/fdb-relational-server/fdb-relational-server.gradle index 279e15e74e..056b9dd0d5 100644 --- a/fdb-relational-server/fdb-relational-server.gradle +++ b/fdb-relational-server/fdb-relational-server.gradle @@ -119,6 +119,7 @@ dependencies { testImplementation(libs.bundles.test.impl) testImplementation(project(':fdb-test-utils')) + testImplementation(testFixtures(project(":fdb-relational-core"))) testRuntimeOnly(libs.bundles.test.runtime) testCompileOnly(libs.bundles.test.compileOnly) testImplementation(libs.grpc.testing) diff --git a/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/InProcessRelationalServerTest.java b/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/InProcessRelationalServerTest.java index 200fe1b82f..6786239d89 100644 --- a/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/InProcessRelationalServerTest.java +++ b/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/InProcessRelationalServerTest.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.sql.SQLException; /** * Test server instance that runs inprocess. @@ -52,7 +53,7 @@ public static void afterAll() throws IOException { * runs and then shut it all down. Runs basic test from sister test class, RelationalServerTest. */ @Test - public void simpleJDBCServiceClientOperation() throws IOException, InterruptedException { + public void simpleJDBCServiceClientOperation() throws SQLException { String serverName = this.server.getServerName(); ManagedChannel managedChannel = InProcessChannelBuilder.forName(serverName).directExecutor().build(); try { diff --git a/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/RelationalServerTest.java b/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/RelationalServerTest.java index 49cb3c6daf..ecb4220fa0 100644 --- a/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/RelationalServerTest.java +++ b/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/RelationalServerTest.java @@ -27,6 +27,7 @@ import com.apple.foundationdb.relational.jdbc.grpc.v1.StatementRequest; import com.apple.foundationdb.relational.jdbc.grpc.v1.StatementResponse; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.test.Tags; import com.google.protobuf.TextFormat; @@ -58,9 +59,11 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class RelationalServerTest { @@ -116,19 +119,31 @@ private static ResultSet execute(JDBCServiceGrpc.JDBCServiceBlockingStub stub, S return statementResponse.hasResultSet() ? statementResponse.getResultSet() : null; } - static void simpleJDBCServiceClientOperation(ManagedChannel managedChannel) { + static void simpleJDBCServiceClientOperation(ManagedChannel managedChannel) throws SQLException { String sysDbPath = "/" + RelationalKeyspaceProvider.SYS; - String testdb = "/FRL/server_test_db"; + // Each invocation gets its own database + template names so that parallel test JVMs + // (this test can be triggered from multiple entry points, and the underlying FDB + // cluster is shared with jdbc-tests etc.) can't collide on the catalog. + final String suffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + final String testdb = "/FRL/server_test_db_" + suffix; + final String schemaTemplate = "test_template_" + suffix; JDBCServiceGrpc.JDBCServiceBlockingStub stub = JDBCServiceGrpc.newBlockingStub(managedChannel); try { - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop database if exists \"" + testdb + "\""); - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop schema template if exists test_template"); - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, - "CREATE SCHEMA TEMPLATE test_template " + - "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))"); - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "create database \"" + testdb + "\""); - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "create schema \"" + testdb + "/test_schema\" with template test_template"); - ResultSet resultSet = execute(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "select * from databases"); + // Serialise catalog DDL against every other test in this JVM via the JVM-wide + // monitor + retry-on-conflict machinery. + CatalogOperations.runLockedWithRetry(() -> { + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop database if exists \"" + testdb + "\""); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop schema template if exists " + schemaTemplate); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, + "CREATE SCHEMA TEMPLATE " + schemaTemplate + " " + + "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))"); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "create database \"" + testdb + "\""); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "create schema \"" + testdb + "/test_schema\" with template " + schemaTemplate); + }); + // Filter the databases result to the two we care about so sibling tests' DBs on + // the shared catalog don't skew the count/order. + ResultSet resultSet = execute(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, + "select * from databases where database_id in ('" + testdb + "', '" + sysDbPath + "')"); Assertions.assertEquals(2, resultSet.getRowCount()); Assertions.assertEquals(1, resultSet.getRow(0).getColumns().getColumnCount()); Assertions.assertEquals(1, resultSet.getRow(1).getColumns().getColumnCount()); @@ -145,7 +160,10 @@ static void simpleJDBCServiceClientOperation(ManagedChannel managedChannel) { } throw t; } finally { - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop database \"" + testdb + "\""); + CatalogOperations.runLockedWithRetry(() -> { + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop database if exists \"" + testdb + "\""); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop schema template if exists " + schemaTemplate); + }); } } @@ -155,7 +173,7 @@ static void simpleJDBCServiceClientOperation(ManagedChannel managedChannel) { * shut it all down. */ @Test - public void simpleJDBCServiceClientOperation() throws IOException, InterruptedException { + public void simpleJDBCServiceClientOperation() throws SQLException { ManagedChannel managedChannel = ManagedChannelBuilder.forTarget("localhost:" + relationalServer.getGrpcPort()).usePlaintext().build(); try { @@ -169,7 +187,7 @@ public void simpleJDBCServiceClientOperation() throws IOException, InterruptedEx * Check the {@link HealthGrpc} Service is up and working. */ @Test - public void healthServiceClientOperation() throws IOException, InterruptedException { + public void healthServiceClientOperation() throws InterruptedException { ManagedChannel managedChannel = ManagedChannelBuilder .forTarget("localhost:" + relationalServer.getGrpcPort()) .usePlaintext().build(); @@ -196,7 +214,7 @@ public void healthServiceClientOperation() throws IOException, InterruptedExcept * Prometheus metrics are made for dashboarding and exotic querying, not for easy evalution in unit tests. */ @Test - public void testMetrics() throws IOException, InterruptedException { + public void testMetrics() throws IOException, InterruptedException, SQLException { // Metrics names recorded for grpc -- all we currently record for prometheus -- can be gotten from // down the page on https://github.com/grpc-ecosystem/java-grpc-prometheus CollectorRegistry collectorRegistry = relationalServer.getCollectorRegistry(); @@ -206,7 +224,7 @@ public void testMetrics() throws IOException, InterruptedException { // Run some queries which will tickle grpc. simpleJDBCServiceClientOperation(); double after = countSampleValues(metricName, metricName + "_total", collectorRegistry); - Assertions.assertEquals(after - before, 7.0/* Expected Difference -- 4 calls*/); + Assertions.assertEquals(after - before, 8.0/* 5 setup + 1 execute + 2 cleanup grpc calls */); // Streaming is not implemented yet so these should be zero. var receivedAfter = findRecordedMetricOrThrow("grpc_server_msg_received", collectorRegistry); Assertions.assertEquals(0, receivedAfter.samples.size()); From f97aecb5593f7b99e98482267ec391b2f34df9a5 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 6 Jul 2026 10:06:56 -0400 Subject: [PATCH 55/74] Tag RecordLayerStoreCatalog test hierarchy with WipesFDB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RecordLayerStoreCatalogImplTest and RecordLayerStoreCatalogWithNoTemplateOperationsTest both call FDBRecordStore.deleteStore on /__SYS/CATALOG in @AfterEach — literally the record store every relational test depends on. They were previously marked @Isolated, which only serialises inside the JVM; a sibling Gradle worker JVM could still see the catalog vanish from under its own tests, or write its own schema to the /__SYS record store just as these tests were about to assert "exactly one schema exists". The correct tag for tests that wipe global FDB state is @Tag(Tags.WipesFDB). The destructiveTest Gradle task includes WipesFDB with maxParallelForks = 1, giving cross-JVM serialisation on top of same-JVM isolation. The tag also routes these tests out of the normal test task so they don't slow every CI build. Hoist the shared setup/teardown lifecycle into the abstract base: * @Tag(Tags.WipesFDB) is declared once on RecordLayerStoreCatalogTestBase. JUnit propagates class-level tags down the inheritance chain, so both concrete subclasses pick it up without redeclaring — see ExtendedDirectoryLayerTest / LocatableResolverTest for the same pattern already in use in fdb-record-layer-core. * @BeforeEach in the base opens the FDB, wipes any leftover catalog, then calls a new abstract createCatalog(txn) that subclasses implement to pick between StoreCatalogProvider.getCatalog(...) and getCatalogWithNoTemplateOperations(...). * @AfterEach delegates to the same private deleteCatalog() helper. The extra pre-test wipe is defensive against a previous run that crashed or was killed between phases and left orphan catalog state behind. * Both concrete subclasses drop their @Isolated, their imports for FDBDatabaseFactory / FDBRecordStore / KeySpacePath / RelationalKeyspaceProvider / FDBTestEnvironment / Tags, and their duplicated @BeforeEach / @AfterEach / deleteCatalog blocks — they now contribute only createCatalog() plus their variant-specific @Tests. --- .../RecordLayerStoreCatalogImplTest.java | 38 ++------------ .../RecordLayerStoreCatalogTestBase.java | 51 +++++++++++++++++++ ...reCatalogWithNoTemplateOperationsTest.java | 37 ++------------ 3 files changed, 61 insertions(+), 65 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java index 30a111aff1..d63c388381 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java @@ -20,12 +20,8 @@ package com.apple.foundationdb.relational.recordlayer.catalog; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore; -import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; -import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.foundationdb.relational.api.Transaction; +import com.apple.foundationdb.relational.api.catalog.StoreCatalog; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.metadata.Metadata; @@ -33,45 +29,21 @@ import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.api.metadata.View; import com.apple.foundationdb.relational.recordlayer.RecordContextTransaction; -import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.api.Test; +import javax.annotation.Nonnull; import java.net.URI; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; -// Marked @Isolated because @AfterEach calls FDBRecordStore.deleteStore on the SYS catalog -// (/__SYS/CATALOG) — the very record store every other test depends on. Running concurrently -// with any sibling test would wipe the catalog out from under it, surfacing as -// RecordStoreDoesNotExistException in the other test's @BeforeEach. @Isolated tells JUnit to -// suspend all other tests while this class runs. -@Isolated public class RecordLayerStoreCatalogImplTest extends RecordLayerStoreCatalogTestBase { - @BeforeEach - void setUpCatalog() throws RelationalException { - fdb = FDBDatabaseFactory.instance().getDatabase(FDBTestEnvironment.randomClusterFile()); - // create a FDBRecordStore - try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); - } - } - - @AfterEach - void deleteAllRecords() throws RelationalException { - try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { - - final KeySpacePath keySpacePath = RelationalKeyspaceProvider.toDatabasePath(URI.create("/__SYS"), keySpace).schemaPath("CATALOG"); - FDBRecordStore.deleteStore(txn.unwrap(FDBRecordContext.class), keySpacePath); - txn.commit(); - } + @Override + protected StoreCatalog createCatalog(@Nonnull Transaction txn) throws RelationalException { + return StoreCatalogProvider.getCatalog(txn, keySpace); } @Test diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogTestBase.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogTestBase.java index 5a883d24ae..7dd4bb57b6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogTestBase.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogTestBase.java @@ -21,7 +21,11 @@ package com.apple.foundationdb.relational.recordlayer.catalog; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; +import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpace; +import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.Transaction; @@ -38,7 +42,12 @@ import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerTable; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerView; +import com.apple.foundationdb.test.FDBTestEnvironment; +import com.apple.test.Tags; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import javax.annotation.Nonnull; @@ -48,6 +57,12 @@ import java.util.HashSet; import java.util.Set; +// Tagged @WipesFDB because @BeforeEach/@AfterEach call FDBRecordStore.deleteStore on the SYS +// catalog (/__SYS/CATALOG) — the very record store every other test in relational depends on. +// The destructiveTest Gradle task runs WipesFDB-tagged tests with maxParallelForks = 1, so no +// sibling JVM (or in-JVM test) can observe the catalog while it's being wiped and rebuilt. +// The tag is inherited by every concrete subclass, so subclasses do not need to redeclare it. +@Tag(Tags.WipesFDB) public abstract class RecordLayerStoreCatalogTestBase { FDBDatabase fdb; @@ -61,6 +76,42 @@ public abstract class RecordLayerStoreCatalogTestBase { keySpace = keyspaceProvider.getKeySpace(); } + @BeforeEach + void setUpCatalog() throws RelationalException { + fdb = FDBDatabaseFactory.instance().getDatabase(FDBTestEnvironment.randomClusterFile()); + // Defensively wipe any state left over from a previous run (e.g. a JVM that crashed + // between @BeforeEach and @AfterEach) so each test starts against an empty catalog. + deleteCatalog(); + try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { + storeCatalog = createCatalog(txn); + txn.commit(); + } + } + + @AfterEach + void deleteAllRecords() throws RelationalException { + deleteCatalog(); + } + + /** + * Build the {@link StoreCatalog} variant this test class exercises. Called from + * {@link #setUpCatalog()} inside a fresh transaction; implementations should not commit — + * the caller commits after this returns. + * + * @param txn the transaction to build the catalog under + * @return the freshly-created catalog + * @throws RelationalException if the catalog cannot be created + */ + protected abstract StoreCatalog createCatalog(@Nonnull Transaction txn) throws RelationalException; + + private void deleteCatalog() throws RelationalException { + try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { + final KeySpacePath keySpacePath = RelationalKeyspaceProvider.toDatabasePath(URI.create("/__SYS"), keySpace).schemaPath("CATALOG"); + FDBRecordStore.deleteStore(txn.unwrap(FDBRecordContext.class), keySpacePath); + txn.commit(); + } + } + @Test void testListSchemasEmptyResult() throws RelationalException, SQLException { // list all schemas diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java index 0f20d6e6c6..7243eff89f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java @@ -20,52 +20,25 @@ package com.apple.foundationdb.relational.recordlayer.catalog; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore; -import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; -import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.foundationdb.relational.api.Transaction; +import com.apple.foundationdb.relational.api.catalog.StoreCatalog; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.metadata.Schema; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.recordlayer.RecordContextTransaction; -import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; +import javax.annotation.Nonnull; import java.net.URI; -// Marked @Isolated because @AfterEach calls FDBRecordStore.deleteStore on the SYS catalog -// (/__SYS/CATALOG) — the very record store every other test depends on. Running concurrently -// with any sibling test would wipe the catalog out from under it, surfacing as -// RecordStoreDoesNotExistException in the other test's @BeforeEach. @Isolated tells JUnit to -// suspend all other tests while this class runs. -@Isolated public class RecordLayerStoreCatalogWithNoTemplateOperationsTest extends RecordLayerStoreCatalogTestBase { - @BeforeEach - void setUpCatalog() throws RelationalException { - fdb = FDBDatabaseFactory.instance().getDatabase(FDBTestEnvironment.randomClusterFile()); - // create a FDBRecordStore - try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { - storeCatalog = StoreCatalogProvider.getCatalogWithNoTemplateOperations(txn); - txn.commit(); - } - } - - @AfterEach - void deleteAllRecords() throws RelationalException { - try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { - final KeySpacePath keySpacePath = RelationalKeyspaceProvider.instance().toDatabasePath(URI.create("/__SYS")).schemaPath("CATALOG"); - FDBRecordStore.deleteStore(txn.unwrap(FDBRecordContext.class), keySpacePath); - txn.commit(); - } + @Override + protected StoreCatalog createCatalog(@Nonnull Transaction txn) throws RelationalException { + return StoreCatalogProvider.getCatalogWithNoTemplateOperations(txn); } @Test From bf07d498eba10f503e2ec253aefdf8c2036e47bc Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 6 Jul 2026 12:12:09 -0400 Subject: [PATCH 56/74] Fix testReadOnlyHasCallerGrv racing with directory-layer cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parallel test execution surfaced a latent race in AgilityContextTest.testReadOnlyHasCallerGrv. The test depends on earlyContext's read version being pinned *before* laterContext committed. Nothing in the test pinned that GRV explicitly. Instead the test relied on the side effect of `path.toSubspace(earlyContext)` — which resolves DirectoryLayer entries and, if any of them need an FDB round-trip, forces earlyContext to grab a GRV in the process. But those resolutions are JVM-cached, so once any sibling test in the same worker JVM has warmed the resolver for this test's path prefix, toSubspace() returns from memory without touching FDB and earlyContext's GRV stays unset. The GRV is then first grabbed inside ReadOnlyNonAgileContext.ctor via callerContext.getReadVersion() — which now happens after laterContext has already committed, so earlyContext (and the derived AgilityContext) both see the write and the assertion fails. Fix: call earlyContext.getReadVersion() immediately after openContext(), before laterContext is opened. That pins the read version deterministically regardless of what the directory-layer cache does. --- .../record/lucene/directory/AgilityContextTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java index cad8a24d3e..0201c33f51 100644 --- a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java +++ b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java @@ -631,6 +631,14 @@ void testReadOnlyHasCallerGrv() throws Exception { byte[] key; FDBRecordContext earlyContext = openContext(); + // Pin earlyContext's read version *now*, before laterContext writes. Otherwise the GRV + // is deferred until the first FDB read against earlyContext — and if path.toSubspace() + // below is served from an in-JVM directory-layer cache (which happens whenever other + // tests in the same JVM have already warmed that resolver), no FDB round-trip occurs + // and earlyContext's GRV ends up being grabbed later — after laterContext commits — + // via `callerContext.getReadVersion()` inside ReadOnlyNonAgileContext.ctor. That would + // let earlyContext see the "Hello" write and break the invariant this test is checking. + earlyContext.getReadVersion(); final Subspace subspace = path.toSubspace(earlyContext); key = subspace.pack(Tuple.from("something")); From 643bcb05e55712013f4af553fc7ea22fff376a4e Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 6 Jul 2026 15:48:47 -0400 Subject: [PATCH 57/74] Fix DL-allocator races in core tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three tests were failing under parallel test execution with allocator IllegalStateException: * FDBRecordStoreReplaceIndexTest.buildReplacementsInMultipleStores * VersionIndexTest.deleteStoreWithUncommittedVersionData [FORMAT_CONTROL, true, true] * FDBReverseDirectoryCacheTest.testReverseDirectoryCacheLookup All three trip the root HighContentionAllocator's hasConflictAtRoot check when a randomly-chosen candidate byte prefix already has keys under it — which happens when sibling tests are concurrently writing at low-integer prefixes. Fix without moving the tests to destructiveTest: * TestKeySpace.STORE_PATH: change from DirectoryLayerDirectory to a KeyType.LONG KeySpaceDirectory. STORE_PATH had only two callers (FDBRecordStoreReplaceIndexTest, VersionIndexTest); both were passing fixed strings ("store_0", "path1", …) that got interned via the shared root DL. Passing raw longs skips the allocator entirely — the id encodes directly into the tuple. * FDBReverseDirectoryCacheTest.testReverseDirectoryCacheLookup: this one legitimately exercises the shared globalScope, so add a resolveWithRetry helper that catches the specific IllegalStateException the FRL HCA and re-invokes globalScope.resolve — the HCA picks a fresh random candidate each attempt, so the retry converges quickly. createRandomDirectoryEntries uses the helper; other test-body resolves look up already-created names and hit the cached forward mapping without allocating, so they don't need the retry. --- .../FDBRecordStoreReplaceIndexTest.java | 23 +++++++------ .../FDBReverseDirectoryCacheTest.java | 33 +++++++++++++++++-- .../indexes/VersionIndexTest.java | 8 +++-- .../record/test/TestKeySpace.java | 11 ++++++- 4 files changed, 57 insertions(+), 18 deletions(-) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreReplaceIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreReplaceIndexTest.java index 2b76e6e1bb..78f30992bb 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreReplaceIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreReplaceIndexTest.java @@ -47,7 +47,6 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.stream.Collectors; -import java.util.stream.IntStream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -235,20 +234,20 @@ public void buildReplacementsInMultipleStores() { final Index newIndex = new Index("MySimpleRecord$(num_value_2, repeater)", Key.Expressions.concat(Key.Expressions.field("num_value_2"), Key.Expressions.field("repeater", KeyExpression.FanType.FanOut))); final RecordMetaDataHook allIndexesHook = composeHooks(addIndexHook(recordTypeName, origIndex), addIndexHook(recordTypeName, newIndex)); - final List stores = IntStream.range(0, 10).mapToObj(i -> "store_" + i).collect(Collectors.toList()); + final int storeCount = 10; try (FDBRecordContext context = openContext()) { openSimpleRecordStore(context, allIndexesHook); // Create a bunch of stores and disable the new index in all of them - forEachStore(multiStoreRoot, stores, (storePathName, subStore) -> { + forEachStore(multiStoreRoot, storeCount, (index, subStore) -> { assertTrue(context.asyncToSync(FDBStoreTimer.Waits.WAIT_DROP_INDEX, subStore.markIndexDisabled(newIndex))); subStore.saveRecord(TestRecords1Proto.MySimpleRecord.newBuilder() .setRecNo(1066L) .addRepeater(42) .addRepeater(800) - .setStrValueIndexed(storePathName) + .setStrValueIndexed("String " + index) .build()); }); @@ -260,7 +259,7 @@ public void buildReplacementsInMultipleStores() { try (FDBRecordContext context = openContext()) { openSimpleRecordStore(context, withReplacementHook); - forEachStore(multiStoreRoot, stores, (storePathName, subStore) -> { + forEachStore(multiStoreRoot, storeCount, (index, subStore) -> { final List> records = context.asyncToSync(FDBStoreTimer.Waits.WAIT_SCAN_RECORDS, subStore.scanIndexRecords(origIndex.getName()).asList()); assertThat(records, hasSize(2)); records.stream() @@ -270,7 +269,7 @@ public void buildReplacementsInMultipleStores() { assertThat(fieldValue, instanceOf(String.class)); return (String)fieldValue; }) - .forEach(strValue -> assertEquals(storePathName, strValue)); + .forEach(strValue -> assertEquals("String " + index, strValue)); context.asyncToSync(FDBStoreTimer.Waits.WAIT_ONLINE_BUILD_INDEX, subStore.rebuildIndex(subStore.getRecordMetaData().getIndex(newIndex.getName()))); }); @@ -281,14 +280,14 @@ public void buildReplacementsInMultipleStores() { // Validate that each store has had the original index removed (because the replacement index was built) try (FDBRecordContext context = openContext()) { openSimpleRecordStore(context, withReplacementHook); - forEachStore(multiStoreRoot, stores, (storePathName, subStore) -> assertTrue(subStore.isIndexDisabled(origIndex.getName()))); + forEachStore(multiStoreRoot, storeCount, (storePathName, subStore) -> assertTrue(subStore.isIndexDisabled(origIndex.getName()))); commit(context); } // Validate each store has had the old index data cleaned out try (FDBRecordContext context = openContext()) { openSimpleRecordStore(context, composeHooks(allIndexesHook, bumpMetaDataVersionHook(), bumpMetaDataVersionHook())); - forEachStore(multiStoreRoot, stores, (storePathName, subStore) -> { + forEachStore(multiStoreRoot, storeCount, (storePathName, subStore) -> { assertTrue(context.asyncToSync(FDBStoreTimer.Waits.WAIT_ADD_INDEX, subStore.uncheckedMarkIndexReadable(origIndex.getName()))); assertEquals(Collections.emptyList(), context.asyncToSync(FDBStoreTimer.Waits.WAIT_SCAN_INDEX_RECORDS, subStore.scanIndexRecords(origIndex.getName()).asList())); }); @@ -302,13 +301,13 @@ public void buildReplacementsInMultipleStores() { } } - private void forEachStore(@Nonnull KeySpacePath root, @Nonnull List storePaths, @Nonnull BiConsumer subStoreConsumer) { - for (String storePathName : storePaths) { - final KeySpacePath storePath = root.add(TestKeySpace.STORE_PATH, storePathName); + private void forEachStore(@Nonnull KeySpacePath root, int count, @Nonnull BiConsumer subStoreConsumer) { + for (int i = 0; i < count; i++) { + final KeySpacePath storePath = root.add(TestKeySpace.STORE_PATH, (long) i); final FDBRecordStore subStore = recordStore.asBuilder() .setKeySpacePath(storePath) .createOrOpen(); - subStoreConsumer.accept(storePathName, subStore); + subStoreConsumer.accept(i, subStore); } } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBReverseDirectoryCacheTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBReverseDirectoryCacheTest.java index 5f8e48964a..887a7c2fdc 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBReverseDirectoryCacheTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBReverseDirectoryCacheTest.java @@ -34,8 +34,8 @@ import com.apple.foundationdb.record.test.FDBDatabaseExtension; import com.apple.foundationdb.record.test.TestKeySpace; import com.apple.foundationdb.record.test.TestKeySpacePathManagerExtension; -import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.foundationdb.record.util.pair.Pair; +import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.foundationdb.tuple.Tuple; import com.apple.test.Tags; import com.google.common.collect.BiMap; @@ -59,6 +59,7 @@ import java.util.Optional; import java.util.Random; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ForkJoinPool; @@ -658,12 +659,40 @@ private Pair[] createRandomDirectoryEntries(FDBDatabase database, keys.add("dir_" + Math.abs(random.nextInt())); } List values = keys.stream() - .map(key -> globalScope.resolve(context.getTimer(), key).join()) + .map(key -> resolveWithRetry(key, context.getTimer())) .collect(Collectors.toList()); return zipKeysAndValues(keys, values); } } + /** + * Resolve a name in {@link #globalScope} with a retry loop around the specific + * "database already has keys in allocation range" / "database has keys stored at the prefix + * chosen by the automatic prefix allocator" failures that the root + * {@link com.apple.foundationdb.directory.DirectoryLayer.HighContentionAllocator} + * throws when a randomly-chosen candidate byte prefix happens to already have keys under it. + * Under parallel test execution any sibling test committing writes at low-integer prefixes + * can trip this check on a fresh candidate — the allocation is fine to retry, since the HCA + * picks a new random candidate each attempt. + */ + private Long resolveWithRetry(String key, FDBStoreTimer timer) { + final int maxAttempts = 20; + RuntimeException lastFailure = null; + for (int attempt = 0; attempt < maxAttempts; attempt++) { + try { + return globalScope.resolve(timer, key).join(); + } catch (CompletionException | IllegalStateException e) { + Throwable cause = e instanceof CompletionException ? e.getCause() : e; + if (cause instanceof IllegalStateException) { + lastFailure = e; + continue; + } + throw e; + } + } + throw new AssertionError("globalScope.resolve failed after " + maxAttempts + " attempts", lastFailure); + } + @SuppressWarnings({"rawtypes", "unchecked"}) private static Pair[] zipKeysAndValues(List keys, List values) { final Iterator valuesIter = values.iterator(); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VersionIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VersionIndexTest.java index b0227434ed..1db8aa2d54 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VersionIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VersionIndexTest.java @@ -3045,9 +3045,11 @@ void deleteStoreWithUncommittedVersionData(FormatVersion testFormatVersion, bool splitLongRecords = testSplitLongRecords; final KeySpacePath baseMultiPath = pathManager.createPath(TestKeySpace.MULTI_RECORD_STORE); - final KeySpacePath path1 = baseMultiPath.add(TestKeySpace.STORE_PATH, "path1"); - final KeySpacePath path2 = baseMultiPath.add(TestKeySpace.STORE_PATH, "path2"); - final KeySpacePath path3 = baseMultiPath.add(TestKeySpace.STORE_PATH, "path3"); + // STORE_PATH is a KeyType.LONG KeySpaceDirectory (see TestKeySpace) — pass numeric ids + // rather than strings so no shared root-DL allocation happens here. + final KeySpacePath path1 = baseMultiPath.add(TestKeySpace.STORE_PATH, 1L); + final KeySpacePath path2 = baseMultiPath.add(TestKeySpace.STORE_PATH, 2L); + final KeySpacePath path3 = baseMultiPath.add(TestKeySpace.STORE_PATH, 3L); final List multiPaths = List.of(path1, path2, path3); final Map version1ByStore = new HashMap<>(); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpace.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpace.java index a00012f24c..9903509e63 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpace.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/test/TestKeySpace.java @@ -55,7 +55,16 @@ public class TestKeySpace { .addSubdirectory(new DirectoryLayerDirectory(META_DATA_STORE, META_DATA_STORE)) .addSubdirectory(new DirectoryLayerDirectory(RAW_DATA, RAW_DATA)) .addSubdirectory(new DirectoryLayerDirectory(MULTI_RECORD_STORE, MULTI_RECORD_STORE) - .addSubdirectory(new DirectoryLayerDirectory(STORE_PATH)) + // STORE_PATH is a plain LONG-typed KeySpaceDirectory rather than a + // DirectoryLayerDirectory. It used to be a DirectoryLayerDirectory, + // which meant every unique test-supplied sub-store key (e.g. "store_0") + // triggered a fresh allocation against the JVM-wide root + // HighContentionAllocator. Under parallel test execution that racy + // allocator was hitting hasConflictAtRoot failures with sibling tests. + // A LONG here lets each test supply its own integer sub-store id + // (0, 1, 2, ...) which encodes directly into the tuple with no shared + // allocator involved. + .addSubdirectory(new KeySpaceDirectory(STORE_PATH, KeySpaceDirectory.KeyType.LONG)) ) .addSubdirectory(new DirectoryLayerDirectory(RESOLVER_MAPPING_REPLICATOR, RESOLVER_MAPPING_REPLICATOR) .addSubdirectory(new KeySpaceDirectory("to", KeySpaceDirectory.KeyType.STRING, "to") From 2ef1c57d51e471ec71a5ddd2bf635918fb0d0ea2 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 6 Jul 2026 16:40:49 -0400 Subject: [PATCH 58/74] Decrease batch size of VectorIndexTest.insertReadDeleteReadGroupedWithContinuationTest This test is getting transaction-too-old now that we have parallelism, try just decreasing the batch size a bit. --- .../record/provider/foundationdb/indexes/VectorIndexTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexTest.java index 922109400f..2eb0436799 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexTest.java @@ -276,7 +276,7 @@ private void checkResultsGrouped(@Nonnull final RecordQueryIndexPlan indexPlan, void insertReadDeleteReadGroupedWithContinuationTest(final long seed, final boolean useAsync, final int limit) throws Exception { final int size = 1000; Assertions.assertThat(size % 2).isEqualTo(0); // needs to be even - final int updateBatchSize = 50; + final int updateBatchSize = 25; Assertions.assertThat(size % updateBatchSize).isEqualTo(0); // needs to be divisible final int k = 100; From 4d1eb9932ee70a81d5092578d686d39f1760ce65 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 6 Jul 2026 17:38:29 -0400 Subject: [PATCH 59/74] Stop closing the shared FDBDatabase in EmbeddedRelationalExtension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RecordTypeKeyTest.testScanningWithUnknownKeys — and, sporadically, any other test that opens an FDB transaction — failed under parallel test execution with: IllegalStateException: Cannot access closed object at NativeObjectWrapper.getPtr at FDBDatabase.createTransaction EmbeddedRelationalExtension.makeDatabase was: try (var connection = new DirectFdbConnection(database); Transaction txn = ...) { ... } DirectFdbConnection.close() calls fdb.close() on the underlying FDBDatabase, but the `database` field here holds the shared handle returned by FDBDatabaseFactory.instance().getDatabase(clusterFile) — the same JVM-wide handle every other test is using. Every extension @BeforeEach closed that shared handle. Any sibling test that reached NativeObjectWrapper.getPtr during that window hit "Cannot access closed object", which the test's assertion helper reported as a mismatch with the expected SQLException. Fix: pull `connection` out of the try-with-resources — only close the Transaction. The DirectFdbConnection wrapper is lightweight; the FDBDatabase belongs to the factory cache and must stay open for the JVM's lifetime. --- .../recordlayer/EmbeddedRelationalExtension.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java index 8679ce62f1..bd382294b9 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java @@ -124,9 +124,18 @@ private void makeDatabase(String clusterFile) throws RelationalException { // races with every other test class's @BeforeEach. Without the JVM-wide lock, parallel // class execution surfaces these races as `FDBStoreTransactionConflictException`. See // CatalogOperations for the lock contract. + // + // Deliberately NOT try-with-resources on the DirectFdbConnection — its close() calls + // fdb.close() on the shared FDBDatabaseFactory handle that every other test in this + // JVM is holding. Closing it here (once per @BeforeEach) invalidates the native pointer + // and any sibling test with an in-flight transaction fails with + // `IllegalStateException: Cannot access closed object` from NativeObjectWrapper.getPtr. + // We only need the Transaction closed; the connection is a lightweight wrapper we can + // leak. + // Probably DirectFdbConnection should not close the underlying database CatalogOperations.runLockedWithRelationalRetry(() -> { - try (var connection = new DirectFdbConnection(database); - Transaction txn = connection.getTransactionManager().createTransaction(Options.NONE)) { + final var connection = new DirectFdbConnection(database); + try (Transaction txn = connection.getTransactionManager().createTransaction(Options.NONE)) { storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); txn.commit(); } From 7c9bbed117089394e6a1f3bba58e79fb6606e872 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 7 Jul 2026 14:51:30 -0400 Subject: [PATCH 60/74] Add concurrency to YamlTests, but protect concurrent usages of the file This sets Everything annotated with @YamlTest to run the test methods concurrently, but adds a resource-lock so that the individual parameterizations of each method run serially --- .../PerMethodResourceLocksProvider.java | 61 +++++++++++++++++++ .../relational/yamltests/YamlTest.java | 17 ++++++ 2 files changed, 78 insertions(+) create mode 100644 yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/PerMethodResourceLocksProvider.java diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/PerMethodResourceLocksProvider.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/PerMethodResourceLocksProvider.java new file mode 100644 index 0000000000..df6ab555d4 --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/PerMethodResourceLocksProvider.java @@ -0,0 +1,61 @@ +/* + * PerMethodResourceLocksProvider.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.relational.yamltests; + +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLocksProvider; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +/** + * A {@link ResourceLocksProvider} that hands each {@code @YamlTest} test method its own unique + * lock keyed by the method's fully-qualified name. + * + *

The point is to let JUnit run different {@code @TestTemplate} methods in {@code @YamlTest} + * classes in parallel with each other while still serialising the multiple + * {@link org.junit.jupiter.api.TestTemplate} invocations within a single method. Each + * yamsql file typically hard-codes cluster-wide state (databases, schema templates, plan-cache + * seeds, planner-metric expectations) that the framework relies on being touched by only one + * runner configuration at a time — so two invocations of the same method against the same + * cluster can't safely run in parallel, but two different methods against + * different yamsql files (with disjoint hard-coded names) can. + * + *

Wiring: {@code @YamlTest} is meta-annotated with + * {@code @ResourceLock(providers = PerMethodResourceLocksProvider.class)}. JUnit invokes + * {@link #provideForMethod} once per test method, and the returned lock is applied uniformly + * to every {@link org.junit.jupiter.api.TestTemplate} invocation of that method — so + * invocations of one method compete for the shared key and run one at a time, while invocations + * of different methods hold different keys and can proceed concurrently. + */ +public class PerMethodResourceLocksProvider implements ResourceLocksProvider { + + private static final String KEY_PREFIX = "yaml-test/"; + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { + // The key uses the fully-qualified class name so that if two @YamlTest classes happen to + // declare a method with the same simple name they still get independent locks. + final String key = KEY_PREFIX + testClass.getName() + "#" + testMethod.getName(); + return Set.of(new Lock(key, ResourceAccessMode.READ_WRITE)); + } +} diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTest.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTest.java index fe186d0c90..732d3f5955 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTest.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTest.java @@ -22,6 +22,9 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.parallel.ResourceLock; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -41,12 +44,26 @@ * annotation {@link ExcludeYamlTestConfig}. Ideally, these are short lived, primarily to support adding a config, * and then fixing some tests that may fail with that config. *

+ *

+ * Parallelism model: different test methods within a {@code @YamlTest} class run concurrently + * with each other, but the multiple {@link org.junit.jupiter.api.TestTemplate} invocations of + * any single method run serially. This is enforced by + * {@link Execution @Execution(CONCURRENT)} for the method-level scheduling and by + * {@link ResourceLock @ResourceLock(providers = PerMethodResourceLocksProvider.class)} which + * hands each test method its own per-method lock so its invocations serialise on that lock + * while different methods use different keys and stay independent. Individual yamsql files + * typically hard-code cluster-wide state (databases, schema templates, planner metrics, + * etc.), and two invocations of the same method against the same cluster can't safely run + * concurrently. + *

*/ @Retention(RetentionPolicy.RUNTIME) @ExtendWith(YamlTestExtension.class) // Right now these are the only tests that have the capability to run in mixed mode, but if we create other tests, this // should be moved to a shared static location @Tag("MixedMode") +@Execution(ExecutionMode.CONCURRENT) +@ResourceLock(providers = PerMethodResourceLocksProvider.class) public @interface YamlTest { /** * Simple interface to run a {@code .yamsql} file, based on the config. From 7306381ed6c64d7a2a01e8a7d46206eba61605e6 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 7 Jul 2026 19:44:28 -0400 Subject: [PATCH 61/74] Change deleteStore to check StoreHeader before bumping MetaDataVersionStamp In particular the catalog uses the MetaDataVersion-based store cache, and when you delete a schema (as part of a test cleanup), the old deleteStore would invalidate the metadataversion cache, which would cause any catalog operations (which opened the store) to conflict. By being more restrictive, deleting stores that aren't caching the header means that we won't force a refresh on stores that are using the store header cache. --- .../provider/foundationdb/FDBRecordStore.java | 38 ++++-- .../FDBRecordStoreStateCacheTest.java | 126 ++++++++++++++++++ 2 files changed, 155 insertions(+), 9 deletions(-) diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java index 2a135af001..8ea85bef91 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java @@ -1799,12 +1799,16 @@ public static void deleteStore(FDBRecordContext context, KeySpacePath path) { * the store existed. * *

- * This method does not read the underlying record store, so it does not validate - * that a record store exists in the given subspace. As it might be the case that - * this record store has a cacheable store state (see {@link #setStateCacheability(boolean)}), - * this method resets the database's - * {@linkplain FDBRecordContext#getMetaDataVersionStamp(IsolationLevel) meta-data version-stamp}. - * As a result, calling this method may cause other clients to invalidate their caches needlessly. + * This method reads only the record store's header key (see {@link #STORE_INFO_KEY}) in + * order to decide whether it needs to invalidate cached state. If a header is present and + * marks the store as {@linkplain #setStateCacheability(boolean) cacheable}, the database's + * {@linkplain FDBRecordContext#getMetaDataVersionStamp(IsolationLevel) meta-data + * version-stamp} is reset so that other clients drop their cached copies; if the header is + * missing (store doesn't exist) or marks the store as non-cacheable, the version-stamp is + * not touched. This means callers who only ever operate on non-cacheable stores do not + * contend on the single meta-data version-stamp key. The header read uses snapshot + * isolation and does not add a read conflict, so it does not narrow the write-conflict + * behavior of the clear that follows. *

* * @param context the transactional context in which to delete the record store @@ -1812,9 +1816,25 @@ public static void deleteStore(FDBRecordContext context, KeySpacePath path) { */ @SuppressWarnings("PMD.CloseResource") public static void deleteStore(FDBRecordContext context, Subspace subspace) { - // In theory, we only need to set the meta-data version stamp if the record store's - // meta-data is cacheable, but we can't know that from here. - context.setMetaDataVersionStamp(); + // Read the store header at snapshot isolation so we don't add a needless read conflict: + // the clear below already creates a write conflict on the whole range, which is what + // actually serialises us against any concurrent writer of this store's header. + final byte[] headerKey = subspace.pack(STORE_INFO_KEY); + final byte[] headerBytes = context.asyncToSync(FDBStoreTimer.Waits.WAIT_LOAD_RECORD_STORE_STATE, + context.readTransaction(true).get(headerKey)); + if (headerBytes != null) { + boolean shouldBump = true; + try { + shouldBump = RecordMetaDataProto.DataStoreInfo.parseFrom(headerBytes).getCacheable(); + } catch (InvalidProtocolBufferException e) { + // If we can't parse the header, fall back to the pre-change conservative + // behavior and bump. This preserves the "delete a store I know nothing about" + // contract this method advertises. + } + if (shouldBump) { + context.setMetaDataVersionStamp(); + } + } context.setDirtyStoreState(true); context.clear(subspace.range()); } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java index 2e866a2a47..76b1e13599 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java @@ -45,6 +45,7 @@ import com.apple.foundationdb.record.provider.foundationdb.RecordStoreStaleMetaDataVersionException; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; import com.apple.foundationdb.record.test.FakeClusterFileUtil; +import com.apple.foundationdb.subspace.Subspace; import com.apple.foundationdb.tuple.ByteArrayUtil; import com.apple.foundationdb.tuple.ByteArrayUtil2; import com.apple.foundationdb.tuple.Tuple; @@ -766,6 +767,131 @@ public void storeDeletionAcrossContexts(@Nonnull StateCacheTestContext testConte } } + /** + * Deleting a non-cacheable store must NOT bump the meta-data version stamp: no other + * client can possibly hold a cached copy of a non-cacheable header, so the version stamp + * (a JVM-wide bottleneck on the {@code \xff/metadataVersion} key) shouldn't be touched. + * This is the low-level property that lets parallel tests share the SYS catalog without + * conflicting on catalog teardown. + */ + @Test + void deleteNonCacheableStoreDoesNotBumpMetaDataVersionStamp() throws Exception { + // Bootstrap: ensure the meta-data version stamp key has a value before the test runs, + // so we can distinguish "unchanged" from "was never set". The store below is opened + // with cacheability disabled (which is the default from setStateCacheability(false)). + try (FDBRecordContext context = fdb.openContext()) { + if (context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT) == null) { + context.setMetaDataVersionStamp(); + } + commit(context); + } + + Subspace subspace; + try (FDBRecordContext context = openContext()) { + openSimpleRecordStore(context); + assertFalse(recordStore.getRecordStoreState().getStoreHeader().getCacheable(), + "test presumes the store is non-cacheable by default"); + subspace = recordStore.getSubspace(); + commit(context); + } + + // Snapshot the version stamp before deletion — reading via SNAPSHOT so this txn doesn't + // conflict with the delete-txn below and skew the result. + final byte[] beforeStamp; + try (FDBRecordContext context = fdb.openContext()) { + beforeStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + } + assertNotNull(beforeStamp, "bootstrap should have populated the meta-data version stamp"); + + try (FDBRecordContext context = openContext()) { + FDBRecordStore.deleteStore(context, subspace); + commit(context); + } + + try (FDBRecordContext context = fdb.openContext()) { + final byte[] afterStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + assertArrayEquals(beforeStamp, afterStamp, + "deleting a non-cacheable store should not have bumped the meta-data version stamp"); + } + } + + /** + * Complement of {@link #deleteNonCacheableStoreDoesNotBumpMetaDataVersionStamp()}: deleting + * a cacheable store MUST bump the stamp — otherwise sibling clients could keep serving + * reads out of a stale cached header long after the store is gone. + */ + @Test + void deleteCacheableStoreBumpsMetaDataVersionStamp() throws Exception { + try (FDBRecordContext context = fdb.openContext()) { + if (context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT) == null) { + context.setMetaDataVersionStamp(); + } + commit(context); + } + + Subspace subspace; + try (FDBRecordContext context = openContext()) { + openSimpleRecordStore(context); + assertTrue(recordStore.setStateCacheability(true), "flipping to cacheable should have changed something"); + subspace = recordStore.getSubspace(); + commit(context); + } + // Commit above already bumped the stamp (transition to cacheable). Snapshot after that. + final byte[] beforeStamp; + try (FDBRecordContext context = fdb.openContext()) { + beforeStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + } + assertNotNull(beforeStamp); + + try (FDBRecordContext context = openContext()) { + FDBRecordStore.deleteStore(context, subspace); + commit(context); + } + + try (FDBRecordContext context = fdb.openContext()) { + final byte[] afterStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + assertNotNull(afterStamp); + assertFalse(java.util.Arrays.equals(beforeStamp, afterStamp), + "deleting a cacheable store should have bumped the meta-data version stamp"); + } + } + + /** + * Deleting an empty subspace (no store header present) must not bump the stamp either — + * there's no cached header to invalidate. + */ + @Test + void deleteMissingStoreDoesNotBumpMetaDataVersionStamp() throws Exception { + try (FDBRecordContext context = fdb.openContext()) { + if (context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT) == null) { + context.setMetaDataVersionStamp(); + } + commit(context); + } + + // Use the test's per-instance path, but never open a store there. + final Subspace subspace; + try (FDBRecordContext context = openContext()) { + subspace = path.toSubspace(context); + } + final byte[] beforeStamp; + try (FDBRecordContext context = fdb.openContext()) { + beforeStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + } + assertNotNull(beforeStamp); + + try (FDBRecordContext context = openContext()) { + FDBRecordStore.deleteStore(context, subspace); + commit(context); + } + + try (FDBRecordContext context = fdb.openContext()) { + final byte[] afterStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + assertArrayEquals(beforeStamp, afterStamp, + "deleting an empty subspace (no header) should not have bumped the meta-data version stamp"); + } + } + /** * Verify that updating a header user field will be updated if the store state is cached. */ From ac6023737d2dc5dae318f3831232a4d410642988 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 7 Jul 2026 21:52:24 -0400 Subject: [PATCH 62/74] Use random template name in DdlRecordLayerSchemaTest/TemplateTest --- .../ddl/DdlRecordLayerSchemaTemplateTest.java | 48 ++++++++++++------- .../ddl/DdlRecordLayerSchemaTest.java | 7 ++- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java index bca88e1379..4618eed9d8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java @@ -60,8 +60,19 @@ public class DdlRecordLayerSchemaTemplateTest { @RegisterExtension public static final EmbeddedRelationalExtension relational = new EmbeddedRelationalExtension(); + // Suffix added to every schema-template name this class creates. The catalog is + // physically shared across every test run against this FDB cluster, and the tests + // in this class historically never dropped the templates they created — so a second + // run against the same cluster would fail with "Schema template already exists". + // Randomising the suffix per JVM makes each test's template name unique, so a + // previous run's leftovers don't collide with this one, and two concurrent JVMs + // running this class don't collide either. The suffix is uppercased because SQL + // uppercases unquoted identifiers, and describeSchemaTemplate checks the returned + // template name for exact equality. + private static final String NAME_SUFFIX = "_" + Long.toHexString(ThreadLocalRandom.current().nextLong()).toUpperCase(); + public static Stream columnTypePermutations() { - return DdlPermutationGenerator.generateTables("SCHEMA_TEMPLATE_TEST", 2) + return DdlPermutationGenerator.generateTables("SCHEMA_TEMPLATE_TEST" + NAME_SUFFIX + "_", 2) .map(Arguments::of); } @@ -86,7 +97,8 @@ private void run(ThrowingConsumer operation) throws @Test void canDropSchemaTemplates() throws Exception { - String createColumnStatement = "CREATE SCHEMA TEMPLATE drop_template " + + final String templateName = "drop_template" + NAME_SUFFIX; + String createColumnStatement = "CREATE SCHEMA TEMPLATE " + templateName + " " + "CREATE TYPE AS STRUCT FOO_TYPE (a bigint)" + " CREATE TABLE FOO_TBL (b double, PRIMARY KEY(b))"; @@ -94,16 +106,16 @@ void canDropSchemaTemplates() throws Exception { statement.executeUpdate(createColumnStatement); //verify that it's there - try (final var rs = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE drop_template")) { + try (final var rs = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE " + templateName)) { Assertions.assertTrue(rs.next(), "Didn't find created template!"); - Assertions.assertEquals("DROP_TEMPLATE", rs.getString(1)); + Assertions.assertEquals(templateName.toUpperCase(), rs.getString(1)); Assertions.assertFalse(rs.next(), "too many schema templates!"); } //now drop it - statement.executeUpdate("DROP SCHEMA TEMPLATE drop_template"); + statement.executeUpdate("DROP SCHEMA TEMPLATE " + templateName); //verify it's not there, and that means that there is an error trying to describe it - SQLException ve = Assertions.assertThrows(SQLException.class, () -> statement.executeQuery("DESCRIBE SCHEMA TEMPLATE drop_template")); + SQLException ve = Assertions.assertThrows(SQLException.class, () -> statement.executeQuery("DESCRIBE SCHEMA TEMPLATE " + templateName)); Assertions.assertEquals(ErrorCode.UNKNOWN_SCHEMA_TEMPLATE.getErrorCode(), ve.getSQLState(), "Incorrect error code"); }); } @@ -169,7 +181,7 @@ void listSchemaTemplatesWorks(DdlPermutationGenerator.NamedPermutation table) th @Test void createSchemaTemplateWithNoTable() throws SQLException, RelationalException { - String createColumnStatement = "CREATE SCHEMA TEMPLATE no_table " + + String createColumnStatement = "CREATE SCHEMA TEMPLATE no_table" + NAME_SUFFIX + " " + "CREATE TYPE AS STRUCT not_a_table(a bigint)"; run(statement -> RelationalAssertions.assertThrowsSqlException(() -> statement.executeUpdate(createColumnStatement)) @@ -178,7 +190,7 @@ void createSchemaTemplateWithNoTable() throws SQLException, RelationalException @Test void cyclicDependencyTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE cyclic " + + String template = "CREATE SCHEMA TEMPLATE cyclic" + NAME_SUFFIX + " " + "CREATE TYPE AS STRUCT s1 (a s2) " + "CREATE TYPE AS STRUCT s2 (a s1) " + "CREATE TABLE t1 (id bigint, a s1, b s2, PRIMARY KEY(id))"; @@ -190,7 +202,8 @@ void cyclicDependencyTest() throws RelationalException, SQLException { @Test void manyStructsThatDoNotDependOnEachOther() throws RelationalException, SQLException { - StringBuilder template = new StringBuilder("CREATE SCHEMA TEMPLATE many_structs "); + final String templateName = "many_structs" + NAME_SUFFIX; + StringBuilder template = new StringBuilder("CREATE SCHEMA TEMPLATE ").append(templateName).append(' '); for (int i = 0; i < 100; i++) { template.append("CREATE TYPE AS STRUCT s").append(i).append("(a bigint) "); } @@ -202,7 +215,7 @@ void manyStructsThatDoNotDependOnEachOther() throws RelationalException, SQLExce run(statement -> { statement.executeUpdate(template.toString()); - try (RelationalResultSet resultSet = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE many_structs")) { + try (RelationalResultSet resultSet = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE " + templateName)) { ResultSetAssert.assertThat(resultSet).hasNextRow(); final var type = resultSet.getArray("TABLES").getResultSet(); Assert.that(type.next()); @@ -213,7 +226,7 @@ void manyStructsThatDoNotDependOnEachOther() throws RelationalException, SQLExce @Test void missingTypeTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE missing_type " + + String template = "CREATE SCHEMA TEMPLATE missing_type" + NAME_SUFFIX + " " + "CREATE TABLE t1 (id bigint, val unknown_type, PRIMARY KEY(id))"; run(statement -> RelationalAssertions.assertThrowsSqlException(() -> statement.executeUpdate(template)) @@ -223,7 +236,8 @@ void missingTypeTest() throws RelationalException, SQLException { @Test void basicEnumTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE basic_enum_template " + + final String templateName = "basic_enum_template" + NAME_SUFFIX; + String template = "CREATE SCHEMA TEMPLATE " + templateName + " " + "CREATE TYPE AS ENUM basic_enum ('FOO', 'BAR', 'BAZ') " + "CREATE TABLE t1 (id bigint, val basic_enum, PRIMARY KEY(id))"; @@ -231,7 +245,7 @@ void basicEnumTest() throws RelationalException, SQLException { statement.executeUpdate(template); //verify that it's there - try (ResultSet rs = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE basic_enum_template")) { + try (ResultSet rs = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE " + templateName)) { Assertions.assertTrue(rs.next(), "Didn't find created template!"); } }); @@ -239,7 +253,7 @@ void basicEnumTest() throws RelationalException, SQLException { @Test void twoTypesSameNameTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE same_name " + + String template = "CREATE SCHEMA TEMPLATE same_name" + NAME_SUFFIX + " " + "CREATE TABLE t1 (id bigint, foo string, PRIMARY KEY(id)) " + "CREATE TABLE t1 (id bigint, bar string, PRIMARY KEY(id))"; @@ -250,7 +264,7 @@ void twoTypesSameNameTest() throws RelationalException, SQLException { @Test void twoTypesSameNameMixedCase() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE same_name_mixed_case " + + String template = "CREATE SCHEMA TEMPLATE same_name_mixed_case" + NAME_SUFFIX + " " + "CREATE TABLE aTypeName (id bigint, foo string, PRIMARY KEY(id)) " + "CREATE TABLE AtYPEnAME (id bigint, bar string, PRIMARY KEY(id))"; @@ -261,7 +275,7 @@ void twoTypesSameNameMixedCase() throws RelationalException, SQLException { @Test void typeAndEnumSameNameTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE same_name " + + String template = "CREATE SCHEMA TEMPLATE type_and_enum_same_name" + NAME_SUFFIX + " " + "CREATE TABLE foo (id bigint, foo string, PRIMARY KEY(id)) " + "CREATE TYPE AS ENUM foo ('A', 'B', 'C')"; @@ -272,7 +286,7 @@ void typeAndEnumSameNameTest() throws RelationalException, SQLException { @Test void notNullNonArrayTypeNotAllowedTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE not_null_non_array_column_type " + + String template = "CREATE SCHEMA TEMPLATE not_null_non_array_column_type" + NAME_SUFFIX + " " + "CREATE TABLE foo (id bigint, foo string not null, PRIMARY KEY(id))"; run(statement -> RelationalAssertions.assertThrowsSqlException(() -> statement.executeUpdate(template)) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java index ab02bfcb57..c6d701fbe3 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java @@ -98,12 +98,15 @@ void canCreateSchemaTemplateWhenConnectedToNonCatalogSchema() throws Exception { } }); - //now create a new schema in the same db but using a different connection + //now create a new schema in the same db but using a different connection. + // Use a random-per-invocation template name so this test doesn't clash with a prior + // run's leftover FOO template in the shared catalog — the test never drops it. + final String templateName = "FOO_" + Long.toHexString(java.util.concurrent.ThreadLocalRandom.current().nextLong()).toUpperCase(); try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:" + db.getDbUri()))) { conn.setSchema("TEST_SCHEMA"); try (Statement statement = conn.createStatement()) { //create a schema - final String createStatement = "CREATE SCHEMA TEMPLATE FOO CREATE TABLE T(A string, B string, PRIMARY KEY (A))"; + final String createStatement = "CREATE SCHEMA TEMPLATE " + templateName + " CREATE TABLE T(A string, B string, PRIMARY KEY (A))"; statement.executeUpdate(createStatement); } } From b6309fce19a873ce8721e9cc1df90f5b0417f716 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 8 Jul 2026 09:31:15 -0400 Subject: [PATCH 63/74] Initialize the catolog once at the start of yaml tests This should prevent conflicts with the external servers, and initial catalog operations. --- .../yamltests/YamlTestCatalogInitializer.java | 117 ++++++++++++++++++ .../yamltests/YamlTestExtension.java | 8 ++ 2 files changed, 125 insertions(+) create mode 100644 yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java new file mode 100644 index 0000000000..e1d990d72d --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java @@ -0,0 +1,117 @@ +/* + * YamlTestCatalogInitializer.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.relational.yamltests; + +import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; +import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; +import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpace; +import com.apple.foundationdb.relational.api.Transaction; +import com.apple.foundationdb.relational.api.catalog.StoreCatalog; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.recordlayer.RecordContextTransaction; +import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; +import com.apple.foundationdb.relational.recordlayer.catalog.StoreCatalogProvider; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * Proactive one-shot initialiser for the SYS catalog on every cluster the yaml-tests will + * touch. + *

+ * The problem this addresses: {@link com.apple.foundationdb.relational.server.FRL#FRL} and + * the internals under {@link com.apple.foundationdb.relational.api.EmbeddedRelationalDriver} + * both create the record store at {@code /__SYS/CATALOG} on their first construction. When + * yaml-tests spin up (a) an in-process embedded driver, (b) an in-process JDBC server, and + * (c) one or more external-server subprocesses — all racing to construct that record store + * against the same FDB cluster — the concurrent init transactions commit-conflict on the + * catalog metadata (SQLSTATE 40001) or, worse, one process ends up throwing while another + * is mid-init. + *

+ * {@link YamlTestExtension#beforeAll} calls {@link #initializeCatalog(List)} before + * any of those configs run. This method opens a plain FDB transaction, invokes the same + * {@link StoreCatalogProvider#getCatalog} primitive the FRL uses, and commits — retrying on + * SQLSTATE 40001 up to a small attempt limit. After it returns, every subsequent FRL + * construction against the same cluster opens an already-initialised store and does not + * race on the init commit. + *

+ * Lives inside {@code yaml-tests} rather than pulling + * {@code fdb-relational-core}'s testFixtures onto the yaml-tests classpath — the yaml-tests + * package already depends on the record-layer primitives this needs directly, so a private + * helper here is cheaper than adding another cross-module dependency edge. + */ +final class YamlTestCatalogInitializer { + + private static final int MAX_ATTEMPTS = 10; + private static final long RETRY_BASE_MILLIS = 20L; + + private YamlTestCatalogInitializer() { + } + + /** + * Initialise the SYS catalog on every given cluster, in order. Idempotent under retry — + * if the store already exists, {@link StoreCatalogProvider#getCatalog} opens it. If the + * commit races with a concurrent initialiser on the same cluster, retries on SQLSTATE + * 40001 with a short back-off. + * + * @param clusterFiles cluster files to initialise; a {@code null} entry means "the + * default cluster" per {@link FDBDatabaseFactory#getDatabase(String)} + * @throws RelationalException if any cluster's init ultimately fails after retries + */ + static void initializeCatalog(@Nonnull final List clusterFiles) throws RelationalException { + // Register the FRL keyspace domain once up front. It's idempotent, but doing it here + // ensures the keyspace tree is ready before we resolve any paths against it below. + final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); + keyspaceProvider.registerDomainIfNotExists("FRL"); + final KeySpace keySpace = keyspaceProvider.getKeySpace(); + for (final String clusterFile : clusterFiles) { + initializeOne(clusterFile, keySpace); + } + } + + private static void initializeOne(final String clusterFile, @Nonnull final KeySpace keySpace) throws RelationalException { + final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(clusterFile); + RelationalException last = null; + for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + try (Transaction txn = new RecordContextTransaction(database.openContext())) { + final StoreCatalog ignored = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + return; + } catch (RelationalException e) { + if (e.getErrorCode() != ErrorCode.SERIALIZATION_FAILURE) { + throw e; + } + last = e; + // Short, linear back-off; the commit-conflict window is brief and clearing it + // just requires the other party's commit to land. + try { + Thread.sleep(RETRY_BASE_MILLIS * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + e.addSuppressed(ie); + throw e; + } + } + } + throw last; + } +} diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java index 9b0f5c18bc..63d6565475 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java @@ -108,6 +108,14 @@ public YamlTestExtension(@Nonnull final List clusterFiles, final boolean @Override public void beforeAll(final ExtensionContext context) throws Exception { + // Proactively initialise the SYS catalog on every cluster this extension will use, + // BEFORE any config's beforeAll spins up an FRL / EmbeddedRelationalDriver / external + // server. Running it here means the catalog is either already-created (this call) or + // being created by us (retry-safe) — sibling FRLs constructed later in the various + // configs (embedded / JDBC in-process / external subprocesses) all open an existing + // store and skip the racing init-commit path that we previously worked around with + // per-operation locks. See YamlTestCatalogInitializer for the details. + YamlTestCatalogInitializer.initializeCatalog(clusterFiles); maintainConfigs = List.of( new CorrectExplains(new EmbeddedConfig(clusterFiles)), new CorrectMetrics(new EmbeddedConfig(clusterFiles)), From 805f532ac9fb97a9c03677abb75a1fe258b7fda7 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 8 Jul 2026 12:38:35 -0400 Subject: [PATCH 64/74] Make catalog not saveSchema if the schema already exists --- .../recordlayer/catalog/RecordLayerStoreCatalog.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java index 35e93a0574..64a193b706 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java @@ -180,8 +180,14 @@ StoreCatalog initialize(@Nonnull final Transaction createTxn, @Nonnull final Sch schemaTemplateCatalog.createTemplate(createTxn, this.catalogSchemaTemplate); } this.schemaTemplateCatalog = schemaTemplateCatalog; - // Persist our hard-coded catalog schema. - saveSchema(createTxn, this.catalogSchema, true); + // Persist our hard-coded catalog schema — but only if it isn't already there. Every + // FRL construction and every proactive test-time initialiser calls this method; + // writing the same schema row on every call turned every parallel init into a + // write-write commit conflict on the catalog table. Gate on doesSchemaExist so the + // second and subsequent inits become read-only and can commit concurrently. + if (!doesSchemaExist(createTxn, URI.create(this.catalogSchema.getDatabaseName()), this.catalogSchema.getName())) { + saveSchema(createTxn, this.catalogSchema, true); + } } catch (RecordCoreStorageException ex) { throw ExceptionUtil.toRelationalException(ex); } From 95583247c95736f755a833be83d0fead56b0e95d Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 8 Jul 2026 12:39:46 -0400 Subject: [PATCH 65/74] Synchronize RelationalKeyspaceProvider.registerDomainIfExists This mutates the singleton keyspace, so should be synchronized --- .../recordlayer/RelationalKeyspaceProvider.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RelationalKeyspaceProvider.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RelationalKeyspaceProvider.java index ef82783624..5fa6769c6b 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RelationalKeyspaceProvider.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RelationalKeyspaceProvider.java @@ -188,7 +188,14 @@ private static KeySpaceDirectory getSystemDirectory() { .addSubdirectory(new KeySpaceDirectory(INTERNING_LAYER, KeySpaceDirectory.KeyType.STRING, INTERNING_LAYER_VALUE)); } - public void registerDomainIfNotExists(@Nonnull String domainName) { + public synchronized void registerDomainIfNotExists(@Nonnull String domainName) { + // Synchronized because this mutates the singleton KeySpace's tree via + // addSubdirectory. Without the lock, two concurrent callers can both observe "not + // present" and both add the same domain, leaving the tree with two subdirectories + // matching the same name — every subsequent path resolution then throws + // "<...> is ambigous" from KeySpaceUtils#matchPathToSubdirectories. That surfaces + // once multiple test-class @BeforeAll hooks (or parallel FRL constructions) hit + // this on the same JVM. final var keySpaceRoot = getKeySpace().getRoot(); final var exists = keySpaceRoot.getSubdirectories().stream() .map(KeySpaceDirectory::getName) From c0653b77ce9f49df5d3c05b5bebe43bf6f1a3a5e Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 8 Jul 2026 12:40:25 -0400 Subject: [PATCH 66/74] Remove now unnecessary synchronized/retry for catalog operations Changes to the production code mean that the test code no longer needs to synchronize/retry. --- .../relational/utils/CatalogOperations.java | 206 ++++-------------- .../foundationdb/relational/server/FRL.java | 76 ++----- .../yamltests/block/SetupBlock.java | 59 +---- 3 files changed, 73 insertions(+), 268 deletions(-) diff --git a/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java index aca99e6579..c8c3fdaf00 100644 --- a/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java +++ b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -20,11 +20,9 @@ package com.apple.foundationdb.relational.utils; -import com.apple.foundationdb.record.provider.foundationdb.RecordStoreDoesNotExistException; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; import com.apple.foundationdb.relational.api.RelationalDriver; -import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import javax.annotation.Nonnull; @@ -34,41 +32,26 @@ import java.sql.Statement; /** - * Test-only synchronization helpers for catalog-mutating DDL (CREATE/DROP DATABASE, - * CREATE/DROP SCHEMA TEMPLATE, CREATE/DROP SCHEMA). + * Test-only helpers for running catalog-mutating DDL (CREATE/DROP DATABASE, CREATE/DROP SCHEMA + * TEMPLATE, CREATE/DROP SCHEMA) against {@code /__SYS/CATALOG}. *

- * Under parallel JUnit class execution, every test class's {@code @BeforeEach}/{@code @AfterEach} - * hooks race on the cluster-wide catalog metadata stored in {@code /__SYS/CATALOG}. The FDB - * commit layer surfaces those races as SQLSTATE 40001 transaction conflicts. The - * {@link #runLockedWithRetry(ThrowingRunnable)} wrapper serializes the operations within the JVM - * (mirroring the {@code FRL.catalogLock()} pattern used by yaml-tests' {@code SetupBlock}) and - * retries any residual conflicts caused by metadata writes the lock doesn't cover - * (cluster-global version-stamp updates, etc.). - *

- * The wrapper also retries on {@link RecordStoreDoesNotExistException}: under concurrent - * extension setup, a {@code SchemaRule}'s {@code @BeforeEach} can race the very first call to - * {@code /__SYS} and observe the record store before another thread's bootstrap has fully - * propagated. The retry covers that window. - *

- * Lives in {@code testFixtures} so downstream modules (fdb-relational-jdbc, etc.) can serialize - * their catalog DDL against the same JVM-wide monitor. - *

- * Wrap CREATE/DROP DDL in {@code @BeforeEach}/{@code @AfterEach} hooks with this helper. Drop - * statements should use {@code DROP ... IF EXISTS} so a retry after a partial success doesn't - * trip an "unknown ..." error. + * Historically this class also owned a JVM-wide monitor + retry loop that serialised catalog + * DDL across tests to work around two contention sources: + *

    + *
  1. Concurrent FRL construction racing on the catalog-init commit (now removed — the + * proactive initializer in {@code YamlTestCatalogInitializer} covers that once per test + * class before any FRL is constructed).
  2. + *
  3. Every {@code FDBRecordStore.deleteStore} bumping the shared meta-data-version stamp, + * which then conflicted with every concurrent catalog read (now conditional on the + * deleted store being cacheable, which the SYS catalog is but transient user stores + * are not — so most concurrent DDL no longer contends).
  4. + *
+ * With both root causes addressed, the runners here execute their action directly. The + * method names are kept ({@link #runLockedWithRetry}, {@link #runLockedWithRelationalRetry}) + * so existing test call sites don't need mechanical updates. */ public final class CatalogOperations { - /** - * JVM-wide monitor guarding catalog-mutating DDL issued from test rules. FRL construction - * in fdb-relational-server has its own separate {@code FRL.catalogLock()} and is also - * retry-guarded — a separate lock is fine because FRL bootstrap is a one-time-per-JVM step - * that runs before any tests contend for this monitor. - */ - private static final Object CATALOG_LOCK = new Object(); - - private static final int MAX_ATTEMPTS = 5; - /** * URI of the system catalog database every {@link #runDdl} invocation connects against * when going through a {@link RelationalDriver}. The {@code jdbc:embed:} scheme is only @@ -80,37 +63,27 @@ private CatalogOperations() { } /** - * Runs one or more catalog-mutating DDL statements under the JVM-wide catalog lock with - * retry on transient races. Each call opens a fresh connection to {@code /__SYS} via the - * given driver, sets the schema to {@code CATALOG}, applies {@code connectionOptions}, and - * runs every statement in order. Drop statements should use {@code DROP ... IF EXISTS} so - * a retry after a partial success doesn't trip an "unknown ..." error. - *

- * This is the preferred entry point for tests that already hold a {@link RelationalDriver} - * reference (e.g. via {@code EmbeddedRelationalExtension.getDriver()}) — prefer it over - * calling {@link #runLockedWithRetry(ThrowingRunnable)} directly, which only exists for - * callers that need to compose multiple steps on an existing connection. + * Runs one or more catalog-mutating DDL statements. Each call opens a fresh connection to + * {@code /__SYS} via the given driver, sets the schema to {@code CATALOG}, applies + * {@code connectionOptions}, and runs every statement in order. * * @param driver the driver to use for the {@code /__SYS} connection * @param connectionOptions options to apply to the connection before running the statements * @param statements DDL statements to run in order - * @throws SQLException if the operation ultimately fails (after retries, or with a - * non-retriable error) + * @throws SQLException if the operation fails */ public static void runDdl(@Nonnull final RelationalDriver driver, @Nonnull final Options connectionOptions, @Nonnull final String... statements) throws SQLException { - runLockedWithRetry(() -> { - try (Connection connection = driver.connect(SYS_CATALOG_URI)) { - connection.setSchema("CATALOG"); - applyConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - for (final String ddl : statements) { - statement.executeUpdate(ddl); - } + try (Connection connection = driver.connect(SYS_CATALOG_URI)) { + connection.setSchema("CATALOG"); + applyConnectionOptions(connection, connectionOptions); + try (Statement statement = connection.createStatement()) { + for (final String ddl : statements) { + statement.executeUpdate(ddl); } } - }); + } } /** @@ -119,7 +92,7 @@ public static void runDdl(@Nonnull final RelationalDriver driver, * * @param driver the driver to use for the {@code /__SYS} connection * @param statements DDL statements to run in order - * @throws SQLException if the operation ultimately fails + * @throws SQLException if the operation fails */ public static void runDdl(@Nonnull final RelationalDriver driver, @Nonnull final String... statements) throws SQLException { @@ -127,27 +100,20 @@ public static void runDdl(@Nonnull final RelationalDriver driver, } /** - * Runs {@code action} on an open connection to {@code /__SYS/CATALOG} under the JVM-wide - * catalog lock with retry. Use this for tests that interleave DDL and queries against the - * catalog and need to share a single connection across both. - *

- * Because the whole body runs under the lock + retry, the {@code action} should be - * idempotent on retry — typically achieved by using {@code DROP ... IF EXISTS} for cleanup - * and choosing test data names that don't collide across reruns. If the {@code action} - * throws a non-retriable {@link SQLException}, it propagates after no retry. + * Runs {@code action} on an open connection to {@code /__SYS/CATALOG}. Use this for tests + * that interleave DDL and queries against the catalog and need to share a single connection + * across both. * * @param driver the driver to use for the {@code /__SYS} connection * @param action the body to run against the open catalog connection - * @throws SQLException if the action ultimately fails + * @throws SQLException if the action fails */ public static void runOnCatalog(@Nonnull final RelationalDriver driver, @Nonnull final ThrowingConnectionConsumer action) throws SQLException { - runLockedWithRetry(() -> { - try (Connection connection = driver.connect(SYS_CATALOG_URI)) { - connection.setSchema("CATALOG"); - action.accept(connection); - } - }); + try (Connection connection = driver.connect(SYS_CATALOG_URI)) { + connection.setSchema("CATALOG"); + action.accept(connection); + } } /** @@ -214,105 +180,29 @@ public interface RelationalThrowingRunnable { } /** - * Runs {@code action} under the JVM-wide catalog lock with a small retry loop on SQLSTATE - * 40001 transaction conflicts. Use for any CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, - * or CREATE/DROP SCHEMA executed against {@code /__SYS/CATALOG}. + * Runs {@code action}. Historically this method wrapped the action in a JVM-wide + * monitor + retry loop; that infrastructure has been removed now that the underlying + * contention (catalog-init commits, meta-data-version-stamp bumps on non-cacheable + * {@code deleteStore}) has been fixed upstream. The method is preserved so existing test + * call sites keep compiling; new call sites can skip the wrapper and call their action + * directly. * * @param action the catalog DDL to run - * @throws SQLException if the action ultimately fails (after retries, or with a - * non-retriable error) + * @throws SQLException if the action fails */ public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) throws SQLException { - for (int attempt = 1; ; attempt++) { - final SQLException failure; - synchronized (CATALOG_LOCK) { - try { - action.run(); - return; - } catch (SQLException e) { - // TODO these retries were added when other code didn't pass through here, can it be removed now - if (attempt >= MAX_ATTEMPTS || !isRetriable(e)) { - throw e; - } - failure = e; - } - } - // Backoff with the lock RELEASED so other catalog ops can make progress between - // retries. Sleeping under the monitor triggers SpotBugs SWL_SLEEP_WITH_LOCK_HELD - // and unnecessarily serialises unrelated work. - sleepBeforeRetry(attempt, failure); - } + action.run(); } /** * {@link RelationalException}-throwing variant of {@link #runLockedWithRetry(ThrowingRunnable)}. - * Same lock and retry policy; detects retriable failures via either {@link SQLException} - * SQLSTATE 40001 or {@link RelationalException} carrying {@link ErrorCode#SERIALIZATION_FAILURE}, - * plus {@link RecordStoreDoesNotExistException}. - * Named distinctly because Java can't disambiguate lambdas between the two - * functional-interface overloads (their abstract methods differ only in their throws clause). + * As with that method, no JVM-wide lock or retry is applied any more; the wrapper is kept + * only to avoid mass-editing every existing call site. * * @param action the catalog operation to run - * @throws RelationalException if the action ultimately fails (after retries, or with a - * non-retriable error) + * @throws RelationalException if the action fails */ public static void runLockedWithRelationalRetry(@Nonnull final RelationalThrowingRunnable action) throws RelationalException { - for (int attempt = 1; ; attempt++) { - final RelationalException failure; - synchronized (CATALOG_LOCK) { - try { - action.run(); - return; - } catch (RelationalException e) { - if (attempt >= MAX_ATTEMPTS || !isRetriable(e)) { - throw e; - } - failure = e; - } - } - sleepBeforeRetry(attempt, failure); - } - } - - private static void sleepBeforeRetry(final int attempt, @Nonnull final E pending) throws E { - try { - Thread.sleep(10L * attempt); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - pending.addSuppressed(ie); - throw pending; - } - } - - /** - * Decides whether a thrown exception is worth retrying. We retry on: - *

    - *
  • SQLSTATE 40001 / {@link ErrorCode#SERIALIZATION_FAILURE} — FDB transaction conflicts - * (commits that lost the optimistic-concurrency race against a sibling test). - *
  • {@link RecordStoreDoesNotExistException} — a transient visibility race during the - * first concurrent extension setup, where one thread's bootstrap commit hasn't fully - * propagated by the time another thread connects to {@code /__SYS}. The committed - * state catches up within a few milliseconds, so the small retry loop covers it. - *
- * Walks the exception causal chain so wrapped variants (e.g. {@code ContextualSQLException} - * wrapping a {@code RelationalException} wrapping a {@code RecordCoreException}) are caught. - */ - private static boolean isRetriable(@Nonnull final Throwable t) { - Throwable cursor = t; - while (cursor != null) { - if (cursor instanceof SQLException - && ErrorCode.SERIALIZATION_FAILURE.getErrorCode().equals(((SQLException) cursor).getSQLState())) { - return true; - } - if (cursor instanceof RelationalException - && ((RelationalException) cursor).getErrorCode() == ErrorCode.SERIALIZATION_FAILURE) { - return true; - } - if (cursor instanceof RecordStoreDoesNotExistException) { - return true; - } - cursor = cursor.getCause(); - } - return false; + action.run(); } } diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index 6df75f9f87..570047af04 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -79,37 +79,11 @@ // Needs to be public so can be used by sub-packages; i.e. the JDBCService @API(API.Status.EXPERIMENTAL) public class FRL implements AutoCloseable { - /** - * JVM-wide lock guarding any operation that mutates the shared catalog metadata or its underlying - * static state ({@link com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider#registerDomainIfNotExists} - * and the catalog-bootstrap transaction below). Without this, concurrent {@code new FRL(...)} calls - * — e.g. from parallel test {@code @BeforeAll} hooks — race on the catalog-init transaction commit - * and fail with "Transaction not committed due to conflict with another transaction". - *

- * Exposed (package-public to {@code .server}; referenced by reflection or test-only callers via - * {@link #catalogLock()}) so that test scaffolding that runs additional catalog-mutating DDL - * (CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE) can serialize against FRL initialization - * on the same JVM and avoid the same race. - */ - private static final Object CATALOG_LOCK = new Object(); private final FdbConnection fdbDatabase; private final RelationalDriver driver; private boolean registeredJDBCEmbedDriver; - /** - * Returns the JVM-wide monitor used to serialize catalog-mutating DDL. Hold this when issuing - * CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, or any other operation that writes the - * cluster-global catalog metadata, to avoid SQLSTATE 40001 conflicts with concurrent - * {@link #FRL(Options, String, boolean)} construction or other DDL on the same JVM. - * - * @return the JVM-wide catalog-mutation monitor - */ - @Nonnull - public static Object catalogLock() { - return CATALOG_LOCK; - } - public FRL() throws RelationalException { this(Options.NONE, null); } @@ -130,23 +104,21 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } this.fdbDatabase = new DirectFdbConnection(fdbDb, NoOpMetricRegistry.INSTANCE); - // Serialize the rest of construction in-JVM: registerDomainIfNotExists mutates the shared - // keyspace, and the catalog-init transaction below races on commit when multiple FRLs are - // constructed concurrently against the same cluster (typical in parallel tests). - // - // The lock only covers within-JVM contention; multiple OS processes (e.g. parent test JVM - // + several external-server subprocesses) constructing FRL against the same cluster will - // still race on the catalog-init commit and produce SQLSTATE 40001. Retry handles that - // case — the catalog init is idempotent under retry because openRecordStore opens the - // already-initialized store on a re-attempt. - final StoreCatalog storeCatalog; - final KeySpace keySpace; - synchronized (CATALOG_LOCK) { - final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); - keyspaceProvider.registerDomainIfNotExists("FRL"); - keySpace = keyspaceProvider.getKeySpace(); - storeCatalog = initializeCatalogWithRetry(keySpace); - } + // No JVM-wide lock is needed here — `registerDomainIfNotExists` is synchronized on + // the singleton and `RecordLayerStoreCatalog.initialize` is idempotent (see the + // conditional saveSchema in that method). We do retry the catalog-open commit on + // SERIALIZATION_FAILURE, though: multiple FRLs can still race on the very first init + // against a fresh cluster (multiple test-class @BeforeAll hooks constructing FRLs + // concurrently against the same cluster), or on any cross-process init when several + // FRL processes start together. Once the catalog exists, subsequent inits are + // read-only and don't need the retry, so the loop terminates quickly in the common + // case. For test wiring where multiple JVM processes race on the very first init of a + // fresh cluster, see the proactive one-shot initializer wired into + // {@code YamlTestExtension.beforeAll} (yaml-tests/YamlTestCatalogInitializer). + final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); + keyspaceProvider.registerDomainIfNotExists("FRL"); + final KeySpace keySpace = keyspaceProvider.getKeySpace(); + final StoreCatalog storeCatalog = openCatalogWithRetry(keySpace); RecordLayerConfig rlConfig = RecordLayerConfig.getDefault(); RecordLayerMetadataOperationsFactory ddlFactory = new RecordLayerMetadataOperationsFactory.Builder() @@ -181,15 +153,16 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } /** - * Initializes the store catalog under a fresh transaction, retrying on SQLSTATE 40001 - * (transaction conflict) up to a small attempt limit. Used to absorb cross-JVM races on the - * catalog-init commit when several FRL processes start concurrently against the same cluster - * (e.g. the test JVM plus several external-server subprocesses). Within a single JVM the - * surrounding {@link #CATALOG_LOCK} prevents contention; this retry covers the cross-process - * case where that lock has no effect. + * Opens (creating on first use) the store catalog, retrying on SQLSTATE 40001 transaction + * conflicts up to a small attempt limit. Absorbs races between concurrent FRL + * constructions — both within-JVM (parallel test class @BeforeAll hooks) and cross-JVM + * (parent test process plus external-server subprocesses) — on the catalog-init commit. + * After the very first init succeeds, subsequent calls are read-only (see the conditional + * saveSchema in {@code RecordLayerStoreCatalog#initialize}) and this loop terminates on + * the first attempt. */ @Nonnull - private StoreCatalog initializeCatalogWithRetry(@Nonnull KeySpace keySpace) throws RelationalException { + private StoreCatalog openCatalogWithRetry(@Nonnull KeySpace keySpace) throws RelationalException { final int maxAttempts = 10; RelationalException last = null; for (int attempt = 1; attempt <= maxAttempts; attempt++) { @@ -202,7 +175,7 @@ private StoreCatalog initializeCatalogWithRetry(@Nonnull KeySpace keySpace) thro throw e; } last = e; - // Short, slightly increasing backoff; cross-process commit-conflict windows are brief. + // Short, linearly-increasing backoff; commit-conflict windows are brief. try { Thread.sleep(20L * attempt); } catch (InterruptedException ie) { @@ -211,7 +184,6 @@ private StoreCatalog initializeCatalogWithRetry(@Nonnull KeySpace keySpace) thro } } } - // Exhausted retries; surface the most recent conflict so callers see why startup failed. throw last; } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java index 25eebe7a01..b349a8ea12 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java @@ -21,8 +21,6 @@ package com.apple.foundationdb.relational.yamltests.block; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.exceptions.ErrorCode; -import com.apple.foundationdb.relational.server.FRL; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.yamltests.CustomYamlConstructor; import com.apple.foundationdb.relational.yamltests.Matchers; @@ -73,7 +71,7 @@ protected SetupBlock(@Nonnull YamlReference reference, @Nonnull List executeExecutables(executables)); + executeExecutables(executables); } catch (Throwable e) { throw YamlExecutionContext.wrapContext(e, () -> "‼️ Failed to execute all the setup steps in Setup block at " + getReference(), @@ -81,61 +79,6 @@ public void execute() { } } - /** - * Runs {@code runnable} under {@link FRL#catalogLock()} with a small retry loop on SQLSTATE - * 40001 transaction conflicts. The lock is shared with {@link FRL}'s catalog-bootstrap, so - * setup-block DDL (CREATE/DROP DATABASE, CREATE/DROP SCHEMA TEMPLATE, CREATE SCHEMA) is - * serialized against both other setup blocks and other test classes' FRL construction on the - * same JVM. The retry catches any residual conflicts caused by code paths that write the - * cluster-global meta-data version-stamp outside our control. - *

- * Retry is safe because the schema_template and destruct executable lists use {@code DROP … - * IF EXISTS} so a partial-success retry doesn't trip "unknown database" errors. Manual setup - * blocks that include catalog-mutating DDL are expected to follow the same convention. - */ - private static void runLockedWithRetry(@Nonnull Runnable runnable) { - final int maxAttempts = 5; - for (int attempt = 1; ; attempt++) { - final Throwable failure; - synchronized (FRL.catalogLock()) { - try { - runnable.run(); - return; - } catch (Throwable e) { - if (attempt >= maxAttempts || !isTransactionConflict(e)) { - throw e; - } - failure = e; - } - } - // Sleep with the lock RELEASED — keeping it held during backoff would block other - // catalog ops unnecessarily and triggers SpotBugs SWL_SLEEP_WITH_LOCK_HELD. The - // retry races other catalog ops on the next iteration, which is what we want. - try { - Thread.sleep(10L * attempt); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - // Wrap the interrupt as the cause to satisfy PMD's PreserveStackTrace; attach - // the original retriable failure as suppressed so its stack trace survives too. - final RuntimeException wrapped = new RuntimeException(ie); - wrapped.addSuppressed(failure); - throw wrapped; - } - } - } - - private static boolean isTransactionConflict(@Nonnull Throwable t) { - Throwable cursor = t; - while (cursor != null) { - if (cursor instanceof SQLException - && ErrorCode.SERIALIZATION_FAILURE.getErrorCode().equals(((SQLException) cursor).getSQLState())) { - return true; - } - cursor = cursor.getCause(); - } - return false; - } - public static final class ManualSetupBlock extends SetupBlock { public static final String STEPS = "steps"; From b2a0486d107495780bcd792b37797e481308baf3 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 8 Jul 2026 14:00:53 -0400 Subject: [PATCH 67/74] Initialize the catalog in more places --- .../relational/yamltests/YamlTestCatalogInitializer.java | 4 ++-- .../relational/yamltests/configs/EmbeddedConfig.java | 9 +++++++++ .../yamltests/configs/JDBCInProcessConfig.java | 5 +++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java index e1d990d72d..2a6f57c2c0 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java @@ -59,7 +59,7 @@ * package already depends on the record-layer primitives this needs directly, so a private * helper here is cheaper than adding another cross-module dependency edge. */ -final class YamlTestCatalogInitializer { +public final class YamlTestCatalogInitializer { private static final int MAX_ATTEMPTS = 10; private static final long RETRY_BASE_MILLIS = 20L; @@ -77,7 +77,7 @@ private YamlTestCatalogInitializer() { * default cluster" per {@link FDBDatabaseFactory#getDatabase(String)} * @throws RelationalException if any cluster's init ultimately fails after retries */ - static void initializeCatalog(@Nonnull final List clusterFiles) throws RelationalException { + public static void initializeCatalog(@Nonnull final List clusterFiles) throws RelationalException { // Register the FRL keyspace domain once up front. It's idempotent, but doing it here // ensures the keyspace tree is ready before we resolve any paths against it below. final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java index ebb5e3051b..163f529ec0 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java @@ -26,6 +26,7 @@ import com.apple.foundationdb.relational.server.FRL; import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; +import com.apple.foundationdb.relational.yamltests.YamlTestCatalogInitializer; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.apple.foundationdb.relational.yamltests.connectionfactory.EmbeddedYamlConnectionFactory; @@ -57,6 +58,14 @@ public EmbeddedConfig(@Nonnull final List clusterFiles) { @Override @SuppressWarnings("PMD.CloseResource") // FRLs are tracked in the list and closed in afterAll() public void beforeAll() throws Exception { + // Proactively initialise the SYS catalog on every cluster before we spin up any + // in-JVM FRL for this config. This absorbs the concurrent-init commit conflict that + // otherwise fires when parallel @YamlTest / test-class @BeforeAll hooks all + // construct their own EmbeddedConfig against the same cluster. The initializer is + // idempotent — after the first successful commit against a cluster, subsequent + // invocations are read-only and effectively free. See YamlTestCatalogInitializer + // for the details. + YamlTestCatalogInitializer.initializeCatalog(clusterFiles); var options = Options.builder() .withOption(Options.Name.PLAN_CACHE_PRIMARY_TIME_TO_LIVE_MILLIS, 3_600_000L) .withOption(Options.Name.PLAN_CACHE_SECONDARY_TIME_TO_LIVE_MILLIS, 3_600_000L) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCInProcessConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCInProcessConfig.java index d7bef4b69e..a7fa45d2db 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCInProcessConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCInProcessConfig.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.relational.server.InProcessRelationalServer; import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; +import com.apple.foundationdb.relational.yamltests.YamlTestCatalogInitializer; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.apple.foundationdb.relational.yamltests.connectionfactory.JDBCInProcessYamlConnectionFactory; @@ -49,6 +50,10 @@ public JDBCInProcessConfig(@Nonnull final List clusterFiles) { @Override @SuppressWarnings("PMD.CloseResource") // Servers are tracked in the list and closed in afterAll() public void beforeAll() throws Exception { + // Proactively initialise the SYS catalog on every cluster before we spin up any + // in-process server. Same reasoning as EmbeddedConfig — parallel test-class @BeforeAll + // hooks would otherwise race on the catalog-init commit. Idempotent under retry. + YamlTestCatalogInitializer.initializeCatalog(clusterFiles); clusters = Clusters.fromClusterFilesAsEntries(clusterFiles, clusterFile -> { try { From 6b3456a55dd575944a74d7eb285a02bff834ba12 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 9 Jul 2026 11:26:42 -0400 Subject: [PATCH 68/74] Add support for exposing conflicting keys and set that in YamlTest --- .../foundationdb/relational/api/Options.java | 11 ++ .../EmbeddedRelationalConnection.java | 52 +++++++++ .../RecordLayerTransactionManager.java | 4 + .../util/ConflictKeyFormatter.java | 103 ++++++++++++++++++ .../yamltests/SimpleYamlConnection.java | 11 ++ .../yamltests/YamlTestCatalogInitializer.java | 23 +++- 6 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/ConflictKeyFormatter.java diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/Options.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/Options.java index a159ee60d9..ad78486be4 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/Options.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/Options.java @@ -257,6 +257,15 @@ public enum Name { * A boolean indicating whether to (attempt to) compress records when saving. */ COMPRESS_WHEN_SERIALIZING, + + /** + * A boolean indicating whether the underlying {@code FDBRecordContext} should be configured to + * populate its list of not-committed conflicting keys after a failed commit. Useful for + * diagnosing serialization failures — when enabled, the connection's commit failure message + * will include the concrete FDB key ranges that conflicted. Off by default (there is a small + * extra read against the FDB special-key-space on commit failure when it is on). + */ + REPORT_CONFLICTING_KEYS, } public enum IndexFetchMethod { @@ -300,6 +309,7 @@ public enum IndexFetchMethod { builder.put(Name.ENCRYPT_WHEN_SERIALIZING, false); builder.put(Name.ENCRYPTION_KEY_PASSWORD, ""); builder.put(Name.COMPRESS_WHEN_SERIALIZING, true); + builder.put(Name.REPORT_CONFLICTING_KEYS, false); OPTIONS_DEFAULT_VALUES = builder.build(); } @@ -560,6 +570,7 @@ private static Map> makeContracts() { data.put(Name.ENCRYPTION_KEY_ENTRY_LIST, List.of(new OrderedCollectionContract<>(TypeContract.stringType()))); data.put(Name.ENCRYPTION_KEY_PASSWORD, List.of(TypeContract.nullableStringType())); data.put(Name.COMPRESS_WHEN_SERIALIZING, List.of(TypeContract.booleanType())); + data.put(Name.REPORT_CONFLICTING_KEYS, List.of(TypeContract.booleanType())); return Collections.unmodifiableMap(data); } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java index bb9c0671f5..ed4ed623fa 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java @@ -20,10 +20,12 @@ package com.apple.foundationdb.relational.recordlayer; +import com.apple.foundationdb.Range; import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.ExecuteProperties; import com.apple.foundationdb.record.IsolationLevel; import com.apple.foundationdb.record.RecordCoreException; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; import com.apple.foundationdb.relational.api.EmbeddedRelationalDriver; import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; import com.apple.foundationdb.relational.api.Options; @@ -48,11 +50,15 @@ import com.apple.foundationdb.relational.recordlayer.metric.StoreTimerMetricCollector; import com.apple.foundationdb.relational.recordlayer.structuredsql.expression.ExpressionFactoryImpl; import com.apple.foundationdb.relational.recordlayer.structuredsql.statement.StatementBuilderFactoryImpl; +import com.apple.foundationdb.relational.recordlayer.util.ConflictKeyFormatter; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; import com.apple.foundationdb.relational.util.Supplier; import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.URI; @@ -62,6 +68,7 @@ import java.sql.SQLWarning; import java.sql.Struct; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.stream.Collectors; @@ -81,6 +88,8 @@ @API(API.Status.EXPERIMENTAL) public class EmbeddedRelationalConnection implements RelationalConnection { + private static final Logger LOGGER = LoggerFactory.getLogger(EmbeddedRelationalConnection.class); + /** * Chose a transaction level that we *pretend* to support (Rather than throw an exception that stops * all processing). @@ -199,6 +208,10 @@ void commitInternal() throws SQLException { getTransaction().commit(); } catch (RuntimeException | RelationalException re) { err = ExceptionUtil.toRelationalException(re); + // Best-effort: if the underlying FDB context was configured with + // Options.Name.REPORT_CONFLICTING_KEYS, pull the not-committed conflict ranges out and + // enrich the exception (and log) so we can see which key(s) actually collided. + err = attachConflictingKeys(err); } try { getTransaction().close(); @@ -215,6 +228,45 @@ void commitInternal() throws SQLException { } } + /** + * If the transaction's FDB context had {@code reportConflictingKeys} enabled and the commit + * failed with NOT_COMMITTED, rewrap the exception with a message that includes the concrete + * conflict ranges (so they appear in every stack trace) and also attach them to the exception's + * context map. Never throws — instrumentation must not itself fail the commit path. + * + * @param err the original exception; returned unchanged if extraction fails or reporting is off + * @return the (possibly-rewrapped) exception with conflict information baked into its message + */ + @Nonnull + private RelationalException attachConflictingKeys(@Nonnull RelationalException err) { + try { + final Transaction txn = transaction; + if (txn == null) { + return err; + } + final FDBRecordContext ctx = txn.unwrap(FDBRecordContext.class); + final List ranges = ctx.getNotCommittedConflictingKeys(); + if (ranges == null) { + return err; // reporting was not enabled, or commit did not fail with NOT_COMMITTED + } + final String rendered = ConflictKeyFormatter.formatRanges(ranges); + final String enrichedMessage = (err.getMessage() == null ? "" : err.getMessage()) + + " [conflicting_keys_count=" + ranges.size() + ", conflicting_keys=" + rendered + "]"; + LOGGER.warn("FDB transaction commit failed with conflicting keys: count={} ranges={}", + ranges.size(), rendered); + final RelationalException rewrapped = new RelationalException(enrichedMessage, err.getErrorCode(), err); + rewrapped.addContext("conflicting_keys", rendered); + rewrapped.addContext("conflicting_keys_count", ranges.size()); + // Preserve any context that was already on the original exception. + rewrapped.withContext(err.getContext()); + return rewrapped; + } catch (Exception e) { + // Never let diagnostic extraction mask the real error. + LOGGER.debug("Failed to extract conflicting keys from failed commit", e); + return err; + } + } + @Override public void rollback() throws SQLException { checkOpen(); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordLayerTransactionManager.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordLayerTransactionManager.java index 8cf472ece2..92e5a70996 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordLayerTransactionManager.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordLayerTransactionManager.java @@ -66,6 +66,10 @@ private FDBRecordContextConfig getFDBRecordContextConfig(@Nonnull Options option if (transactionTimeout != null) { builder.setTransactionTimeoutMillis(transactionTimeout); } + Boolean reportConflictingKeys = options.getOption(Options.Name.REPORT_CONFLICTING_KEYS); + if (Boolean.TRUE.equals(reportConflictingKeys)) { + builder.setReportConflictingKeys(true); + } return builder.build(); } } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/ConflictKeyFormatter.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/ConflictKeyFormatter.java new file mode 100644 index 0000000000..da82689411 --- /dev/null +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/ConflictKeyFormatter.java @@ -0,0 +1,103 @@ +/* + * ConflictKeyFormatter.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2021-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.relational.recordlayer.util; + +import com.apple.foundationdb.Range; +import com.apple.foundationdb.annotation.API; +import com.apple.foundationdb.tuple.ByteArrayUtil; +import com.apple.foundationdb.tuple.Tuple; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Utilities for rendering FDB conflict ranges (returned by + * {@code FDBRecordContext.getNotCommittedConflictingKeys()}) into strings suitable for logging + * and inclusion in exception messages. + * + *

For each key we attempt to parse it as an FDB {@link Tuple}; if that fails we fall back to + * {@link ByteArrayUtil#printable(byte[])} which produces the same escaping FDB tooling uses (e.g. + * {@code \xff/metadataVersion}).

+ */ +@API(API.Status.EXPERIMENTAL) +public final class ConflictKeyFormatter { + + private ConflictKeyFormatter() { + } + + /** + * Render a list of conflict ranges as a compact human-readable string. + * Returns {@code "[]"} for an empty list, {@code "null"} for null. + * + * @param ranges the conflict ranges, or null + * @return a comma-separated description of the ranges enclosed in brackets + */ + @Nonnull + public static String formatRanges(@Nullable List ranges) { + if (ranges == null) { + return "null"; + } + if (ranges.isEmpty()) { + return "[]"; + } + return ranges.stream() + .map(ConflictKeyFormatter::formatRange) + .collect(Collectors.joining(", ", "[", "]")); + } + + /** + * Render a single conflict range as {@code "begin..end"} using {@link #formatKey(byte[])} for each bound. + * @param range the range + * @return a printable description + */ + @Nonnull + public static String formatRange(@Nonnull Range range) { + return formatKey(range.begin) + ".." + formatKey(range.end); + } + + /** + * Render a single FDB key. First attempts to decode as a {@link Tuple}; if that fails, falls back to + * {@link ByteArrayUtil#printable(byte[])}. + * @param key the raw key bytes, may be null + * @return a printable description + */ + @Nonnull + public static String formatKey(@Nullable byte[] key) { + if (key == null) { + return "null"; + } + if (key.length == 0) { + return "\"\""; + } + // System-key-space keys start with 0xff; don't try to Tuple-decode them (they aren't tuples). + if ((key[0] & 0xff) != 0xff) { + try { + final Tuple t = Tuple.fromBytes(key); + return t.toString(); + } catch (RuntimeException ignored) { + // Fall through to raw byte rendering. + } + } + return ByteArrayUtil.printable(key); + } +} diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/SimpleYamlConnection.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/SimpleYamlConnection.java index 558d7ec8fe..721b16abe7 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/SimpleYamlConnection.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/SimpleYamlConnection.java @@ -59,6 +59,17 @@ public SimpleYamlConnection(@Nonnull Connection connection, @Nonnull SemanticVer this.versions = List.of(version); this.connectionLabel = connectionLabel; this.clusterFile = clusterFile; + // Diagnostic: ask the underlying FDB context to record the ranges that caused any + // commit-time serialization failure. On failure, EmbeddedRelationalConnection.commitInternal + // will attach the ranges to the exception's context and warn-log them. Overhead is only + // paid on a failed commit (an extra read of the conflict special-key-space). + // + // Restricted to embedded connections because the JDBC/gRPC option-serialization layer + // does not yet know how to encode this new option name (throws "Cannot encode option in + // protobuf" later when the options are pushed to the server). + if (underlying instanceof EmbeddedRelationalConnection) { + underlying.setOption(Options.Name.REPORT_CONFLICTING_KEYS, true); + } } @Nonnull diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java index 2a6f57c2c0..1e0b8019f1 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java @@ -20,8 +20,11 @@ package com.apple.foundationdb.relational.yamltests; +import com.apple.foundationdb.Range; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContextConfig; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpace; import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; @@ -30,6 +33,10 @@ import com.apple.foundationdb.relational.recordlayer.RecordContextTransaction; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.recordlayer.catalog.StoreCatalogProvider; +import com.apple.foundationdb.relational.recordlayer.util.ConflictKeyFormatter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.util.List; @@ -61,6 +68,8 @@ */ public final class YamlTestCatalogInitializer { + private static final Logger LOGGER = LoggerFactory.getLogger(YamlTestCatalogInitializer.class); + private static final int MAX_ATTEMPTS = 10; private static final long RETRY_BASE_MILLIS = 20L; @@ -90,9 +99,15 @@ public static void initializeCatalog(@Nonnull final List clusterFiles) t private static void initializeOne(final String clusterFile, @Nonnull final KeySpace keySpace) throws RelationalException { final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(clusterFile); + // Ask FDB to record the specific conflict ranges on any commit failure so we can log + // exactly which key(s) collided when a retry happens. + final FDBRecordContextConfig config = FDBRecordContextConfig.newBuilder() + .setReportConflictingKeys(true) + .build(); RelationalException last = null; for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { - try (Transaction txn = new RecordContextTransaction(database.openContext())) { + final FDBRecordContext ctx = database.openContext(config); + try (Transaction txn = new RecordContextTransaction(ctx)) { final StoreCatalog ignored = StoreCatalogProvider.getCatalog(txn, keySpace); txn.commit(); return; @@ -100,6 +115,12 @@ private static void initializeOne(final String clusterFile, @Nonnull final KeySp if (e.getErrorCode() != ErrorCode.SERIALIZATION_FAILURE) { throw e; } + final List ranges = ctx.getNotCommittedConflictingKeys(); + if (ranges != null) { + final String rendered = ConflictKeyFormatter.formatRanges(ranges); + LOGGER.warn("YamlTestCatalogInitializer commit conflict on {} (attempt {}/{}): count={} ranges={}", + clusterFile == null ? "" : clusterFile, attempt, MAX_ATTEMPTS, ranges.size(), rendered); + } last = e; // Short, linear back-off; the commit-conflict window is brief and clearing it // just requires the other party's commit to land. From 74cc3ee2ebdc7cc9ebaf608e0b6b5d637dbe46fe Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 9 Jul 2026 11:27:21 -0400 Subject: [PATCH 69/74] Temp add plan cache metric logging Temporary additional logging on the plan cache to expose more information because sometimes tests fail because they have a cache miss. I haven't reproduced with the logging but saving that for now. --- .../yamltests/command/QueryExecutor.java | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java index c0f67b7b82..0961c01d0a 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java @@ -155,17 +155,44 @@ private Object executeStatementAndCheckCacheIfNeeded(@Nonnull Statement s, final return executeStatementAndCheckForceContinuations(s, statementHasQuery, queryString, connection, maxRows); } final var preMetricCollector = connection.getMetricCollector(); - final var preValue = preMetricCollector != null && - preMetricCollector.hasCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) ? - preMetricCollector.getCountsForCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) : 0; + final long preTertiaryHit = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT); + // Snapshot all cache counters so we can log per-counter deltas if the plan-hit + // assertion fails. Distinguishes: + // - miss (populate happened this call) → PLAN_CACHE_TERTIARY_MISS incremented + // - eviction wiped the entry we expected → *_LRU_EVICTION incremented + // - loader-race (another thread populated) → all miss/hit counters unchanged for us + // - primary/secondary wipeout that killed the tertiary → PRIMARY_MISS / SECONDARY_MISS + final long prePrimaryMiss = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_PRIMARY_MISS); + final long preSecondaryMiss = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_SECONDARY_MISS); + final long preTertiaryMiss = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_MISS); + final long prePrimaryLru = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_PRIMARY_LRU_EVICTION); + final long preSecondaryLru = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_SECONDARY_LRU_EVICTION); + final long preTertiaryLru = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_LRU_EVICTION); final var toReturn = executeStatementAndCheckForceContinuations(s, statementHasQuery, queryString, connection, maxRows); final var postMetricCollector = connection.getMetricCollector(); if (postMetricCollector != null) { final var postValue = postMetricCollector.hasCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) ? postMetricCollector.getCountsForCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) : 0; - final var planFound = preMetricCollector != postMetricCollector ? postValue == 1 : postValue == preValue + 1; + final var planFound = preMetricCollector != postMetricCollector ? postValue == 1 : postValue == preTertiaryHit + 1; if (!planFound) { - reportTestFailure("‼️ Expected to retrieve the plan from the cache at " + reference); + final long postPrimaryMiss = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_PRIMARY_MISS); + final long postSecondaryMiss = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_SECONDARY_MISS); + final long postTertiaryMiss = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_MISS); + final long postPrimaryLru = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_PRIMARY_LRU_EVICTION); + final long postSecondaryLru = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_SECONDARY_LRU_EVICTION); + final long postTertiaryLru = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_LRU_EVICTION); + final String delta = String.format( + " [samePMC=%s, tertiaryHit %d→%d, tertiaryMiss +%d, secondaryMiss +%d, primaryMiss +%d," + + " tertiaryLru +%d, secondaryLru +%d, primaryLru +%d]", + preMetricCollector == postMetricCollector, + preTertiaryHit, postValue, + postTertiaryMiss - preTertiaryMiss, + postSecondaryMiss - preSecondaryMiss, + postPrimaryMiss - prePrimaryMiss, + postTertiaryLru - preTertiaryLru, + postSecondaryLru - preSecondaryLru, + postPrimaryLru - prePrimaryLru); + reportTestFailure("‼️ Expected to retrieve the plan from the cache at " + reference + delta); } else { logger.debug("🎁 Retrieved the plan from the cache!"); } @@ -173,6 +200,11 @@ private Object executeStatementAndCheckCacheIfNeeded(@Nonnull Statement s, final return toReturn; } + private static long readCounter(@Nullable com.apple.foundationdb.relational.api.metrics.MetricCollector mc, + @Nonnull RelationalMetric.RelationalCount c) throws RelationalException { + return mc != null && mc.hasCounter(c) ? mc.getCountsForCounter(c) : 0L; + } + @Nullable private Continuation executeQuery(@Nonnull YamlConnection connection, @Nonnull QueryConfig config, @Nonnull String currentQuery, boolean checkCache, @Nullable Integer maxRows, From 786e22bcb32836c5585f3ddc090ea1ffc1208d70 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 9 Jul 2026 12:02:35 -0400 Subject: [PATCH 70/74] TEMP: Characterise state-cache miss vs. hit conflict behaviour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add three related tests to FDBRecordStoreStateCacheTest that pin the interaction between the state cache and FDB's read-conflict tracking in FDBRecordStore.loadStoreHeaderAsync. cacheMissOpenConflictsWithConcurrentHeaderWrite Miss path takes loadStoreHeaderAsync, which issues a SERIALIZABLE getRange(subspace.range(), 1). The resulting read conflict covers the store header key. A concurrent commit that writes STORE_INFO_KEY (setHeaderUserField here) fails the reader with FDBStoreTransactionConflictException. cacheHitOpenDoesNotConflictWithConcurrentRecordInsert When the cache serves the open, FDBRecordStoreStateCacheEntry.handleCachedState only adds a point read conflict on STORE_INFO_KEY. A concurrent record insert that writes into /RECORDS/... does not overlap, so the reader commits cleanly. concurrentCacheMissOpensBothInsertingRecordsDoNotConflict Two concurrent cache-miss opens that both only insert records (no header mutation) commit cleanly. This is the surprising one: it rules out a natural-sounding hypothesis — that the SERIALIZABLE getRange in loadStoreHeaderAsync adds subspace.range() as a read-conflict range and thereby conflicts with any concurrent write inside that subspace. If that were true, the two record inserts here would collide; they don't, so the miss-path read conflict stops at (or near) the store header key. Motivation: these tests were written while trying to explain /__SYS/CATALOG SERIALIZATION_FAILUREs observed in the parallel yaml-tests harness. The third test disproves the leading hypothesis that the reported subspace-wide conflict range came from the miss-path read alone. The mechanism producing that conflict shape is still not identified, but any future explanation now has to be consistent with these three assertions. --- .../FDBRecordStoreStateCacheTest.java | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java index 76b1e13599..13f8cc17a5 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java @@ -892,6 +892,189 @@ void deleteMissingStoreDoesNotBumpMetaDataVersionStamp() throws Exception { } } + /** + * Regression test that pins the current behaviour: on a state-cache miss, opening a + * cacheable store issues a {@code SERIALIZABLE getRange(subspace.range(), 1)} inside + * {@code FDBRecordStore.loadStoreHeaderAsync}, which registers a read-conflict range that + * covers (at minimum) the store header key. Any concurrent transaction that writes to the + * store header — including the very common case of a second opener that itself hits a cache + * miss and updates the header via {@code checkVersion} — will therefore serialise-fail the + * first transaction if the writer commits first. + * + *

This is the shape of the failure we hit in the yaml-tests parallel harness: a "reader" + * transaction opens the {@code /__SYS/CATALOG} store, misses the cache, adds a wide read + * conflict, and then loses the commit race to a concurrent writer inside the same subspace. + * The paired positive control below ({@link #cacheHitOpenDoesNotConflictWithConcurrentRecordInsert()}) + * shows that when the cache actually serves the open, the read conflict shrinks to a single + * key on {@code STORE_INFO_KEY} and no conflict occurs.

+ */ + @Test + void cacheMissOpenConflictsWithConcurrentHeaderWrite() throws Exception { + fdb.setStoreStateCache(metaDataVersionStampCacheFactory.getCache(fdb)); + + // Bootstrap: create the store as cacheable, then invalidate the cache so the next opens + // are guaranteed to miss. + FDBRecordStore.Builder storeBuilder; + try (FDBRecordContext context = openContext()) { + openSimpleRecordStore(context); + assertTrue(recordStore.setStateCacheability(true)); + storeBuilder = recordStore.asBuilder(); + commit(context); + } + // Bump the meta-data version stamp to invalidate any cache entries. From here forward, + // opens will miss until at least one commits and repopulates the cache. + try (FDBRecordContext context = fdb.openContext()) { + context.setMetaDataVersionStamp(); + commit(context); + } + + // Open two contexts *concurrently* — both will fall through to loadStoreHeaderAsync, both + // will add a range read conflict that covers (at minimum) the header key. + try (FDBRecordContext readerContext = fdb.openContext(null, new FDBStoreTimer()); + FDBRecordContext writerContext = fdb.openContext(null, new FDBStoreTimer())) { + // Pin both read versions *before* the writer commits, so the conflict has to be + // resolved at commit time rather than being masked by an updated read version. + readerContext.getReadVersion(); + writerContext.getReadVersion(); + + // Reader: opens the store — this is where the SERIALIZABLE range read that will + // eventually serialise-fail us gets added to the read-conflict set. + FDBRecordStore reader = storeBuilder.copyBuilder().setContext(readerContext).open(); + // Give the reader an explicit write so its commit actually runs conflict resolution + // (empty transactions skip it). Any unrelated key works. + readerContext.ensureActive().addWriteConflictKey(reader.recordsSubspace().pack(UUID.randomUUID())); + + // Writer: open the same store (also a cache miss), then write the header + // (setHeaderUserField sets STORE_INFO_KEY) and commit first. + FDBRecordStore writer = storeBuilder.copyBuilder().setContext(writerContext).open(); + writer.setHeaderUserField("miss-conflict-probe", + com.google.protobuf.ByteString.copyFromUtf8("probe-" + UUID.randomUUID())); + commit(writerContext); + + // Now the reader must fail: its cache-miss read range on the header overlaps the + // writer's header write. + assertThrows(FDBExceptions.FDBStoreTransactionConflictException.class, readerContext::commit, + "cache-miss reader should conflict with concurrent header writer"); + } + } + + /** + * Positive control for {@link #cacheMissOpenConflictsWithConcurrentHeaderWrite()}: when the + * cache serves the open, the state-cache path adds only a point read conflict on + * {@code STORE_INFO_KEY} (see {@code FDBRecordStoreStateCacheEntry.handleCachedState}). A + * concurrent writer that inserts a record without touching the header will not conflict. + * + *

Together with the negative test above, this proves that the cache-miss ↔ range-read is + * the mechanism that causes the yaml-tests {@code /__SYS/CATALOG} conflicts: eliminating + * misses (e.g. by pinning the cache or by narrowing the miss path) would remove the + * conflict class entirely.

+ */ + @Test + void cacheHitOpenDoesNotConflictWithConcurrentRecordInsert() throws Exception { + fdb.setStoreStateCache(metaDataVersionStampCacheFactory.getCache(fdb)); + + FDBRecordStore.Builder storeBuilder; + try (FDBRecordContext context = openContext()) { + openSimpleRecordStore(context); + assertTrue(recordStore.setStateCacheability(true)); + storeBuilder = recordStore.asBuilder(); + commit(context); + } + // Prime the cache with an open+commit so the entry is loaded and valid. + try (FDBRecordContext context = fdb.openContext(null, new FDBStoreTimer())) { + storeBuilder.copyBuilder().setContext(context).open(); + commit(context); + } + + try (FDBRecordContext readerContext = fdb.openContext(null, new FDBStoreTimer()); + FDBRecordContext writerContext = fdb.openContext(null, new FDBStoreTimer())) { + readerContext.getReadVersion(); + writerContext.getReadVersion(); + + // Reader: cache HIT — only STORE_INFO_KEY gets a point read conflict. + FDBRecordStore reader = storeBuilder.copyBuilder().setContext(readerContext).open(); + // Force conflict resolution to actually run by giving the reader a write of its own, + // targeting a fresh unrelated key so it doesn't collide with the record we insert below. + readerContext.ensureActive().addWriteConflictKey( + reader.recordsSubspace().pack(Tuple.from("cache-hit-probe-" + UUID.randomUUID()))); + + // Writer: insert a new record. This writes only to /RECORDS/ and index + // keys — it does not touch STORE_INFO_KEY. Cache HIT reader's point read conflict on + // STORE_INFO_KEY does not overlap. + FDBRecordStore writer = storeBuilder.copyBuilder().setContext(writerContext).open(); + writer.saveRecord(TestRecords1Proto.MySimpleRecord.newBuilder() + .setRecNo(4242L) + .setStrValueIndexed("cache-hit-probe") + .build()); + commit(writerContext); + + // Reader commits cleanly — its point read conflict on STORE_INFO_KEY doesn't overlap + // the writer's per-record writes. + commit(readerContext); + } + } + + /** + * Companion to the two tests above that isolates the record-insert axis. It confirms — perhaps + * surprisingly — that two concurrent cache-miss opens that both only insert records (no header + * mutation) do not conflict with each other. The SERIALIZABLE + * {@code getRange(subspace.range(), 1)} that the miss path runs in + * {@code loadStoreHeaderAsync} appears to add a read conflict only up to (roughly) the header + * key, not the full subspace, so writes at {@code /RECORDS/} (which sit past the + * header keyspace) don't overlap. + * + *

This constrains the shape of the yaml-tests {@code /__SYS/CATALOG} failure: since the + * observed conflict range there covers the entire CATALOG subspace, the failure cannot be + * explained by "two cache-miss opens both inserting records" alone. There must be an + * additional mechanism — most likely one of the concurrent transactions implicitly writing + * {@code STORE_INFO_KEY} via {@code updateStoreHeaderAsync} on the {@code checkVersion} dirty + * path (format-version bump, metadata-version bump, or cacheability flip), or a wider write + * from a DDL constant action.

+ */ + @Test + void concurrentCacheMissOpensBothInsertingRecordsDoNotConflict() throws Exception { + fdb.setStoreStateCache(metaDataVersionStampCacheFactory.getCache(fdb)); + + FDBRecordStore.Builder storeBuilder; + try (FDBRecordContext context = openContext()) { + openSimpleRecordStore(context); + assertTrue(recordStore.setStateCacheability(true)); + storeBuilder = recordStore.asBuilder(); + commit(context); + } + // Invalidate any cached entries so both concurrent opens below are guaranteed to miss. + try (FDBRecordContext context = fdb.openContext()) { + context.setMetaDataVersionStamp(); + commit(context); + } + + try (FDBRecordContext firstContext = fdb.openContext(null, new FDBStoreTimer()); + FDBRecordContext secondContext = fdb.openContext(null, new FDBStoreTimer())) { + // Pin both read versions before either commits, so conflict resolution runs on both. + firstContext.getReadVersion(); + secondContext.getReadVersion(); + + FDBRecordStore first = storeBuilder.copyBuilder().setContext(firstContext).open(); + FDBRecordStore second = storeBuilder.copyBuilder().setContext(secondContext).open(); + + // Both insert distinct records — no header touched, distinct primary keys, distinct + // index entries (different str_value_indexed). + first.saveRecord(TestRecords1Proto.MySimpleRecord.newBuilder() + .setRecNo(1001L) + .setStrValueIndexed("first-writer") + .build()); + second.saveRecord(TestRecords1Proto.MySimpleRecord.newBuilder() + .setRecNo(1002L) + .setStrValueIndexed("second-writer") + .build()); + + // Both commit cleanly — the cache-miss read conflict does not extend far enough to + // overlap a plain record-write, so this is not by itself the yaml-tests failure mode. + commit(secondContext); + commit(firstContext); + } + } + /** * Verify that updating a header user field will be updated if the store state is cached. */ From f49fdb8fb28f34c92dba53405b77cce2bf742505 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 9 Jul 2026 17:25:18 -0400 Subject: [PATCH 71/74] FIXME: Narrow read-conflict range added by FDBRecordStore.readStoreFirstKey MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FDBRecordStore.readStoreFirstKey previously issued context.readTransaction(isolationLevel.isSnapshot()) .getRange(subspace.range(), 1) which, under SERIALIZABLE isolation, relied on FDB's Java bindings to add a read-conflict range implicitly. Empirically, under heavy concurrent load that implicit range can widen to cover the entire store subspace, causing spurious SERIALIZATION_FAILUREs against any concurrent writer inside that subspace — the shape observed for the /__SYS/CATALOG conflicts in the parallel yaml-tests harness. The exact mechanism by which the implicit range widens is not fully understood. Now always read at SNAPSHOT so no implicit read-conflict range is added, then, if the caller passed a non-snapshot isolation level, explicitly add a deterministic read-conflict range: - [subspace.range().begin, firstKey + 0x00) if a key was found, - the full subspace.range() if the store is empty (so the load still serialises against a concurrent creator). Effect on yaml-tests: catalog SERIALIZATION_FAILUREs are meaningfully less frequent; a lingering hit occasionally still fires from another (currently unidentified) code path but is absorbed by retry. That being said, this doesn't align with analysis of the actual FDB code, and it should be doing what we would expect without this change. Another possibility, since this is yaml-tests, is that the external server (old version), didn't have the change to deleteStore, and we are doing deleteStore with that, and it is manifesting as a conflict range here, even though it shouldn't. Or there's something else wrong, and more testing will show that this did not fix the issue. --- .../provider/foundationdb/FDBRecordStore.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java index 8ea85bef91..7f8d414db4 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java @@ -3039,9 +3039,34 @@ private CompletableFuture updateStoreHeaderAsync(@Nonnull UnaryOperator readStoreFirstKey(@Nonnull FDBRecordContext context, @Nonnull Subspace subspace, @Nonnull IsolationLevel isolationLevel) { - final AsyncIterator iterator = context.readTransaction(isolationLevel.isSnapshot()).getRange(subspace.range(), 1).iterator(); + // Always read at snapshot isolation so FDB does not add an implicit read-conflict range + // spanning the whole subspace (which can pull in unrelated concurrent writers deep inside + // the store's subspace and produce spurious SERIALIZATION_FAILUREs). When the caller + // wanted SERIALIZABLE isolation, add back a narrower, deterministic read-conflict range + // ourselves: from the beginning of the subspace up to and including the first key we + // actually found. When the store is empty (no keys at all) we still have to cover the + // whole range to correctly serialise against a concurrent creator, so we widen back to + // the full subspace in that case. + final Range subspaceRange = subspace.range(); + final AsyncIterator iterator = context.readTransaction(true).getRange(subspaceRange, 1).iterator(); return context.instrument(FDBStoreTimer.Events.LOAD_RECORD_STORE_INFO, - iterator.onHasNext().thenApply(hasNext -> hasNext ? iterator.next() : null)); + iterator.onHasNext().thenApply(hasNext -> { + final KeyValue firstKey = hasNext ? iterator.next() : null; + if (!isolationLevel.isSnapshot()) { + final byte[] end; + if (firstKey == null) { + // Store is empty — we need to serialise against anyone else who might + // write into this subspace between our read and commit. + end = subspaceRange.end; + } else { + // Cover [subspaceRange.begin, firstKey + \x00) — i.e. the found key + // itself and everything before it, but nothing after. + end = ByteArrayUtil.join(firstKey.getKey(), new byte[]{0}); + } + context.ensureActive().addReadConflictRange(subspaceRange.begin, end); + } + return firstKey; + })); } /** From 9a09a596e3038ce233353bd4bbb395e8c45e143c Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 9 Jul 2026 17:28:46 -0400 Subject: [PATCH 72/74] FIXME: add trace logging to InstrumentedTransaction the goal here is to detect whatever is touching the catalog's entire range causing read conflicts in other tests. There wasn't really any success, but maybe with more runs it will show something. --- .../foundationdb/InstrumentedTransaction.java | 36 +++++++++++++++++++ .../yamltests/YamlTestCatalogInitializer.java | 4 ++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/InstrumentedTransaction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/InstrumentedTransaction.java index 942b749962..3745a2e6f5 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/InstrumentedTransaction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/InstrumentedTransaction.java @@ -30,6 +30,10 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.async.AsyncUtil; import com.apple.foundationdb.record.provider.common.StoreTimer; +import com.apple.foundationdb.tuple.ByteArrayUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -39,10 +43,21 @@ /** * Wrapper around {@link Transaction} that instruments certain calls to expose their behavior with * {@link FDBStoreTimer} metrics. + * + *

When this class's logger is set to {@code TRACE}, every mutation call + * ({@link #clear(byte[])}, {@link #clear(byte[], byte[])}, {@link #clear(Range)}, + * {@link #clearRangeStartsWith(byte[])}, and range-based + * {@link #addWriteConflictRange(byte[], byte[])}) emits the affected key range along with a + * captured stack trace. This is intended purely for diagnostic use — for example when hunting + * down which code path is issuing a wide subspace clear that's causing a + * {@code SERIALIZATION_FAILURE} on a concurrent read. Leaving the logger at its default level + * (WARN or above) keeps the overhead a single {@code isTraceEnabled()} check per call.

*/ @API(API.Status.INTERNAL) public class InstrumentedTransaction extends InstrumentedReadTransaction implements Transaction { + private static final Logger LOGGER = LoggerFactory.getLogger(InstrumentedTransaction.class); + @Nullable protected ReadTransaction snapshot; // lazily cached snapshot wrapper @@ -77,6 +92,7 @@ public void addReadConflictKey(byte[] key) { @Override public void addWriteConflictRange(byte[] keyBegin, byte[] keyEnd) { + traceMutation("addWriteConflictRange", keyBegin, keyEnd); underlying.addWriteConflictRange(checkKey(keyBegin), checkKey(keyEnd)); } @@ -94,12 +110,14 @@ public void set(byte[] key, byte[] value) { @Override public void clear(byte[] key) { + traceMutation("clear(key)", key, null); underlying.clear(checkKey(key)); increment(FDBStoreTimer.Counts.DELETES); } @Override public void clear(byte[] keyBegin, byte[] keyEnd) { + traceMutation("clear(range)", keyBegin, keyEnd); underlying.clear(checkKey(keyBegin), checkKey(keyEnd)); increment(FDBStoreTimer.Counts.DELETES); increment(FDBStoreTimer.Counts.RANGE_DELETES); @@ -107,6 +125,7 @@ public void clear(byte[] keyBegin, byte[] keyEnd) { @Override public void clear(Range range) { + traceMutation("clear(Range)", range.begin, range.end); checkKey(range.begin); checkKey(range.end); @@ -118,11 +137,28 @@ public void clear(Range range) { @Override @Deprecated public void clearRangeStartsWith(byte[] prefix) { + traceMutation("clearRangeStartsWith", prefix, null); underlying.clearRangeStartsWith(checkKey(prefix)); increment(FDBStoreTimer.Counts.DELETES); increment(FDBStoreTimer.Counts.RANGE_DELETES); } + /** + * Diagnostic hook: when this class's SLF4J logger is at TRACE, emit the operation name, the + * printable form of the affected key(s), and a stack trace. Callers pay only the cost of the + * {@code isTraceEnabled()} check at other log levels. Intended for hunting down subspace-wide + * range writes that cause serialization failures on concurrent readers. + */ + private static void traceMutation(@Nonnull String op, @Nullable byte[] begin, @Nullable byte[] end) { + if (!LOGGER.isTraceEnabled()) { + return; + } + final String beginStr = begin == null ? "null" : ByteArrayUtil.printable(begin); + final String endStr = end == null ? "" : ".." + ByteArrayUtil.printable(end); + LOGGER.trace("InstrumentedTransaction mutation: {} [{}{}]", op, beginStr, endStr, + new Throwable("stack for " + op)); + } + @Override public void mutate(MutationType opType, byte[] key, byte[] param) { underlying.mutate(opType, checkKey(key), param); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java index 1e0b8019f1..14b4c65c01 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java @@ -100,9 +100,11 @@ public static void initializeCatalog(@Nonnull final List clusterFiles) t private static void initializeOne(final String clusterFile, @Nonnull final KeySpace keySpace) throws RelationalException { final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(clusterFile); // Ask FDB to record the specific conflict ranges on any commit failure so we can log - // exactly which key(s) collided when a retry happens. + // exactly which key(s) collided when a retry happens. Setting a timer ensures the + // transaction is wrapped in InstrumentedTransaction so its mutations can be traced. final FDBRecordContextConfig config = FDBRecordContextConfig.newBuilder() .setReportConflictingKeys(true) + .setTimer(new com.apple.foundationdb.record.provider.foundationdb.FDBStoreTimer()) .build(); RelationalException last = null; for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { From 721a8f9999baafcd2e9b4a924aa75d7c1531ec1d Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 9 Jul 2026 17:29:44 -0400 Subject: [PATCH 73/74] Increase logging used for tests --- .../src/test/resources/log4j2-test.properties | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/yaml-tests/src/test/resources/log4j2-test.properties b/yaml-tests/src/test/resources/log4j2-test.properties index 0ec15c0797..0b5b19fa9b 100644 --- a/yaml-tests/src/test/resources/log4j2-test.properties +++ b/yaml-tests/src/test/resources/log4j2-test.properties @@ -1,10 +1,10 @@ appenders = console -loggers = yamlTestsLogger +loggers = yamlTestsLogger, embeddedConnLogger, recordStoreLogger, catalogInitLogger, instrumentedTxnLogger appender.console.type = Console appender.console.name = console appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %.-5level\t%date{DEFAULT}\t%msg%n +appender.console.layout.pattern = %.-5level\t%date{DEFAULT}\t%logger{1}\t%msg%n rootLogger.level = OFF @@ -12,3 +12,27 @@ logger.yamlTestsLogger.name=com.apple.foundationdb.relational.yamltests logger.yamlTestsLogger.level = INFO logger.yamlTestsLogger.appenderRefs = stdout logger.yamlTestsLogger.appenderRef.stdout.ref = console + +# Diagnostic: surface commit-conflict enrichment from EmbeddedRelationalConnection +logger.embeddedConnLogger.name=com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalConnection +logger.embeddedConnLogger.level = WARN +logger.embeddedConnLogger.appenderRefs = stdout +logger.embeddedConnLogger.appenderRef.stdout.ref = console + +# Diagnostic: surface checkVersion "dirty header" reasons from FDBRecordStore +logger.recordStoreLogger.name=com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore +logger.recordStoreLogger.level = INFO +logger.recordStoreLogger.appenderRefs = stdout +logger.recordStoreLogger.appenderRef.stdout.ref = console + +logger.catalogInitLogger.name=com.apple.foundationdb.relational.yamltests.YamlTestCatalogInitializer +logger.catalogInitLogger.level = WARN +logger.catalogInitLogger.appenderRefs = stdout +logger.catalogInitLogger.appenderRef.stdout.ref = console + +# Diagnostic: TRACE emits every clear()/addWriteConflictRange() with the affected key range and +# a captured stack trace. Extremely noisy — leave off unless you're hunting a range-conflict. +logger.instrumentedTxnLogger.name=com.apple.foundationdb.record.provider.foundationdb.InstrumentedTransaction +logger.instrumentedTxnLogger.level = TRACE +logger.instrumentedTxnLogger.appenderRefs = stdout +logger.instrumentedTxnLogger.appenderRef.stdout.ref = console From 689c32bd600d40e55f96b0b16f17e095a85b642e Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 9 Jul 2026 17:30:58 -0400 Subject: [PATCH 74/74] FIXME parallelism of 4 I've already seen a parallelism of 4 fail in CI, but on a more powerful laptop, it is needed to hit concurrency issues. --- gradle/testing.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/testing.gradle b/gradle/testing.gradle index 72a5410ca2..41fcc266a0 100644 --- a/gradle/testing.gradle +++ b/gradle/testing.gradle @@ -386,7 +386,7 @@ tasks.withType(Test).configureEach { task -> // at 5 minutes. The current theory is some thread pool is saturated and unable to unblock // itself. In the meantime, try a fixed 2 threads. task.systemProperty('junit.jupiter.execution.parallel.config.strategy', 'fixed') - task.systemProperty('junit.jupiter.execution.parallel.config.fixed.parallelism', '2') + task.systemProperty('junit.jupiter.execution.parallel.config.fixed.parallelism', '4') // We use worker_thread_pool rather than fork_join_pool because we heavily depend on ForkJoinPool for // FDB operations, which causes junit to incorrectly increase the number of concurrent tests to well // beyond what we want.