From 2efabfba291f2015c9884f9c09221d7991b749ab Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 30 Mar 2026 14:04:46 -0400 Subject: [PATCH 01/25] First pass at multi-cluster support for yamsql --- .../foundationdb/relational/server/FRL.java | 14 +++- .../yamltests/ConnectionTarget.java | 66 +++++++++++++++++++ .../yamltests/YamlConnectionFactory.java | 17 +++++ .../YamlConnectionFactoryWithOptions.java | 7 ++ .../yamltests/YamlExecutionContext.java | 29 ++++++++ .../yamltests/block/ConnectedBlock.java | 18 ++--- .../yamltests/block/SetupBlock.java | 17 ++--- .../relational/yamltests/block/TestBlock.java | 8 +-- .../yamltests/configs/EmbeddedConfig.java | 31 ++++++++- .../EmbeddedYamlConnectionFactory.java | 42 ++++++++++++ 10 files changed, 225 insertions(+), 24 deletions(-) create mode 100644 yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java 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 96f3fdd2d4..d526cee023 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 @@ -91,6 +91,10 @@ public FRL(@Nonnull Options options) throws RelationalException { } public FRL(@Nonnull Options options, @Nullable String clusterFile) throws RelationalException { + this(options, clusterFile, true); + } + + public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean registerDriver) throws RelationalException { final FDBDatabase fdbDb = FDBDatabaseFactory.instance().getDatabase(clusterFile); final Long asyncToSyncTimeout = options.getOption(Options.Name.ASYNC_OPERATIONS_TIMEOUT_MILLIS); if (asyncToSyncTimeout > 0) { @@ -130,13 +134,19 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile) throws Relati .setTertiarySize(options.getOption(Options.Name.PLAN_CACHE_TERTIARY_MAX_ENTRIES)) .build())); - DriverManager.registerDriver(this.registeredDriver); - this.registeredJDBCEmbedDriver = true; + if (registerDriver) { + DriverManager.registerDriver(this.registeredDriver); + this.registeredJDBCEmbedDriver = true; + } } catch (SQLException ve) { throw new RelationalException(ve); } } + public RelationalDriver getDriver() { + return registeredDriver; + } + @SuppressWarnings("AbbreviationAsWordInName") // allow JDBCURI, though perhaps we should update this to make it clearer private static String createEmbeddedJDBCURI(String database, String schema) { return EmbeddedRelationalDriver.JDBC_URL_PREFIX + database + (schema != null ? "?schema=" + schema : ""); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java new file mode 100644 index 0000000000..61223e37a9 --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java @@ -0,0 +1,66 @@ +/* + * ConnectionTarget.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 javax.annotation.Nonnull; +import java.net.URI; + +/** + * A resolved connection target consisting of a URI and a cluster index. + * + *

The cluster index identifies which FDB cluster to connect to. Index 0 is the default (and only) cluster + * in single-cluster configurations. Additional clusters can be specified in YAMSQL files using the map form + * of the {@code connect} directive: + *

{@code
+ * connect: { cluster: 1, uri: 0 }
+ * }
+ */ +public class ConnectionTarget { + @Nonnull + private final URI uri; + private final int clusterIndex; + + public ConnectionTarget(@Nonnull URI uri, int clusterIndex) { + this.uri = uri; + this.clusterIndex = clusterIndex; + } + + public ConnectionTarget(@Nonnull URI uri) { + this(uri, 0); + } + + @Nonnull + public URI getUri() { + return uri; + } + + public int getClusterIndex() { + return clusterIndex; + } + + @Override + public String toString() { + if (clusterIndex == 0) { + return uri.toString(); + } + return uri + " [cluster=" + clusterIndex + "]"; + } +} diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java index ed368fe181..e58e21dafa 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java @@ -43,6 +43,23 @@ public interface YamlConnectionFactory { */ YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException; + /** + * Convert a connection uri into an actual connection on a specific cluster. + * + * @param connectPath the path to connect to + * @param clusterIndex the cluster to connect to (0 is the default cluster) + * + * @return A new {@link RelationalConnection} for the given path on the specified cluster + * + * @throws SQLException if we cannot connect or the cluster index is not supported + */ + default YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { + if (clusterIndex != 0) { + throw new SQLException("This connection factory does not support multiple clusters (requested cluster " + clusterIndex + ")"); + } + return getNewConnection(connectPath); + } + /** * The versions that the connection has, other than the current code. *

diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java index ff96fb2223..2d1522f984 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java @@ -48,6 +48,13 @@ public YamlConnection getNewConnection(@Nonnull final URI connectPath) throws SQ return connection; } + @Override + public YamlConnection getNewConnection(@Nonnull final URI connectPath, int clusterIndex) throws SQLException { + final var connection = underlying.getNewConnection(connectPath, clusterIndex); + connection.setConnectionOptions(options); + return connection; + } + @Override public Set getVersionsUnderTest() { return underlying.getVersionsUnderTest(); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java index ade3113c7e..f85717f6f8 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java @@ -302,7 +302,36 @@ public void registerConnectionURI(@Nonnull YamlReference.YamlResource resource, * @return a valid connection URI */ public URI inferConnectionURI(@Nonnull final YamlReference.YamlResource resource, @Nullable Object connectObject) { + return inferConnectionTarget(resource, connectObject).getUri(); + } + + /** + * Infers the connection target (URI and cluster index) for a block. + *
+ * Supports all the forms of {@link #inferConnectionURI}, plus a map form for specifying the cluster: + *

{@code
+     * connect: { cluster: 1, uri: 0 }
+     * connect: { cluster: 1 }
+     * }
+ * + * @param connectObject can be {@code null}, an {@link Integer}, a {@link String}, or a {@link Map} with + * optional {@code cluster} and {@code uri} keys. + * + * @return a valid connection target + */ + public ConnectionTarget inferConnectionTarget(@Nonnull final YamlReference.YamlResource resource, @Nullable Object connectObject) { Assert.thatUnchecked(registeredResources.contains(resource), "A YamlResource should be registered before registering available connection URIs"); + if (connectObject instanceof Map) { + final Map connectMap = CustomYamlConstructor.LinedObject.unlineKeys(Matchers.map(connectObject, "connect")); + final int clusterIndex = connectMap.containsKey("cluster") + ? ((Number) connectMap.get("cluster")).intValue() : 0; + final Object uriSpec = connectMap.getOrDefault("uri", null); + return new ConnectionTarget(resolveConnectionURI(resource, uriSpec), clusterIndex); + } + return new ConnectionTarget(resolveConnectionURI(resource, connectObject), 0); + } + + private URI resolveConnectionURI(@Nonnull final YamlReference.YamlResource resource, @Nullable Object connectObject) { if (connectObject == null) { return getConnectionFromConnectionURIList(resource, true, -1, true); } else if (connectObject instanceof Integer) { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/ConnectedBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/ConnectedBlock.java index 5faf7e8b68..575e2d829a 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/ConnectedBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/ConnectedBlock.java @@ -20,6 +20,7 @@ package com.apple.foundationdb.relational.yamltests.block; +import com.apple.foundationdb.relational.yamltests.ConnectionTarget; import com.apple.foundationdb.relational.yamltests.YamlReference; import com.apple.foundationdb.relational.yamltests.YamlConnection; import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; @@ -28,7 +29,6 @@ import org.apache.logging.log4j.Logger; import javax.annotation.Nonnull; -import java.net.URI; import java.sql.SQLException; import java.util.Collection; import java.util.List; @@ -54,14 +54,14 @@ public abstract class ConnectedBlock extends ReferencedBlock implements Block { @Nonnull YamlExecutionContext executionContext; @Nonnull - private final URI connectionURI; + private final ConnectionTarget connectionTarget; @Nonnull final List> executables; - ConnectedBlock(@Nonnull YamlReference reference, @Nonnull List> executables, @Nonnull URI connectionURI, @Nonnull YamlExecutionContext executionContext) { + ConnectedBlock(@Nonnull YamlReference reference, @Nonnull List> executables, @Nonnull ConnectionTarget connectionTarget, @Nonnull YamlExecutionContext executionContext) { super(reference); this.executables = executables; - this.connectionURI = connectionURI; + this.connectionTarget = connectionTarget; this.executionContext = executionContext; } @@ -75,14 +75,14 @@ protected final void executeExecutables(@Nonnull Collection consumer) { - logger.debug("🚠 Connecting to database: `{}`", connectionURI); - try (var connection = executionContext.getConnectionFactory().getNewConnection(connectionURI)) { - logger.debug("✅ Connected to database: `{}`", connectionURI); + logger.debug("🚠 Connecting to database: `{}`", connectionTarget); + try (var connection = executionContext.getConnectionFactory().getNewConnection(connectionTarget.getUri(), connectionTarget.getClusterIndex())) { + logger.debug("✅ Connected to database: `{}`", connectionTarget); consumer.accept(connection); } catch (SQLException sqle) { throw YamlExecutionContext.wrapContext(sqle, - () -> String.format(Locale.ROOT, "‼️ Error connecting to the database `%s` in block at %s", connectionURI, getReference()), - "connection [" + connectionURI + "]", getReference()); + () -> String.format(Locale.ROOT, "‼️ Error connecting to the database `%s` in block at %s", connectionTarget, getReference()), + "connection [" + connectionTarget + "]", getReference()); } } 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 d6a0b03d4f..b56aec4d02 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 @@ -30,8 +30,9 @@ import com.apple.foundationdb.relational.yamltests.command.Command; import com.apple.foundationdb.relational.yamltests.command.QueryCommand; +import com.apple.foundationdb.relational.yamltests.ConnectionTarget; + import javax.annotation.Nonnull; -import java.net.URI; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -56,9 +57,9 @@ public class SetupBlock extends ConnectedBlock { public static final String SETUP_BLOCK = "setup"; - protected SetupBlock(@Nonnull YamlReference reference, @Nonnull List> executables, @Nonnull URI connectionURI, + protected SetupBlock(@Nonnull YamlReference reference, @Nonnull List> executables, @Nonnull ConnectionTarget connectionTarget, @Nonnull YamlExecutionContext executionContext) { - super(reference, executables, connectionURI, executionContext); + super(reference, executables, connectionTarget, executionContext); } @Override @@ -99,16 +100,16 @@ public static List parse(@Nonnull YamlReference reference, @Nonnull Objec final var resolvedCommand = Objects.requireNonNull(Command.parse(reference.getResource(), List.of(step), "unnamed-setup-block", executionContext)); executables.add(createSetupExecutable(resolvedCommand, connectionOptions)); } - return List.of(new ManualSetupBlock(reference, executables, executionContext.inferConnectionURI(reference.getResource(), setupMap.getOrDefault(BLOCK_CONNECT, null)), + return List.of(new ManualSetupBlock(reference, executables, executionContext.inferConnectionTarget(reference.getResource(), setupMap.getOrDefault(BLOCK_CONNECT, null)), executionContext)); } catch (Throwable e) { throw YamlExecutionContext.wrapContext(e, () -> "‼️ Error parsing the setup block at " + reference, SETUP_BLOCK, reference); } } - private ManualSetupBlock(@Nonnull YamlReference reference, @Nonnull List> executables, @Nonnull URI connectionURI, + private ManualSetupBlock(@Nonnull YamlReference reference, @Nonnull List> executables, @Nonnull ConnectionTarget connectionTarget, @Nonnull YamlExecutionContext executionContext) { - super(reference, executables, connectionURI, executionContext); + super(reference, executables, connectionTarget, executionContext); } @Nonnull @@ -159,7 +160,7 @@ public static List parse(@Nonnull final YamlReference reference, @Nonnull private SchemaTemplateBlock(@Nonnull final YamlReference reference, @Nonnull final String schemaTemplateName, @Nonnull final String databaseName, @Nonnull List> executables, @Nonnull YamlExecutionContext executionContext) { - super(reference, executables, executionContext.inferConnectionURI(reference.getResource(), 0), executionContext); + super(reference, executables, executionContext.inferConnectionTarget(reference.getResource(), 0), executionContext); this.finalizingBlocks = List.of(DestructTemplateBlock.withDatabaseAndSchema(reference, executionContext, schemaTemplateName, databaseName)); } @@ -192,7 +193,7 @@ public static DestructTemplateBlock withDatabaseAndSchema(@Nonnull final YamlRef } private DestructTemplateBlock(@Nonnull final YamlReference reference, @Nonnull List> executables, @Nonnull YamlExecutionContext executionContext) { - super(reference, executables, executionContext.inferConnectionURI(reference.getResource(), 0), executionContext); + super(reference, executables, executionContext.inferConnectionTarget(reference.getResource(), 0), executionContext); } } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/TestBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/TestBlock.java index bd60d3b626..71404a850a 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/TestBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/TestBlock.java @@ -22,6 +22,7 @@ import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.util.Assert; +import com.apple.foundationdb.relational.yamltests.ConnectionTarget; import com.apple.foundationdb.relational.yamltests.CustomYamlConstructor; import com.apple.foundationdb.relational.yamltests.Matchers; import com.apple.foundationdb.relational.yamltests.YamlReference; @@ -42,7 +43,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.net.URI; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; @@ -400,7 +400,7 @@ public static List parse(int blockNumber, @Nonnull final YamlReference re Assert.thatUnchecked(!executables.isEmpty(), "‼️ Test block at " + reference + " have no tests to execute"); return ImmutableList.of(new TestBlock(reference, blockName, queryCommands, executables, executableTestsWithCacheCheck, - executionContext.inferConnectionURI(reference.getResource(), testsMap.getOrDefault(BLOCK_CONNECT, null)), options, executionContext)); + executionContext.inferConnectionTarget(reference.getResource(), testsMap.getOrDefault(BLOCK_CONNECT, null)), options, executionContext)); } catch (Throwable e) { throw executionContext.wrapContext(e, () -> "‼️ Error parsing the test block at " + reference, TEST_BLOCK, reference); } @@ -408,9 +408,9 @@ public static List parse(int blockNumber, @Nonnull final YamlReference re private TestBlock(@Nonnull final YamlReference reference, @Nonnull final String blockName, @Nonnull final List queryCommands, @Nonnull final List> executables, - @Nonnull final List> executableTestsWithCacheCheck, @Nonnull final URI connectionURI, + @Nonnull final List> executableTestsWithCacheCheck, @Nonnull final ConnectionTarget connectionTarget, @Nonnull final TestBlockOptions options, @Nonnull final YamlExecutionContext executionContext) { - super(reference, executables, connectionURI, executionContext); + super(reference, executables, connectionTarget, executionContext); this.blockName = blockName; this.queryCommands = queryCommands; this.options = options; 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 36f1fa6c5e..76fb59554f 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 @@ -25,9 +25,13 @@ import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; import com.apple.foundationdb.relational.yamltests.connectionfactory.EmbeddedYamlConnectionFactory; +import com.apple.foundationdb.test.FDBTestEnvironment; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; /** * Run directly against an instance of {@link FRL}. @@ -36,6 +40,8 @@ public class EmbeddedConfig implements YamlTestConfig { private FRL frl; @Nullable private final String clusterFile; + @Nonnull + private final List additionalClusterFrls = new ArrayList<>(); public EmbeddedConfig(@Nullable final String clusterFile) { this.clusterFile = clusterFile; @@ -50,10 +56,21 @@ public void beforeAll() throws Exception { .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 10) .build(); frl = new FRL(options, clusterFile); + + // Create drivers for additional clusters (without registering them in DriverManager) + for (final String otherClusterFile : FDBTestEnvironment.allClusterFiles()) { + if (!Objects.equals(otherClusterFile, clusterFile)) { + additionalClusterFrls.add(new FRL(options, otherClusterFile, false)); + } + } } @Override public void afterAll() throws Exception { + for (final FRL additionalFrl : additionalClusterFrls) { + additionalFrl.close(); + } + additionalClusterFrls.clear(); if (frl != null) { frl.close(); frl = null; @@ -62,7 +79,19 @@ public void afterAll() throws Exception { @Override public YamlConnectionFactory createConnectionFactory() { - return new EmbeddedYamlConnectionFactory(clusterFile); + final List additionalDrivers = new ArrayList<>(); + final List allClusterFiles = FDBTestEnvironment.allClusterFiles(); + for (int i = 0; i < additionalClusterFrls.size(); i++) { + // Find the cluster file for this additional FRL + final String otherClusterFile = allClusterFiles.stream() + .filter(cf -> !Objects.equals(cf, clusterFile)) + .skip(i) + .findFirst() + .orElseThrow(); + additionalDrivers.add(new EmbeddedYamlConnectionFactory.ClusterDriver( + additionalClusterFrls.get(i).getDriver(), otherClusterFile)); + } + return new EmbeddedYamlConnectionFactory(clusterFile, additionalDrivers); } @Override diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java index bf9b19674c..ad31f22957 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java @@ -20,6 +20,8 @@ package com.apple.foundationdb.relational.yamltests.connectionfactory; +import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.yamltests.SimpleYamlConnection; import com.apple.foundationdb.relational.yamltests.YamlConnection; import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; @@ -29,13 +31,21 @@ import java.net.URI; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.List; import java.util.Set; public class EmbeddedYamlConnectionFactory implements YamlConnectionFactory { private final String clusterFile; + @Nonnull + private final List additionalClusterDrivers; public EmbeddedYamlConnectionFactory(String clusterFile) { + this(clusterFile, List.of()); + } + + public EmbeddedYamlConnectionFactory(String clusterFile, @Nonnull List additionalClusterDrivers) { this.clusterFile = clusterFile; + this.additionalClusterDrivers = additionalClusterDrivers; } @Override @@ -44,9 +54,41 @@ public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLExcep SemanticVersion.current(), "Embedded", clusterFile); } + @Override + public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { + if (clusterIndex == 0) { + return getNewConnection(connectPath); + } + final int idx = clusterIndex - 1; + if (idx >= additionalClusterDrivers.size()) { + throw new SQLException("Cluster index " + clusterIndex + " not available (only " + + (additionalClusterDrivers.size() + 1) + " clusters configured)"); + } + final ClusterDriver clusterDriver = additionalClusterDrivers.get(idx); + return new SimpleYamlConnection( + clusterDriver.driver.connect(connectPath, Options.NONE), + SemanticVersion.current(), + "Embedded[cluster=" + clusterIndex + "]", + clusterDriver.clusterFile); + } + @Override public Set getVersionsUnderTest() { return Set.of(SemanticVersion.current()); } + /** + * A driver associated with its cluster file, for additional (non-primary) clusters. + */ + public static class ClusterDriver { + @Nonnull + final RelationalDriver driver; + @Nonnull + final String clusterFile; + + public ClusterDriver(@Nonnull RelationalDriver driver, @Nonnull String clusterFile) { + this.driver = driver; + this.clusterFile = clusterFile; + } + } } From f4683ffc60528d82e0bca761336533f1940db2cc Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 30 Mar 2026 16:58:39 -0400 Subject: [PATCH 02/25] Add a smoke test to make sure the multi-cluster works --- .../yamltests/YamlConnectionFactory.java | 9 ++ .../YamlConnectionFactoryWithOptions.java | 5 + .../yamltests/block/PreambleBlock.java | 10 ++ .../EmbeddedYamlConnectionFactory.java | 5 + .../src/test/java/YamlIntegrationTests.java | 5 + .../resources/multi-cluster-isolation.yamsql | 110 ++++++++++++++++++ 6 files changed, 144 insertions(+) create mode 100644 yaml-tests/src/test/resources/multi-cluster-isolation.yamsql diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java index e58e21dafa..d982a5973c 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java @@ -83,4 +83,13 @@ default YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterInd default boolean isMultiServer() { return false; } + + /** + * Returns the number of clusters available for testing. + * + * @return the number of available clusters (1 means only the default cluster) + */ + default int getAvailableClusterCount() { + return 1; + } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java index 2d1522f984..c1b6c5976a 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java @@ -60,6 +60,11 @@ public Set getVersionsUnderTest() { return underlying.getVersionsUnderTest(); } + @Override + public int getAvailableClusterCount() { + return underlying.getAvailableClusterCount(); + } + @Override public boolean isMultiServer() { return underlying.isMultiServer(); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/PreambleBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/PreambleBlock.java index 8bf605d2f3..40d175fe12 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/PreambleBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/PreambleBlock.java @@ -42,6 +42,7 @@ public class PreambleBlock extends SupportBlock { public static final String OPTIONS = "options"; public static final String PREAMBLE_BLOCK_SUPPORTED_VERSION = "supported_version"; + public static final String PREAMBLE_BLOCK_REQUIRED_CLUSTERS = "required_clusters"; public static final String PREAMBLE_BLOCK_CONNECTION_OPTIONS = "connection_options"; private static final Logger logger = LogManager.getLogger(PreambleBlock.class); @@ -62,6 +63,15 @@ public static List parse(@Nonnull final Object document, @Nonnull final Y } Assumptions.assumeTrue(check.isSupported(), check.getMessage()); } + + // read the required_clusters option, and skip the test if not enough clusters are available. + if (optionsMap.containsKey(PREAMBLE_BLOCK_REQUIRED_CLUSTERS)) { + final int requiredClusters = ((Number) optionsMap.get(PREAMBLE_BLOCK_REQUIRED_CLUSTERS)).intValue(); + final int availableClusters = executionContext.getConnectionFactory().getAvailableClusterCount(); + Assumptions.assumeTrue(availableClusters >= requiredClusters, + "Test requires " + requiredClusters + " clusters but only " + availableClusters + " available"); + } + var connectionOptions = Options.none(); if (optionsMap.containsKey(PREAMBLE_BLOCK_CONNECTION_OPTIONS)) { connectionOptions = TestBlock.TestBlockOptions.parseConnectionOptions(Matchers.map(optionsMap.get(PREAMBLE_BLOCK_CONNECTION_OPTIONS))); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java index ad31f22957..62a68370f6 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java @@ -72,6 +72,11 @@ public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterInde clusterDriver.clusterFile); } + @Override + public int getAvailableClusterCount() { + return 1 + additionalClusterDrivers.size(); + } + @Override public Set getVersionsUnderTest() { return Set.of(SemanticVersion.current()); diff --git a/yaml-tests/src/test/java/YamlIntegrationTests.java b/yaml-tests/src/test/java/YamlIntegrationTests.java index aff182c251..527555c6fa 100644 --- a/yaml-tests/src/test/java/YamlIntegrationTests.java +++ b/yaml-tests/src/test/java/YamlIntegrationTests.java @@ -222,6 +222,11 @@ public void maxRows(YamlTest.Runner runner) throws Exception { runner.runYamsql("maxRows.yamsql"); } + @TestTemplate + public void multiClusterIsolation(YamlTest.Runner runner) throws Exception { + runner.runYamsql("multi-cluster-isolation.yamsql"); + } + @TestTemplate public void indexDdl(YamlTest.Runner runner) throws Exception { runner.runYamsql("index-ddl.yamsql"); diff --git a/yaml-tests/src/test/resources/multi-cluster-isolation.yamsql b/yaml-tests/src/test/resources/multi-cluster-isolation.yamsql new file mode 100644 index 0000000000..674a5f50e0 --- /dev/null +++ b/yaml-tests/src/test/resources/multi-cluster-isolation.yamsql @@ -0,0 +1,110 @@ +# +# multi-cluster-isolation.yamsql +# +# 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. + +# Verifies that data written to one cluster is not visible on another cluster. +# Requires at least 2 clusters; skipped otherwise. +--- +options: + required_clusters: 2 +--- +# Cleanup cluster 0 +setup: + connect: { cluster: 0, uri: "jdbc:embed:/__SYS?schema=CATALOG" } + steps: + - query: drop database if exists /FRL/MCI_DB + - query: drop schema template if exists MCI_TEMPLATE +--- +# Cleanup cluster 1 +setup: + connect: { cluster: 1, uri: "jdbc:embed:/__SYS?schema=CATALOG" } + steps: + - query: drop database if exists /FRL/MCI_DB + - query: drop schema template if exists MCI_TEMPLATE +--- +# Create template, database, and schema on cluster 0 +setup: + connect: { cluster: 0, uri: "jdbc:embed:/__SYS?schema=CATALOG" } + steps: + - query: create schema template MCI_TEMPLATE create table T1(ID bigint, NAME string, primary key(ID)) + - query: create database /FRL/MCI_DB + - query: create schema /FRL/MCI_DB/S1 with template MCI_TEMPLATE +--- +# Insert data on cluster 0 +setup: + connect: { cluster: 0, uri: "jdbc:embed:/FRL/MCI_DB?schema=S1" } + steps: + - query: insert into T1 values (1, 'cluster0_row1') + - query: insert into T1 values (2, 'cluster0_row2') +--- +# Verify cluster 1 does not have the database +test_block: + connect: { cluster: 1, uri: "jdbc:embed:/__SYS?schema=CATALOG" } + preset: single_repetition_ordered + tests: + - + - query: select count(*) from "DATABASES" where database_id = '/FRL/MCI_DB' + - result: [{0}] +--- +# Create same template, database, and schema on cluster 1 +setup: + connect: { cluster: 1, uri: "jdbc:embed:/__SYS?schema=CATALOG" } + steps: + - query: create schema template MCI_TEMPLATE create table T1(ID bigint, NAME string, primary key(ID)) + - query: create database /FRL/MCI_DB + - query: create schema /FRL/MCI_DB/S1 with template MCI_TEMPLATE +--- +# Insert different data on cluster 1 +setup: + connect: { cluster: 1, uri: "jdbc:embed:/FRL/MCI_DB?schema=S1" } + steps: + - query: insert into T1 values (10, 'cluster1_row1') + - query: insert into T1 values (20, 'cluster1_row2') + - query: insert into T1 values (30, 'cluster1_row3') +--- +# Verify cluster 1 has only its own data +test_block: + connect: { cluster: 1, uri: "jdbc:embed:/FRL/MCI_DB?schema=S1" } + preset: single_repetition_ordered + tests: + - + - query: select * from T1 + - result: [{ID: 10, NAME: 'cluster1_row1'}, {ID: 20, NAME: 'cluster1_row2'}, {ID: 30, NAME: 'cluster1_row3'}] +--- +# Verify cluster 0 still has only its own data +test_block: + connect: { cluster: 0, uri: "jdbc:embed:/FRL/MCI_DB?schema=S1" } + preset: single_repetition_ordered + tests: + - + - query: select * from T1 + - result: [{ID: 1, NAME: 'cluster0_row1'}, {ID: 2, NAME: 'cluster0_row2'}] +--- +# Cleanup cluster 0 +setup: + connect: { cluster: 0, uri: "jdbc:embed:/__SYS?schema=CATALOG" } + steps: + - query: drop database /FRL/MCI_DB + - query: drop schema template MCI_TEMPLATE +--- +# Cleanup cluster 1 +setup: + connect: { cluster: 1, uri: "jdbc:embed:/__SYS?schema=CATALOG" } + steps: + - query: drop database /FRL/MCI_DB + - query: drop schema template MCI_TEMPLATE From b0986b2d232c5a825bb4f907b3d116742559fb21 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 31 Mar 2026 09:59:39 -0400 Subject: [PATCH 03/25] Implement multi-cluster support in multi-server (I think) --- .../MultiServerConnectionFactory.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java index c0e1801ca4..cb0bfb15ef 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java @@ -101,6 +101,16 @@ public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLExcep } } + @Override + public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { + if (connectionSelectionPolicy == ConnectionSelectionPolicy.DEFAULT) { + return defaultFactory.getNewConnection(connectPath, clusterIndex); + } else { + return new MultiServerConnection(connectionSelectionPolicy, getNextConnectionNumber(), + defaultFactory.getNewConnection(connectPath, clusterIndex), alternateConnections(connectPath, clusterIndex)); + } + } + @Override public Set getVersionsUnderTest() { return versionsUnderTest; @@ -111,6 +121,15 @@ public boolean isMultiServer() { return true; } + @Override + public int getAvailableClusterCount() { + int min = defaultFactory.getAvailableClusterCount(); + for (final YamlConnectionFactory factory : alternateFactories) { + min = Math.min(min, factory.getAvailableClusterCount()); + } + return min; + } + @Nonnull private List alternateConnections(URI connectPath) { return alternateFactories.stream().map(factory -> { @@ -122,6 +141,17 @@ private List alternateConnections(URI connectPath) { }).collect(Collectors.toList()); } + @Nonnull + private List alternateConnections(URI connectPath, int clusterIndex) { + return alternateFactories.stream().map(factory -> { + try { + return factory.getNewConnection(connectPath, clusterIndex); + } catch (SQLException e) { + throw new IllegalStateException("Failed to create a connection", e); + } + }).collect(Collectors.toList()); + } + /** * Increment and return the next connection's initialConnection number. * This allows us to better distribute the connections positions as connections are created per query in the tests From e450ac724198c6828300141eb66188022b1419ab Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Tue, 31 Mar 2026 16:36:22 -0400 Subject: [PATCH 04/25] Improve multi-cluster testing It now will run all the test types, but non-0 clusters always go to the embedded or jdbc connection --- .../foundationdb/relational/server/FRL.java | 6 +- .../configs/JDBCInProcessConfig.java | 9 +++ .../configs/JDBCMultiServerConfig.java | 44 ++++++++++++++- .../JDBCInProcessYamlConnectionFactory.java | 55 +++++++++++++++++-- .../MultiServerConnectionFactory.java | 27 ++------- 5 files changed, 110 insertions(+), 31 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 d526cee023..3c485a5785 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 @@ -214,8 +214,7 @@ public Response execute(String database, String schema, String sql, List + * When multiple cluster files are available, starts one in-process server per additional cluster so that + * multi-cluster tests (using {@code connect: { cluster: N }}) can route to the correct cluster. */ public class JDBCMultiServerConfig extends JDBCInProcessConfig { private final ExternalServer externalServer; private final int initialConnection; + @Nonnull + private final List additionalClusterServers = new ArrayList<>(); public JDBCMultiServerConfig(final int initialConnection, ExternalServer externalServer) { this(initialConnection, externalServer, null); @@ -50,14 +61,45 @@ public JDBCMultiServerConfig(final int initialConnection, ExternalServer externa @Override public void beforeAll() throws Exception { super.beforeAll(); + // Start one in-process server per additional cluster file + for (final String otherClusterFile : FDBTestEnvironment.allClusterFiles()) { + if (!Objects.equals(otherClusterFile, getClusterFile())) { + final InProcessRelationalServer server = new InProcessRelationalServer(otherClusterFile).start(); + additionalClusterServers.add(server); + } + } + } + + @Override + public void afterAll() throws Exception { + for (final InProcessRelationalServer server : additionalClusterServers) { + server.close(); + } + additionalClusterServers.clear(); + super.afterAll(); } @Override public YamlConnectionFactory createConnectionFactory() { + // Build the JDBCInProcessYamlConnectionFactory with multi-cluster support + final List clusterServers = new ArrayList<>(); + final List allClusterFiles = FDBTestEnvironment.allClusterFiles(); + for (int i = 0; i < additionalClusterServers.size(); i++) { + final String otherClusterFile = allClusterFiles.stream() + .filter(cf -> !Objects.equals(cf, getClusterFile())) + .skip(i) + .findFirst() + .orElseThrow(); + clusterServers.add(new JDBCInProcessYamlConnectionFactory.ClusterServer( + additionalClusterServers.get(i), otherClusterFile)); + } + final JDBCInProcessYamlConnectionFactory jdbcFactory = + new JDBCInProcessYamlConnectionFactory(getServer(), getClusterFile(), clusterServers); + return new MultiServerConnectionFactory( MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE, initialConnection, - super.createConnectionFactory(), + jdbcFactory, List.of(new ExternalServerYamlConnectionFactory(externalServer))); } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java index 6bd680dc46..584d471f6c 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java @@ -34,34 +34,81 @@ import java.net.URI; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.List; import java.util.Set; public class JDBCInProcessYamlConnectionFactory implements YamlConnectionFactory { private static final Logger LOG = LogManager.getLogger(JDBCInProcessYamlConnectionFactory.class); private final InProcessRelationalServer server; private final String clusterFile; + @Nonnull + private final List additionalClusterServers; public JDBCInProcessYamlConnectionFactory(final InProcessRelationalServer server, final String clusterFile) { + this(server, clusterFile, List.of()); + } + + public JDBCInProcessYamlConnectionFactory(final InProcessRelationalServer server, final String clusterFile, + @Nonnull List additionalClusterServers) { this.server = server; this.clusterFile = clusterFile; + this.additionalClusterServers = additionalClusterServers; } @Override public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException { - // Add name of the in-process running server to the connectPath. - URI connectPathPlusServerName = JDBCURI.addQueryParameter(connectPath, JDBCURI.INPROCESS_URI_QUERY_SERVERNAME_KEY, server.getServerName()); + return createConnection(connectPath, server, clusterFile); + } + + @Override + public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { + if (clusterIndex == 0) { + return getNewConnection(connectPath); + } + final int idx = clusterIndex - 1; + if (idx >= additionalClusterServers.size()) { + throw new SQLException("Cluster index " + clusterIndex + " not available (only " + + (additionalClusterServers.size() + 1) + " clusters configured)"); + } + final ClusterServer clusterServer = additionalClusterServers.get(idx); + return createConnection(connectPath, clusterServer.server, clusterServer.clusterFile); + } + + @Override + public int getAvailableClusterCount() { + return 1 + additionalClusterServers.size(); + } + + private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull InProcessRelationalServer targetServer, + @Nonnull String targetClusterFile) throws SQLException { + URI connectPathPlusServerName = JDBCURI.addQueryParameter(connectPath, JDBCURI.INPROCESS_URI_QUERY_SERVERNAME_KEY, targetServer.getServerName()); String uriStr = connectPathPlusServerName.toString().replaceFirst("embed:", "relational://"); if (LOG.isInfoEnabled()) { LOG.info(KeyValueLogMessage.of("Rewrote connection string for in-process server", "original", connectPath, "rewritten", uriStr, - "server", server.getServerName())); + "server", targetServer.getServerName())); } - return new SimpleYamlConnection(DriverManager.getConnection(uriStr), SemanticVersion.current(), "JDBC In-Process", clusterFile); + return new SimpleYamlConnection(DriverManager.getConnection(uriStr), SemanticVersion.current(), "JDBC In-Process", targetClusterFile); } @Override public Set getVersionsUnderTest() { return Set.of(SemanticVersion.current()); } + + /** + * A server associated with its cluster file, for additional (non-primary) clusters. + */ + public static class ClusterServer { + @Nonnull + final InProcessRelationalServer server; + @Nonnull + final String clusterFile; + + public ClusterServer(@Nonnull InProcessRelationalServer server, @Nonnull String clusterFile) { + this.server = server; + this.clusterFile = clusterFile; + } + } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java index cb0bfb15ef..f187ce5d56 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java @@ -103,12 +103,10 @@ public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLExcep @Override public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { - if (connectionSelectionPolicy == ConnectionSelectionPolicy.DEFAULT) { - return defaultFactory.getNewConnection(connectPath, clusterIndex); - } else { - return new MultiServerConnection(connectionSelectionPolicy, getNextConnectionNumber(), - defaultFactory.getNewConnection(connectPath, clusterIndex), alternateConnections(connectPath, clusterIndex)); - } + // For multi-cluster connections, delegate directly to the default factory which has multi-cluster support. + // Alternation is not applied here because alternate factories (e.g. external servers) may not support + // all clusters. + return defaultFactory.getNewConnection(connectPath, clusterIndex); } @Override @@ -123,11 +121,7 @@ public boolean isMultiServer() { @Override public int getAvailableClusterCount() { - int min = defaultFactory.getAvailableClusterCount(); - for (final YamlConnectionFactory factory : alternateFactories) { - min = Math.min(min, factory.getAvailableClusterCount()); - } - return min; + return defaultFactory.getAvailableClusterCount(); } @Nonnull @@ -141,17 +135,6 @@ private List alternateConnections(URI connectPath) { }).collect(Collectors.toList()); } - @Nonnull - private List alternateConnections(URI connectPath, int clusterIndex) { - return alternateFactories.stream().map(factory -> { - try { - return factory.getNewConnection(connectPath, clusterIndex); - } catch (SQLException e) { - throw new IllegalStateException("Failed to create a connection", e); - } - }).collect(Collectors.toList()); - } - /** * Increment and return the next connection's initialConnection number. * This allows us to better distribute the connections positions as connections are created per query in the tests From b99da6742ca538a2e99f66dd8fd00471d609662c Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 1 Apr 2026 11:18:03 -0400 Subject: [PATCH 05/25] Run a separate server for each cluster so that mixed-mode still works --- .../yamltests/YamlTestExtension.java | 49 ++++++++++++++++--- .../configs/JDBCMultiServerConfig.java | 13 ++++- .../ExternalServerYamlConnectionFactory.java | 39 +++++++++++++-- .../MultiServerConnectionFactory.java | 22 +++++++-- 4 files changed, 106 insertions(+), 17 deletions(-) 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 673dc8a002..6f8113ccae 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 @@ -51,8 +51,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.File; +import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -66,6 +69,9 @@ public class YamlTestExtension implements TestTemplateInvocationContextProvider, private List testConfigs; private List maintainConfigs; private List servers; + /** Additional external servers for non-primary cluster files, keyed by the primary server they belong to. */ + @Nonnull + private final Map> additionalClusterExternalServers = new HashMap<>(); @Nullable private final String clusterFile; private final boolean includeMethodInDescriptions; @@ -108,10 +114,24 @@ public void beforeAll(final ExtensionContext context) throws Exception { // not a likely scenario. Assertions.assertFalse(jars.isEmpty(), "There are no external servers available to run"); servers = new ArrayList<>(); + List allExternalServers = new ArrayList<>(); + final List otherClusterFiles = FDBTestEnvironment.allClusterFiles().stream() + .filter(cf -> !Objects.equals(cf, clusterFile)) + .collect(Collectors.toList()); for (File jar : jars) { - servers.add(new ExternalServer(jar, clusterFile)); + ExternalServer primaryServer = new ExternalServer(jar, clusterFile); + servers.add(primaryServer); + allExternalServers.add(primaryServer); + // Create additional external servers for non-primary cluster files + List additionalServers = new ArrayList<>(); + for (String otherClusterFile : otherClusterFiles) { + ExternalServer additionalServer = new ExternalServer(jar, otherClusterFile); + additionalServers.add(additionalServer); + allExternalServers.add(additionalServer); + } + additionalClusterExternalServers.put(primaryServer, additionalServers); } - ExternalServer.startMultiple(servers); + ExternalServer.startMultiple(allExternalServers); final boolean mixedModeOnly = Boolean.parseBoolean(System.getProperty("tests.mixedModeOnly", "false")); final boolean singleExternalVersionOnly = Boolean.parseBoolean(System.getProperty("tests.singleVersion", "false")); Stream localTestingConfigs = localConfigs(mixedModeOnly, singleExternalVersionOnly); @@ -145,12 +165,15 @@ private Stream externalServerConfigs(final boolean singleExterna Stream.of(new ExternalMultiServerConfig(0, server, server), new ForceContinuations(new ExternalMultiServerConfig(0, server, server)))); } else { - return servers.stream().flatMap(server -> + return servers.stream().flatMap(server -> { + List additionalServers = + additionalClusterExternalServers.getOrDefault(server, List.of()); // (4 configs for each server available) - Stream.of(new JDBCMultiServerConfig(0, server, clusterFile), - new ForceContinuations(new JDBCMultiServerConfig(0, server, clusterFile)), - new JDBCMultiServerConfig(1, server, clusterFile), - new ForceContinuations(new JDBCMultiServerConfig(1, server, clusterFile)))); + return Stream.of(new JDBCMultiServerConfig(0, server, clusterFile, additionalServers), + new ForceContinuations(new JDBCMultiServerConfig(0, server, clusterFile, additionalServers)), + new JDBCMultiServerConfig(1, server, clusterFile, additionalServers), + new ForceContinuations(new JDBCMultiServerConfig(1, server, clusterFile, additionalServers))); + }); } } @@ -181,6 +204,18 @@ public void afterAll(final ExtensionContext context) throws Exception { } } } + for (List additionalServers : additionalClusterExternalServers.values()) { + for (ExternalServer server : additionalServers) { + try { + server.stop(); + } catch (Exception ex) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to stop additional cluster server " + server.getVersion() + " on " + server.getPort()); + } + } + } + } + additionalClusterExternalServers.clear(); } if (exception.isPresent()) { throw exception.get(); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java index e497722635..a67906891c 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java @@ -45,17 +45,26 @@ public class JDBCMultiServerConfig extends JDBCInProcessConfig { private final ExternalServer externalServer; private final int initialConnection; @Nonnull + private final List additionalClusterExternalServers; + @Nonnull private final List additionalClusterServers = new ArrayList<>(); public JDBCMultiServerConfig(final int initialConnection, ExternalServer externalServer) { - this(initialConnection, externalServer, null); + this(initialConnection, externalServer, null, List.of()); } public JDBCMultiServerConfig(final int initialConnection, ExternalServer externalServer, @Nullable final String clusterFile) { + this(initialConnection, externalServer, clusterFile, List.of()); + } + + public JDBCMultiServerConfig(final int initialConnection, ExternalServer externalServer, + @Nullable final String clusterFile, + @Nonnull List additionalClusterExternalServers) { super(clusterFile); this.initialConnection = initialConnection; this.externalServer = externalServer; + this.additionalClusterExternalServers = additionalClusterExternalServers; } @Override @@ -100,7 +109,7 @@ public YamlConnectionFactory createConnectionFactory() { MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE, initialConnection, jdbcFactory, - List.of(new ExternalServerYamlConnectionFactory(externalServer))); + List.of(new ExternalServerYamlConnectionFactory(externalServer, additionalClusterExternalServers))); } @Override diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java index 2de163fcec..c7b3652928 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java @@ -34,29 +34,60 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.List; import java.util.Set; public class ExternalServerYamlConnectionFactory implements YamlConnectionFactory { private static final Logger LOG = LogManager.getLogger(ExternalServerYamlConnectionFactory.class); private final ExternalServer externalServer; + @Nonnull + private final List additionalClusterServers; public ExternalServerYamlConnectionFactory(final ExternalServer externalServer) { + this(externalServer, List.of()); + } + + public ExternalServerYamlConnectionFactory(final ExternalServer externalServer, + @Nonnull List additionalClusterServers) { this.externalServer = externalServer; + this.additionalClusterServers = additionalClusterServers; } @Override public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException { - String uriStr = connectPath.toString().replaceFirst("embed:", "relational://localhost:" + externalServer.getPort()); + return createConnection(connectPath, externalServer); + } + + @Override + public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { + if (clusterIndex == 0) { + return getNewConnection(connectPath); + } + final int idx = clusterIndex - 1; + if (idx >= additionalClusterServers.size()) { + throw new SQLException("Cluster index " + clusterIndex + " not available (only " + + (additionalClusterServers.size() + 1) + " clusters configured)"); + } + return createConnection(connectPath, additionalClusterServers.get(idx)); + } + + @Override + public int getAvailableClusterCount() { + return 1 + additionalClusterServers.size(); + } + + private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull ExternalServer server) throws SQLException { + String uriStr = connectPath.toString().replaceFirst("embed:", "relational://localhost:" + server.getPort()); if (LOG.isInfoEnabled()) { LOG.info(KeyValueLogMessage.of("Rewrote connection string for external server", "original", connectPath, "rewritten", uriStr, - "version", externalServer.getVersion())); + "version", server.getVersion())); } final Connection connection = DriverManager.getConnection(uriStr); - externalServer.validateConnectionVersion(connection); - return new SimpleYamlConnection(connection, externalServer.getVersion(), externalServer.getClusterFile()); + server.validateConnectionVersion(connection); + return new SimpleYamlConnection(connection, server.getVersion(), server.getClusterFile()); } @Override diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java index f187ce5d56..189f1067af 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java @@ -103,10 +103,13 @@ public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLExcep @Override public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { - // For multi-cluster connections, delegate directly to the default factory which has multi-cluster support. - // Alternation is not applied here because alternate factories (e.g. external servers) may not support - // all clusters. - return defaultFactory.getNewConnection(connectPath, clusterIndex); + if (connectionSelectionPolicy == ConnectionSelectionPolicy.DEFAULT) { + return defaultFactory.getNewConnection(connectPath, clusterIndex); + } else { + return new MultiServerConnection(connectionSelectionPolicy, getNextConnectionNumber(), + defaultFactory.getNewConnection(connectPath, clusterIndex), + alternateConnections(connectPath, clusterIndex)); + } } @Override @@ -135,6 +138,17 @@ private List alternateConnections(URI connectPath) { }).collect(Collectors.toList()); } + @Nonnull + private List alternateConnections(URI connectPath, int clusterIndex) { + return alternateFactories.stream().map(factory -> { + try { + return factory.getNewConnection(connectPath, clusterIndex); + } catch (SQLException e) { + throw new IllegalStateException("Failed to create a connection", e); + } + }).collect(Collectors.toList()); + } + /** * Increment and return the next connection's initialConnection number. * This allows us to better distribute the connections positions as connections are created per query in the tests From 1b30cbd41f76828be3add18484a2d6e71d66b788 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 1 Apr 2026 11:57:01 -0400 Subject: [PATCH 06/25] Change InProcess to support multiple clusters --- .../configs/JDBCInProcessConfig.java | 41 ++++++++++++++++- .../configs/JDBCMultiServerConfig.java | 46 ++----------------- 2 files changed, 44 insertions(+), 43 deletions(-) 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 0a6e64df68..fd5775210c 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 @@ -24,18 +24,27 @@ import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; import com.apple.foundationdb.relational.yamltests.connectionfactory.JDBCInProcessYamlConnectionFactory; +import com.apple.foundationdb.test.FDBTestEnvironment; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; /** * Run against an embedded JDBC server. + *

+ * When multiple cluster files are available, starts one in-process server per additional cluster so that + * multi-cluster tests (using {@code connect: { cluster: N }}) can route to the correct cluster. */ public class JDBCInProcessConfig implements YamlTestConfig { @Nullable private InProcessRelationalServer server; @Nullable private final String clusterFile; + @Nonnull + private final List additionalClusterServers = new ArrayList<>(); public JDBCInProcessConfig() { this(null); @@ -52,10 +61,21 @@ public void beforeAll() throws Exception { } catch (Exception e) { throw new RuntimeException(e); } + // Start one in-process server per additional cluster file + for (final String otherClusterFile : FDBTestEnvironment.allClusterFiles()) { + if (!Objects.equals(otherClusterFile, clusterFile)) { + final InProcessRelationalServer additionalServer = new InProcessRelationalServer(otherClusterFile).start(); + additionalClusterServers.add(additionalServer); + } + } } @Override public void afterAll() throws Exception { + for (final InProcessRelationalServer additionalServer : additionalClusterServers) { + additionalServer.close(); + } + additionalClusterServers.clear(); if (server != null) { server.close(); server = null; @@ -64,7 +84,26 @@ public void afterAll() throws Exception { @Override public YamlConnectionFactory createConnectionFactory() { - return new JDBCInProcessYamlConnectionFactory(server, clusterFile); + return new JDBCInProcessYamlConnectionFactory(server, clusterFile, buildClusterServers()); + } + + /** + * Build the list of {@link JDBCInProcessYamlConnectionFactory.ClusterServer} entries for the additional clusters. + */ + @Nonnull + protected List buildClusterServers() { + final List clusterServers = new ArrayList<>(); + final List allClusterFiles = FDBTestEnvironment.allClusterFiles(); + for (int i = 0; i < additionalClusterServers.size(); i++) { + final String otherClusterFile = allClusterFiles.stream() + .filter(cf -> !Objects.equals(cf, clusterFile)) + .skip(i) + .findFirst() + .orElseThrow(); + clusterServers.add(new JDBCInProcessYamlConnectionFactory.ClusterServer( + additionalClusterServers.get(i), otherClusterFile)); + } + return clusterServers; } protected InProcessRelationalServer getServer() { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java index a67906891c..7e56d60343 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java @@ -20,25 +20,22 @@ package com.apple.foundationdb.relational.yamltests.configs; -import com.apple.foundationdb.relational.server.InProcessRelationalServer; import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.connectionfactory.ExternalServerYamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.connectionfactory.JDBCInProcessYamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.connectionfactory.MultiServerConnectionFactory; import com.apple.foundationdb.relational.yamltests.server.ExternalServer; -import com.apple.foundationdb.test.FDBTestEnvironment; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** * Run against an embedded JDBC driver, and an external server, alternating commands that go against each. *

- * When multiple cluster files are available, starts one in-process server per additional cluster so that - * multi-cluster tests (using {@code connect: { cluster: N }}) can route to the correct cluster. + * Multi-cluster support (additional in-process servers for non-primary cluster files) is inherited from + * {@link JDBCInProcessConfig}. When additional cluster external servers are provided, they are passed to + * the {@link ExternalServerYamlConnectionFactory} so that cluster-specific connections also alternate. */ public class JDBCMultiServerConfig extends JDBCInProcessConfig { @@ -46,8 +43,6 @@ public class JDBCMultiServerConfig extends JDBCInProcessConfig { private final int initialConnection; @Nonnull private final List additionalClusterExternalServers; - @Nonnull - private final List additionalClusterServers = new ArrayList<>(); public JDBCMultiServerConfig(final int initialConnection, ExternalServer externalServer) { this(initialConnection, externalServer, null, List.of()); @@ -67,43 +62,10 @@ public JDBCMultiServerConfig(final int initialConnection, ExternalServer externa this.additionalClusterExternalServers = additionalClusterExternalServers; } - @Override - public void beforeAll() throws Exception { - super.beforeAll(); - // Start one in-process server per additional cluster file - for (final String otherClusterFile : FDBTestEnvironment.allClusterFiles()) { - if (!Objects.equals(otherClusterFile, getClusterFile())) { - final InProcessRelationalServer server = new InProcessRelationalServer(otherClusterFile).start(); - additionalClusterServers.add(server); - } - } - } - - @Override - public void afterAll() throws Exception { - for (final InProcessRelationalServer server : additionalClusterServers) { - server.close(); - } - additionalClusterServers.clear(); - super.afterAll(); - } - @Override public YamlConnectionFactory createConnectionFactory() { - // Build the JDBCInProcessYamlConnectionFactory with multi-cluster support - final List clusterServers = new ArrayList<>(); - final List allClusterFiles = FDBTestEnvironment.allClusterFiles(); - for (int i = 0; i < additionalClusterServers.size(); i++) { - final String otherClusterFile = allClusterFiles.stream() - .filter(cf -> !Objects.equals(cf, getClusterFile())) - .skip(i) - .findFirst() - .orElseThrow(); - clusterServers.add(new JDBCInProcessYamlConnectionFactory.ClusterServer( - additionalClusterServers.get(i), otherClusterFile)); - } final JDBCInProcessYamlConnectionFactory jdbcFactory = - new JDBCInProcessYamlConnectionFactory(getServer(), getClusterFile(), clusterServers); + new JDBCInProcessYamlConnectionFactory(getServer(), getClusterFile(), buildClusterServers()); return new MultiServerConnectionFactory( MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE, From 0a10f472f4c3a565a8910a4ac104c09897fc7d0e Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 1 Apr 2026 14:10:41 -0400 Subject: [PATCH 07/25] Just use one list for cluster files --- .../yamltests/YamlTestExtension.java | 101 ++++++++---------- .../yamltests/configs/EmbeddedConfig.java | 58 +++++----- .../configs/ExternalMultiServerConfig.java | 4 +- .../configs/JDBCInProcessConfig.java | 74 +++---------- .../configs/JDBCMultiServerConfig.java | 43 +++----- .../EmbeddedYamlConnectionFactory.java | 48 +++++---- .../ExternalServerYamlConnectionFactory.java | 32 +++--- .../JDBCInProcessYamlConnectionFactory.java | 49 ++++----- 8 files changed, 169 insertions(+), 240 deletions(-) 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 6f8113ccae..f0cc746e35 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 @@ -51,11 +51,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.File; -import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -68,44 +65,53 @@ public class YamlTestExtension implements TestTemplateInvocationContextProvider, private static final Logger logger = LogManager.getLogger(YamlTestExtension.class); private List testConfigs; private List maintainConfigs; - private List servers; - /** Additional external servers for non-primary cluster files, keyed by the primary server they belong to. */ - @Nonnull - private final Map> additionalClusterExternalServers = new HashMap<>(); + /** External servers grouped by jar version. Each inner list has one server per cluster file (same order as {@link #clusterFiles}). */ @Nullable - private final String clusterFile; + private List> externalServerGroups; + @Nonnull + private final List clusterFiles; private final boolean includeMethodInDescriptions; @SuppressWarnings("unused") // Used implicitly with @ExtendWith(YamlTestExtension.class) public YamlTestExtension() { - this(FDBTestEnvironment.randomClusterFile(), false); + this(FDBTestEnvironment.allClusterFiles(), false); } /** * Create a new extension with some configuration. - * @param clusterFile a custom cluster file to use, or {@code null} to inherit it from the environment, namely - * {@code FDB_CLUSTER_FILE}. + * @param clusterFile a custom cluster file to use as the primary, or {@code null} to inherit it from the + * environment, namely {@code FDB_CLUSTER_FILE}. * @param includeMethodInDescriptions Set this to {@code true} if publishing test results to something that cannot * handle complex test hierarchies. In the record layer we maintain the full hierarchy in the output, so this is not * necessary, but if integrating some other tools this might be necessary. */ public YamlTestExtension(@Nullable final String clusterFile, final boolean includeMethodInDescriptions) { - this.clusterFile = clusterFile; + this(List.of(clusterFile), includeMethodInDescriptions); + } + + /** + * Create a new extension with an explicit list of cluster files. + * @param clusterFiles the cluster files to use, where index 0 is the primary cluster + * @param includeMethodInDescriptions Set this to {@code true} if publishing test results to something that cannot + * handle complex test hierarchies. + */ + public YamlTestExtension(@Nonnull final List clusterFiles, final boolean includeMethodInDescriptions) { + this.clusterFiles = clusterFiles; this.includeMethodInDescriptions = includeMethodInDescriptions; } @Override public void beforeAll(final ExtensionContext context) throws Exception { maintainConfigs = List.of( - new CorrectExplains(new EmbeddedConfig(clusterFile)), - new CorrectMetrics(new EmbeddedConfig(clusterFile)), - new CorrectExplainsAndMetrics(new EmbeddedConfig(clusterFile)), - new ShowPlanOnDiff(new EmbeddedConfig(clusterFile)) + new CorrectExplains(new EmbeddedConfig(clusterFiles)), + new CorrectMetrics(new EmbeddedConfig(clusterFiles)), + new CorrectExplainsAndMetrics(new EmbeddedConfig(clusterFiles)), + new ShowPlanOnDiff(new EmbeddedConfig(clusterFiles)) ); if (Boolean.parseBoolean(System.getProperty("tests.runQuick", "false"))) { - testConfigs = List.of(new EmbeddedConfig(clusterFile)); + testConfigs = List.of(new EmbeddedConfig(clusterFiles)); } else if (Boolean.parseBoolean(System.getProperty("tests.runRPC", "false"))) { - testConfigs = List.of(new JDBCInProcessConfig(clusterFile)); + testConfigs = List.of(new JDBCInProcessConfig(clusterFiles)); } else { List jars = ExternalServer.getAvailableServers(); // Fail the test if there are no available servers. This would force the execution in "runQuick" mode in case @@ -113,23 +119,15 @@ public void beforeAll(final ExtensionContext context) throws Exception { // Potentially, we can relax this a little if all tests are disabled for multi-server execution, but this is // not a likely scenario. Assertions.assertFalse(jars.isEmpty(), "There are no external servers available to run"); - servers = new ArrayList<>(); + externalServerGroups = new ArrayList<>(); List allExternalServers = new ArrayList<>(); - final List otherClusterFiles = FDBTestEnvironment.allClusterFiles().stream() - .filter(cf -> !Objects.equals(cf, clusterFile)) - .collect(Collectors.toList()); for (File jar : jars) { - ExternalServer primaryServer = new ExternalServer(jar, clusterFile); - servers.add(primaryServer); - allExternalServers.add(primaryServer); - // Create additional external servers for non-primary cluster files - List additionalServers = new ArrayList<>(); - for (String otherClusterFile : otherClusterFiles) { - ExternalServer additionalServer = new ExternalServer(jar, otherClusterFile); - additionalServers.add(additionalServer); - allExternalServers.add(additionalServer); + List group = new ArrayList<>(); + for (String cf : clusterFiles) { + group.add(new ExternalServer(jar, cf)); } - additionalClusterExternalServers.put(primaryServer, additionalServers); + externalServerGroups.add(group); + allExternalServers.addAll(group); } ExternalServer.startMultiple(allExternalServers); final boolean mixedModeOnly = Boolean.parseBoolean(System.getProperty("tests.mixedModeOnly", "false")); @@ -152,28 +150,26 @@ private Stream localConfigs(final boolean mixedModeOnly, final b if (mixedModeOnly || singleExternalVersionOnly) { return Stream.of(); } else { - return Stream.of(new EmbeddedConfig(clusterFile), new JDBCInProcessConfig(clusterFile)); + return Stream.of(new EmbeddedConfig(clusterFiles), new JDBCInProcessConfig(clusterFiles)); } } private Stream externalServerConfigs(final boolean singleExternalVersionOnly) { if (singleExternalVersionOnly) { - return servers.stream() + return externalServerGroups.stream() + .map(group -> group.get(0)) // Create an ExternalServer config with two servers of the same version for each server // (with and without forced continuations) .flatMap(server -> Stream.of(new ExternalMultiServerConfig(0, server, server), new ForceContinuations(new ExternalMultiServerConfig(0, server, server)))); } else { - return servers.stream().flatMap(server -> { - List additionalServers = - additionalClusterExternalServers.getOrDefault(server, List.of()); - // (4 configs for each server available) - return Stream.of(new JDBCMultiServerConfig(0, server, clusterFile, additionalServers), - new ForceContinuations(new JDBCMultiServerConfig(0, server, clusterFile, additionalServers)), - new JDBCMultiServerConfig(1, server, clusterFile, additionalServers), - new ForceContinuations(new JDBCMultiServerConfig(1, server, clusterFile, additionalServers))); - }); + return externalServerGroups.stream().flatMap(group -> + // (4 configs for each server version available) + Stream.of(new JDBCMultiServerConfig(0, group, clusterFiles), + new ForceContinuations(new JDBCMultiServerConfig(0, group, clusterFiles)), + new JDBCMultiServerConfig(1, group, clusterFiles), + new ForceContinuations(new JDBCMultiServerConfig(1, group, clusterFiles)))); } } @@ -194,28 +190,19 @@ public void afterAll(final ExtensionContext context) throws Exception { return e; } }).filter(Objects::nonNull).findFirst(); - if (servers != null) { - for (ExternalServer server : servers) { - try { - server.stop(); - } catch (Exception ex) { - if (logger.isWarnEnabled()) { - logger.warn("Failed to stop server " + server.getVersion() + " on " + server.getPort()); - } - } - } - for (List additionalServers : additionalClusterExternalServers.values()) { - for (ExternalServer server : additionalServers) { + if (externalServerGroups != null) { + for (List group : externalServerGroups) { + for (ExternalServer server : group) { try { server.stop(); } catch (Exception ex) { if (logger.isWarnEnabled()) { - logger.warn("Failed to stop additional cluster server " + server.getVersion() + " on " + server.getPort()); + logger.warn("Failed to stop server " + server.getVersion() + " on " + server.getPort()); } } } } - additionalClusterExternalServers.clear(); + externalServerGroups = null; } if (exception.isPresent()) { throw exception.get(); 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 76fb59554f..fddaed92fd 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 @@ -25,26 +25,31 @@ import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; import com.apple.foundationdb.relational.yamltests.connectionfactory.EmbeddedYamlConnectionFactory; -import com.apple.foundationdb.test.FDBTestEnvironment; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Objects; /** * Run directly against an instance of {@link FRL}. + *

+ * Starts one FRL per cluster file so that multi-cluster tests + * (using {@code connect: { cluster: N }}) can route to the correct cluster. */ public class EmbeddedConfig implements YamlTestConfig { - private FRL frl; - @Nullable - private final String clusterFile; @Nonnull - private final List additionalClusterFrls = new ArrayList<>(); + private final List clusterFiles; + @Nonnull + private final List frls = new ArrayList<>(); public EmbeddedConfig(@Nullable final String clusterFile) { - this.clusterFile = clusterFile; + this(Collections.singletonList(clusterFile)); + } + + public EmbeddedConfig(@Nonnull final List clusterFiles) { + this.clusterFiles = clusterFiles; } @Override @@ -55,43 +60,34 @@ public void beforeAll() throws Exception { .withOption(Options.Name.PLAN_CACHE_TERTIARY_TIME_TO_LIVE_MILLIS, 3_600_000L) .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 10) .build(); - frl = new FRL(options, clusterFile); - - // Create drivers for additional clusters (without registering them in DriverManager) - for (final String otherClusterFile : FDBTestEnvironment.allClusterFiles()) { - if (!Objects.equals(otherClusterFile, clusterFile)) { - additionalClusterFrls.add(new FRL(options, otherClusterFile, false)); + // The primary FRL registers its driver in DriverManager; additional ones do not + boolean first = true; + for (final String clusterFile : clusterFiles) { + if (first) { + frls.add(new FRL(options, clusterFile)); + first = false; + } else { + frls.add(new FRL(options, clusterFile, false)); } } } @Override public void afterAll() throws Exception { - for (final FRL additionalFrl : additionalClusterFrls) { - additionalFrl.close(); - } - additionalClusterFrls.clear(); - if (frl != null) { + for (final FRL frl : frls) { frl.close(); - frl = null; } + frls.clear(); } @Override public YamlConnectionFactory createConnectionFactory() { - final List additionalDrivers = new ArrayList<>(); - final List allClusterFiles = FDBTestEnvironment.allClusterFiles(); - for (int i = 0; i < additionalClusterFrls.size(); i++) { - // Find the cluster file for this additional FRL - final String otherClusterFile = allClusterFiles.stream() - .filter(cf -> !Objects.equals(cf, clusterFile)) - .skip(i) - .findFirst() - .orElseThrow(); - additionalDrivers.add(new EmbeddedYamlConnectionFactory.ClusterDriver( - additionalClusterFrls.get(i).getDriver(), otherClusterFile)); + final List clusterDrivers = new ArrayList<>(); + for (int i = 0; i < frls.size(); i++) { + clusterDrivers.add(new EmbeddedYamlConnectionFactory.ClusterDriver( + frls.get(i).getDriver(), clusterFiles.get(i))); } - return new EmbeddedYamlConnectionFactory(clusterFile, additionalDrivers); + return new EmbeddedYamlConnectionFactory(clusterDrivers); } @Override diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java index 05849cd1ee..f5874ab9e1 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java @@ -59,8 +59,8 @@ public YamlConnectionFactory createConnectionFactory() { return new MultiServerConnectionFactory( MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE, initialConnection, - new ExternalServerYamlConnectionFactory(server0), - List.of(new ExternalServerYamlConnectionFactory(server1))); + new ExternalServerYamlConnectionFactory(List.of(server0)), + List.of(new ExternalServerYamlConnectionFactory(List.of(server1)))); } @Override 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 fd5775210c..cf6aee9209 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 @@ -24,97 +24,53 @@ import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; import com.apple.foundationdb.relational.yamltests.connectionfactory.JDBCInProcessYamlConnectionFactory; -import com.apple.foundationdb.test.FDBTestEnvironment; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; -import java.util.Objects; /** * Run against an embedded JDBC server. *

- * When multiple cluster files are available, starts one in-process server per additional cluster so that - * multi-cluster tests (using {@code connect: { cluster: N }}) can route to the correct cluster. + * Starts one in-process server per cluster file so that multi-cluster tests + * (using {@code connect: { cluster: N }}) can route to the correct cluster. */ public class JDBCInProcessConfig implements YamlTestConfig { - @Nullable - private InProcessRelationalServer server; - @Nullable - private final String clusterFile; @Nonnull - private final List additionalClusterServers = new ArrayList<>(); - - public JDBCInProcessConfig() { - this(null); - } + private final List clusterFiles; + @Nonnull + private final List clusterServers = new ArrayList<>(); - public JDBCInProcessConfig(@Nullable final String clusterFile) { - this.clusterFile = clusterFile; + public JDBCInProcessConfig(@Nonnull final List clusterFiles) { + this.clusterFiles = clusterFiles; } @Override public void beforeAll() throws Exception { - try { - server = new InProcessRelationalServer(clusterFile).start(); - } catch (Exception e) { - throw new RuntimeException(e); - } - // Start one in-process server per additional cluster file - for (final String otherClusterFile : FDBTestEnvironment.allClusterFiles()) { - if (!Objects.equals(otherClusterFile, clusterFile)) { - final InProcessRelationalServer additionalServer = new InProcessRelationalServer(otherClusterFile).start(); - additionalClusterServers.add(additionalServer); - } + for (final String clusterFile : clusterFiles) { + final InProcessRelationalServer server = new InProcessRelationalServer(clusterFile).start(); + clusterServers.add(new JDBCInProcessYamlConnectionFactory.ClusterServer(server, clusterFile)); } } @Override public void afterAll() throws Exception { - for (final InProcessRelationalServer additionalServer : additionalClusterServers) { - additionalServer.close(); - } - additionalClusterServers.clear(); - if (server != null) { - server.close(); - server = null; + for (final JDBCInProcessYamlConnectionFactory.ClusterServer cs : clusterServers) { + cs.server().close(); } + clusterServers.clear(); } @Override public YamlConnectionFactory createConnectionFactory() { - return new JDBCInProcessYamlConnectionFactory(server, clusterFile, buildClusterServers()); + return new JDBCInProcessYamlConnectionFactory(clusterServers); } - /** - * Build the list of {@link JDBCInProcessYamlConnectionFactory.ClusterServer} entries for the additional clusters. - */ @Nonnull - protected List buildClusterServers() { - final List clusterServers = new ArrayList<>(); - final List allClusterFiles = FDBTestEnvironment.allClusterFiles(); - for (int i = 0; i < additionalClusterServers.size(); i++) { - final String otherClusterFile = allClusterFiles.stream() - .filter(cf -> !Objects.equals(cf, clusterFile)) - .skip(i) - .findFirst() - .orElseThrow(); - clusterServers.add(new JDBCInProcessYamlConnectionFactory.ClusterServer( - additionalClusterServers.get(i), otherClusterFile)); - } + protected List getClusterServers() { return clusterServers; } - protected InProcessRelationalServer getServer() { - return server; - } - - @Nullable - protected String getClusterFile() { - return clusterFile; - } - @Nonnull @Override public YamlExecutionContext.ContextOptions getRunnerOptions() { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java index 7e56d60343..21783d815d 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java @@ -27,59 +27,48 @@ import com.apple.foundationdb.relational.yamltests.server.ExternalServer; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.List; /** * Run against an embedded JDBC driver, and an external server, alternating commands that go against each. *

- * Multi-cluster support (additional in-process servers for non-primary cluster files) is inherited from - * {@link JDBCInProcessConfig}. When additional cluster external servers are provided, they are passed to - * the {@link ExternalServerYamlConnectionFactory} so that cluster-specific connections also alternate. + * Multi-cluster support (in-process servers for all cluster files) is inherited from + * {@link JDBCInProcessConfig}. The external servers list should have one entry per cluster file + * (matching the in-process servers), so that cluster-specific connections also alternate. */ public class JDBCMultiServerConfig extends JDBCInProcessConfig { - private final ExternalServer externalServer; - private final int initialConnection; @Nonnull - private final List additionalClusterExternalServers; - - public JDBCMultiServerConfig(final int initialConnection, ExternalServer externalServer) { - this(initialConnection, externalServer, null, List.of()); - } + private final List externalServers; + private final int initialConnection; - public JDBCMultiServerConfig(final int initialConnection, ExternalServer externalServer, - @Nullable final String clusterFile) { - this(initialConnection, externalServer, clusterFile, List.of()); + public JDBCMultiServerConfig(final int initialConnection, @Nonnull List externalServers) { + this(initialConnection, externalServers, List.of()); } - public JDBCMultiServerConfig(final int initialConnection, ExternalServer externalServer, - @Nullable final String clusterFile, - @Nonnull List additionalClusterExternalServers) { - super(clusterFile); + public JDBCMultiServerConfig(final int initialConnection, @Nonnull List externalServers, + @Nonnull final List clusterFiles) { + super(clusterFiles); this.initialConnection = initialConnection; - this.externalServer = externalServer; - this.additionalClusterExternalServers = additionalClusterExternalServers; + this.externalServers = externalServers; } @Override public YamlConnectionFactory createConnectionFactory() { - final JDBCInProcessYamlConnectionFactory jdbcFactory = - new JDBCInProcessYamlConnectionFactory(getServer(), getClusterFile(), buildClusterServers()); - return new MultiServerConnectionFactory( MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE, initialConnection, - jdbcFactory, - List.of(new ExternalServerYamlConnectionFactory(externalServer, additionalClusterExternalServers))); + new JDBCInProcessYamlConnectionFactory(getClusterServers()), + List.of(new ExternalServerYamlConnectionFactory(externalServers))); } @Override public String toString() { + final ExternalServer primaryExternal = externalServers.get(0); if (initialConnection == 0) { - return "MultiServer (" + super.toString() + " then " + externalServer.getVersion() + ")"; + return "MultiServer (" + super.toString() + " then " + primaryExternal.getVersion() + ")"; } else { - return "MultiServer (" + externalServer.getVersion() + " then " + super.toString() + ")"; + return "MultiServer (" + primaryExternal.getVersion() + " then " + super.toString() + ")"; } } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java index 62a68370f6..41072dcd3f 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java @@ -35,23 +35,21 @@ import java.util.Set; public class EmbeddedYamlConnectionFactory implements YamlConnectionFactory { - private final String clusterFile; @Nonnull - private final List additionalClusterDrivers; + private final List clusterDrivers; - public EmbeddedYamlConnectionFactory(String clusterFile) { - this(clusterFile, List.of()); - } - - public EmbeddedYamlConnectionFactory(String clusterFile, @Nonnull List additionalClusterDrivers) { - this.clusterFile = clusterFile; - this.additionalClusterDrivers = additionalClusterDrivers; + public EmbeddedYamlConnectionFactory(@Nonnull List clusterDrivers) { + if (clusterDrivers.isEmpty()) { + throw new IllegalArgumentException("At least one cluster driver is required"); + } + this.clusterDrivers = clusterDrivers; } @Override public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException { + // The primary cluster's driver is registered in DriverManager, so use that path return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()), - SemanticVersion.current(), "Embedded", clusterFile); + SemanticVersion.current(), "Embedded", clusterDrivers.get(0).clusterFile()); } @Override @@ -59,22 +57,22 @@ public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterInde if (clusterIndex == 0) { return getNewConnection(connectPath); } - final int idx = clusterIndex - 1; - if (idx >= additionalClusterDrivers.size()) { + if (clusterIndex < 0 || clusterIndex >= clusterDrivers.size()) { throw new SQLException("Cluster index " + clusterIndex + " not available (only " + - (additionalClusterDrivers.size() + 1) + " clusters configured)"); + clusterDrivers.size() + " clusters configured)"); } - final ClusterDriver clusterDriver = additionalClusterDrivers.get(idx); + // Non-primary clusters are not registered in DriverManager, so connect via the driver directly + final ClusterDriver clusterDriver = clusterDrivers.get(clusterIndex); return new SimpleYamlConnection( - clusterDriver.driver.connect(connectPath, Options.NONE), + clusterDriver.driver().connect(connectPath, Options.NONE), SemanticVersion.current(), "Embedded[cluster=" + clusterIndex + "]", - clusterDriver.clusterFile); + clusterDriver.clusterFile()); } @Override public int getAvailableClusterCount() { - return 1 + additionalClusterDrivers.size(); + return clusterDrivers.size(); } @Override @@ -83,17 +81,27 @@ public Set getVersionsUnderTest() { } /** - * A driver associated with its cluster file, for additional (non-primary) clusters. + * A driver associated with its cluster file. */ public static class ClusterDriver { @Nonnull - final RelationalDriver driver; + private final RelationalDriver driver; @Nonnull - final String clusterFile; + private final String clusterFile; public ClusterDriver(@Nonnull RelationalDriver driver, @Nonnull String clusterFile) { this.driver = driver; this.clusterFile = clusterFile; } + + @Nonnull + public RelationalDriver driver() { + return driver; + } + + @Nonnull + public String clusterFile() { + return clusterFile; + } } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java index c7b3652928..e2c026883a 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java @@ -39,41 +39,33 @@ public class ExternalServerYamlConnectionFactory implements YamlConnectionFactory { private static final Logger LOG = LogManager.getLogger(ExternalServerYamlConnectionFactory.class); - private final ExternalServer externalServer; @Nonnull - private final List additionalClusterServers; + private final List servers; - public ExternalServerYamlConnectionFactory(final ExternalServer externalServer) { - this(externalServer, List.of()); - } - - public ExternalServerYamlConnectionFactory(final ExternalServer externalServer, - @Nonnull List additionalClusterServers) { - this.externalServer = externalServer; - this.additionalClusterServers = additionalClusterServers; + public ExternalServerYamlConnectionFactory(@Nonnull List servers) { + if (servers.isEmpty()) { + throw new IllegalArgumentException("At least one external server is required"); + } + this.servers = servers; } @Override public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException { - return createConnection(connectPath, externalServer); + return createConnection(connectPath, servers.get(0)); } @Override public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { - if (clusterIndex == 0) { - return getNewConnection(connectPath); - } - final int idx = clusterIndex - 1; - if (idx >= additionalClusterServers.size()) { + if (clusterIndex < 0 || clusterIndex >= servers.size()) { throw new SQLException("Cluster index " + clusterIndex + " not available (only " + - (additionalClusterServers.size() + 1) + " clusters configured)"); + servers.size() + " clusters configured)"); } - return createConnection(connectPath, additionalClusterServers.get(idx)); + return createConnection(connectPath, servers.get(clusterIndex)); } @Override public int getAvailableClusterCount() { - return 1 + additionalClusterServers.size(); + return servers.size(); } private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull ExternalServer server) throws SQLException { @@ -92,7 +84,7 @@ private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull Exter @Override public Set getVersionsUnderTest() { - return Set.of(externalServer.getVersion()); + return Set.of(servers.get(0).getVersion()); } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java index 584d471f6c..1edd9aa36f 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java @@ -39,44 +39,35 @@ public class JDBCInProcessYamlConnectionFactory implements YamlConnectionFactory { private static final Logger LOG = LogManager.getLogger(JDBCInProcessYamlConnectionFactory.class); - private final InProcessRelationalServer server; - private final String clusterFile; @Nonnull - private final List additionalClusterServers; + private final List clusterServers; - public JDBCInProcessYamlConnectionFactory(final InProcessRelationalServer server, final String clusterFile) { - this(server, clusterFile, List.of()); - } - - public JDBCInProcessYamlConnectionFactory(final InProcessRelationalServer server, final String clusterFile, - @Nonnull List additionalClusterServers) { - this.server = server; - this.clusterFile = clusterFile; - this.additionalClusterServers = additionalClusterServers; + public JDBCInProcessYamlConnectionFactory(@Nonnull List clusterServers) { + if (clusterServers.isEmpty()) { + throw new IllegalArgumentException("At least one cluster server is required"); + } + this.clusterServers = clusterServers; } @Override public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException { - return createConnection(connectPath, server, clusterFile); + final ClusterServer primary = clusterServers.get(0); + return createConnection(connectPath, primary.server, primary.clusterFile); } @Override public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { - if (clusterIndex == 0) { - return getNewConnection(connectPath); - } - final int idx = clusterIndex - 1; - if (idx >= additionalClusterServers.size()) { + if (clusterIndex < 0 || clusterIndex >= clusterServers.size()) { throw new SQLException("Cluster index " + clusterIndex + " not available (only " + - (additionalClusterServers.size() + 1) + " clusters configured)"); + clusterServers.size() + " clusters configured)"); } - final ClusterServer clusterServer = additionalClusterServers.get(idx); + final ClusterServer clusterServer = clusterServers.get(clusterIndex); return createConnection(connectPath, clusterServer.server, clusterServer.clusterFile); } @Override public int getAvailableClusterCount() { - return 1 + additionalClusterServers.size(); + return clusterServers.size(); } private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull InProcessRelationalServer targetServer, @@ -98,17 +89,27 @@ public Set getVersionsUnderTest() { } /** - * A server associated with its cluster file, for additional (non-primary) clusters. + * A server associated with its cluster file. */ public static class ClusterServer { @Nonnull - final InProcessRelationalServer server; + private final InProcessRelationalServer server; @Nonnull - final String clusterFile; + private final String clusterFile; public ClusterServer(@Nonnull InProcessRelationalServer server, @Nonnull String clusterFile) { this.server = server; this.clusterFile = clusterFile; } + + @Nonnull + public InProcessRelationalServer server() { + return server; + } + + @Nonnull + public String clusterFile() { + return clusterFile; + } } } From c3477f3730f5a984a6ab2862c754a4e309eaf8d9 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Wed, 1 Apr 2026 16:33:55 -0400 Subject: [PATCH 08/25] Suppress close warnings --- .../relational/yamltests/configs/EmbeddedConfig.java | 2 ++ .../relational/yamltests/configs/JDBCInProcessConfig.java | 1 + 2 files changed, 3 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 fddaed92fd..efe4960575 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 @@ -53,6 +53,7 @@ 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 { var options = Options.builder() .withOption(Options.Name.PLAN_CACHE_PRIMARY_TIME_TO_LIVE_MILLIS, 3_600_000L) @@ -73,6 +74,7 @@ public void beforeAll() throws Exception { } @Override + @SuppressWarnings("PMD.CloseResource") // FRLs are being closed in this loop public void afterAll() throws Exception { for (final FRL frl : frls) { frl.close(); 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 cf6aee9209..e956fda0f0 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 @@ -46,6 +46,7 @@ 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 { for (final String clusterFile : clusterFiles) { final InProcessRelationalServer server = new InProcessRelationalServer(clusterFile).start(); From dd1838631f9ad00875ce0454fe55a594144ceee5 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Thu, 2 Apr 2026 16:51:44 -0400 Subject: [PATCH 09/25] Remove old YamlConnectionFactory.getNewConnection This breaks backwards compatibility, but I don't think this is really intended to be a public interface --- .../yamltests/ConnectionTarget.java | 4 ++-- .../yamltests/YamlConnectionFactory.java | 18 +--------------- .../YamlConnectionFactoryWithOptions.java | 7 ------- .../EmbeddedYamlConnectionFactory.java | 15 +++++-------- .../ExternalServerYamlConnectionFactory.java | 5 ----- .../JDBCInProcessYamlConnectionFactory.java | 6 ------ .../MultiServerConnectionFactory.java | 21 ------------------- .../src/test/java/IncludeBlockTest.java | 2 +- .../src/test/java/InitialVersionTest.java | 2 +- .../MultiServerConnectionFactoryTest.java | 17 ++++++++------- .../src/test/java/SupportedVersionTest.java | 2 +- .../src/test/java/TransactionSetupTest.java | 2 +- 12 files changed, 21 insertions(+), 80 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java index 61223e37a9..23752e2fb0 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java @@ -26,8 +26,8 @@ /** * A resolved connection target consisting of a URI and a cluster index. * - *

The cluster index identifies which FDB cluster to connect to. Index 0 is the default (and only) cluster - * in single-cluster configurations. Additional clusters can be specified in YAMSQL files using the map form + *

The cluster index identifies which FDB cluster to connect to. Index 0 is the default. + * Additional clusters can be accessed in YAMSQL files using the map form * of the {@code connect} directive: *

{@code
  * connect: { cluster: 1, uri: 0 }
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java
index d982a5973c..b3755ba14d 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactory.java
@@ -32,17 +32,6 @@
  * Connection factory to support yaml tests (see {@link YamlRunner}.
  */
 public interface YamlConnectionFactory {
-    /**
-     * Convert a connection uri into an actual connection.
-     *
-     * @param connectPath the path to connect to
-     *
-     * @return A new {@link RelationalConnection} for the given path appropriate for this test class
-     *
-     * @throws SQLException if we cannot connect
-     */
-    YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException;
-
     /**
      * Convert a connection uri into an actual connection on a specific cluster.
      *
@@ -53,12 +42,7 @@ public interface YamlConnectionFactory {
      *
      * @throws SQLException if we cannot connect or the cluster index is not supported
      */
-    default YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
-        if (clusterIndex != 0) {
-            throw new SQLException("This connection factory does not support multiple clusters (requested cluster " + clusterIndex + ")");
-        }
-        return getNewConnection(connectPath);
-    }
+    YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException;
 
     /**
      * The versions that the connection has, other than the current code.
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java
index c1b6c5976a..e54ab3361d 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlConnectionFactoryWithOptions.java
@@ -41,13 +41,6 @@ public YamlConnectionFactoryWithOptions(@Nonnull final YamlConnectionFactory und
         this.options = options;
     }
 
-    @Override
-    public YamlConnection getNewConnection(@Nonnull final URI connectPath) throws SQLException {
-        final var connection = underlying.getNewConnection(connectPath);
-        connection.setConnectionOptions(options);
-        return connection;
-    }
-
     @Override
     public YamlConnection getNewConnection(@Nonnull final URI connectPath, int clusterIndex) throws SQLException {
         final var connection = underlying.getNewConnection(connectPath, clusterIndex);
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java
index 41072dcd3f..42745a804f 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java
@@ -45,22 +45,17 @@ public EmbeddedYamlConnectionFactory(@Nonnull List clusterDrivers
         this.clusterDrivers = clusterDrivers;
     }
 
-    @Override
-    public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
-        // The primary cluster's driver is registered in DriverManager, so use that path
-        return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()),
-                SemanticVersion.current(), "Embedded", clusterDrivers.get(0).clusterFile());
-    }
-
     @Override
     public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
-        if (clusterIndex == 0) {
-            return getNewConnection(connectPath);
-        }
         if (clusterIndex < 0 || clusterIndex >= clusterDrivers.size()) {
             throw new SQLException("Cluster index " + clusterIndex + " not available (only " +
                     clusterDrivers.size() + " clusters configured)");
         }
+        if (clusterIndex == 0) {
+            // The primary cluster's driver is registered in DriverManager, so use that path
+            return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()),
+                    SemanticVersion.current(), "Embedded", clusterDrivers.get(0).clusterFile());
+        }
         // Non-primary clusters are not registered in DriverManager, so connect via the driver directly
         final ClusterDriver clusterDriver = clusterDrivers.get(clusterIndex);
         return new SimpleYamlConnection(
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java
index e2c026883a..a56a9d12df 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java
@@ -49,11 +49,6 @@ public ExternalServerYamlConnectionFactory(@Nonnull List servers
         this.servers = servers;
     }
 
-    @Override
-    public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
-        return createConnection(connectPath, servers.get(0));
-    }
-
     @Override
     public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
         if (clusterIndex < 0 || clusterIndex >= servers.size()) {
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java
index 1edd9aa36f..a3d20d90c7 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java
@@ -49,12 +49,6 @@ public JDBCInProcessYamlConnectionFactory(@Nonnull List clusterSe
         this.clusterServers = clusterServers;
     }
 
-    @Override
-    public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
-        final ClusterServer primary = clusterServers.get(0);
-        return createConnection(connectPath, primary.server, primary.clusterFile);
-    }
-
     @Override
     public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
         if (clusterIndex < 0 || clusterIndex >= clusterServers.size()) {
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java
index 189f1067af..0f0bde0233 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/MultiServerConnectionFactory.java
@@ -91,16 +91,6 @@ public MultiServerConnectionFactory(@Nonnull final ConnectionSelectionPolicy con
         this.currentConnectionSelector = new AtomicInteger(initialConnection);
     }
 
-    @Override
-    public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
-        if (connectionSelectionPolicy == ConnectionSelectionPolicy.DEFAULT) {
-            return defaultFactory.getNewConnection(connectPath);
-        } else {
-            return new MultiServerConnection(connectionSelectionPolicy, getNextConnectionNumber(),
-                    defaultFactory.getNewConnection(connectPath), alternateConnections(connectPath));
-        }
-    }
-
     @Override
     public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
         if (connectionSelectionPolicy == ConnectionSelectionPolicy.DEFAULT) {
@@ -127,17 +117,6 @@ public int getAvailableClusterCount() {
         return defaultFactory.getAvailableClusterCount();
     }
 
-    @Nonnull
-    private List alternateConnections(URI connectPath) {
-        return alternateFactories.stream().map(factory -> {
-            try {
-                return factory.getNewConnection(connectPath);
-            } catch (SQLException e) {
-                throw new IllegalStateException("Failed to create a connection", e);
-            }
-        }).collect(Collectors.toList());
-    }
-
     @Nonnull
     private List alternateConnections(URI connectPath, int clusterIndex) {
         return alternateFactories.stream().map(factory -> {
diff --git a/yaml-tests/src/test/java/IncludeBlockTest.java b/yaml-tests/src/test/java/IncludeBlockTest.java
index 6c8142f4c0..ed304c9fa8 100644
--- a/yaml-tests/src/test/java/IncludeBlockTest.java
+++ b/yaml-tests/src/test/java/IncludeBlockTest.java
@@ -74,7 +74,7 @@ private void doRun(String fileName) throws Exception {
     YamlConnectionFactory createConnectionFactory() {
         return new YamlConnectionFactory() {
             @Override
-            public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
+            public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
                 return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()), VERSION, CLUSTER_FILE);
             }
 
diff --git a/yaml-tests/src/test/java/InitialVersionTest.java b/yaml-tests/src/test/java/InitialVersionTest.java
index 8e43dec66c..5ece7f3411 100644
--- a/yaml-tests/src/test/java/InitialVersionTest.java
+++ b/yaml-tests/src/test/java/InitialVersionTest.java
@@ -73,7 +73,7 @@ private void doRun(String testName, YamlConnectionFactory connectionFactory) thr
     YamlConnectionFactory createConnectionFactory() {
         return new YamlConnectionFactory() {
             @Override
-            public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
+            public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
                 return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()), VERSION, CLUSTER_FILE);
             }
 
diff --git a/yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java b/yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java
index b1ee176d1d..5e33238de6 100644
--- a/yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java
+++ b/yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java
@@ -40,6 +40,7 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+// TODO add tests of multiple clusters
 public class MultiServerConnectionFactoryTest {
     private static final SemanticVersion PRIMARY_VERSION = SemanticVersion.parse("2.2.2.0");
     private static final SemanticVersion ALTERNATE_VERSION = SemanticVersion.parse("1.1.1.0");
@@ -59,17 +60,17 @@ void testDefaultPolicy(int initialConnection) throws SQLException {
         // just the default for the set of versions
         assertEquals(Set.of(PRIMARY_VERSION, ALTERNATE_VERSION), classUnderTest.getVersionsUnderTest());
 
-        var connection = classUnderTest.getNewConnection(URI.create("Blah"));
+        var connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, PRIMARY_VERSION, List.of(PRIMARY_VERSION));
         assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
         assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
 
-        connection = classUnderTest.getNewConnection(URI.create("Blah"));
+        connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, PRIMARY_VERSION, List.of(PRIMARY_VERSION));
         assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
         assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
 
-        connection = classUnderTest.getNewConnection(URI.create("Blah"));
+        connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, PRIMARY_VERSION, List.of(PRIMARY_VERSION));
         assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
         assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
@@ -92,7 +93,7 @@ void testAlternatePolicy(int initialConnection) throws SQLException {
         // - Factory current connection: initial connection
         // - connection current connection: initial connection
         // - statement: initial connection (2 statements)
-        var connection = classUnderTest.getNewConnection(URI.create("Blah"));
+        var connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, initialConnectionVersion, List.of(initialConnectionVersion, otherConnectionVersion));
         assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion);
         // next statement
@@ -102,7 +103,7 @@ void testAlternatePolicy(int initialConnection) throws SQLException {
         // - Factory current connection: alternate connection
         // - connection current connection: alternate connection
         // - statement: alternate connection (2 statements)
-        connection = classUnderTest.getNewConnection(URI.create("Blah"));
+        connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, otherConnectionVersion, List.of(otherConnectionVersion, initialConnectionVersion));
         assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion);
         // next statement
@@ -112,7 +113,7 @@ void testAlternatePolicy(int initialConnection) throws SQLException {
         // - Factory current connection: initial connection
         // - connection current connection: initial connection
         // - statement: initial connection (1 statement)
-        connection = classUnderTest.getNewConnection(URI.create("Blah"));
+        connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, initialConnectionVersion, List.of(initialConnectionVersion, otherConnectionVersion));
         // just one statement for this connection
         assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion);
@@ -121,7 +122,7 @@ void testAlternatePolicy(int initialConnection) throws SQLException {
         // - Factory current connection: alternate connection
         // - connection current connection: alternate connection
         // - statement: alternate connection (3 statements)
-        connection = classUnderTest.getNewConnection(URI.create("Blah"));
+        connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, otherConnectionVersion, List.of(otherConnectionVersion, initialConnectionVersion));
         assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion);
         // next statements
@@ -155,7 +156,7 @@ private static void assertConnection(final YamlConnection connection, final Sema
     YamlConnectionFactory dummyConnectionFactory(@Nonnull SemanticVersion version) {
         return new YamlConnectionFactory() {
             @Override
-            public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
+            public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
                 // Add query string to connection so we can tell where it came from
                 URI newPath = URI.create(connectPath + "?version=" + version);
                 return new SimpleYamlConnection(dummyConnection(newPath), version, CLUSTER_FILE);
diff --git a/yaml-tests/src/test/java/SupportedVersionTest.java b/yaml-tests/src/test/java/SupportedVersionTest.java
index 4053f60d23..920a1cf33e 100644
--- a/yaml-tests/src/test/java/SupportedVersionTest.java
+++ b/yaml-tests/src/test/java/SupportedVersionTest.java
@@ -66,7 +66,7 @@ private void doRun(String fileName) throws Exception {
     YamlConnectionFactory createConnectionFactory() {
         return new YamlConnectionFactory() {
             @Override
-            public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
+            public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
                 return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()), VERSION, CLUSTER_FILE);
             }
 
diff --git a/yaml-tests/src/test/java/TransactionSetupTest.java b/yaml-tests/src/test/java/TransactionSetupTest.java
index bb9a5ea4d8..eb256bfa9d 100644
--- a/yaml-tests/src/test/java/TransactionSetupTest.java
+++ b/yaml-tests/src/test/java/TransactionSetupTest.java
@@ -74,7 +74,7 @@ private void doRun(String fileName) throws Exception {
     YamlConnectionFactory createConnectionFactory() {
         return new YamlConnectionFactory() {
             @Override
-            public YamlConnection getNewConnection(@Nonnull URI connectPath) throws SQLException {
+            public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
                 return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()), VERSION, CLUSTER_FILE);
             }
 

From 68a9fbd69d191c5b176c68049abc5431610e64e5 Mon Sep 17 00:00:00 2001
From: Scott Dugas 
Date: Thu, 2 Apr 2026 17:07:16 -0400
Subject: [PATCH 10/25] Add a tests to MultiServerConnectionFactoryTest that
 cover multiple clusters

---
 .../MultiServerConnectionFactoryTest.java     | 105 ++++++++++++++----
 1 file changed, 83 insertions(+), 22 deletions(-)

diff --git a/yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java b/yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java
index 5e33238de6..f48fbe0668 100644
--- a/yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java
+++ b/yaml-tests/src/test/java/MultiServerConnectionFactoryTest.java
@@ -25,7 +25,6 @@
 import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory;
 import com.apple.foundationdb.relational.yamltests.connectionfactory.MultiServerConnectionFactory;
 import com.apple.foundationdb.relational.yamltests.server.SemanticVersion;
-import com.apple.foundationdb.test.FDBTestEnvironment;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.CsvSource;
@@ -40,11 +39,11 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
-// TODO add tests of multiple clusters
 public class MultiServerConnectionFactoryTest {
     private static final SemanticVersion PRIMARY_VERSION = SemanticVersion.parse("2.2.2.0");
     private static final SemanticVersion ALTERNATE_VERSION = SemanticVersion.parse("1.1.1.0");
-    private static final String CLUSTER_FILE = FDBTestEnvironment.randomClusterFile();
+    private static final String CLUSTER_FILE = "cluster0";
+    private static final String CLUSTER_FILE_1 = "cluster1";
 
     @ParameterizedTest
     @CsvSource({"0", "1"})
@@ -62,18 +61,18 @@ void testDefaultPolicy(int initialConnection) throws SQLException {
 
         var connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, PRIMARY_VERSION, List.of(PRIMARY_VERSION));
-        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
-        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
+        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION, CLUSTER_FILE);
+        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION, CLUSTER_FILE);
 
         connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, PRIMARY_VERSION, List.of(PRIMARY_VERSION));
-        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
-        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
+        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION, CLUSTER_FILE);
+        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION, CLUSTER_FILE);
 
         connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, PRIMARY_VERSION, List.of(PRIMARY_VERSION));
-        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
-        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION);
+        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION, CLUSTER_FILE);
+        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION, CLUSTER_FILE);
     }
 
     @ParameterizedTest
@@ -95,9 +94,9 @@ void testAlternatePolicy(int initialConnection) throws SQLException {
         // - statement: initial connection (2 statements)
         var connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, initialConnectionVersion, List.of(initialConnectionVersion, otherConnectionVersion));
-        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion);
+        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion, CLUSTER_FILE);
         // next statement
-        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion);
+        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion, CLUSTER_FILE);
 
         // Second run:
         // - Factory current connection: alternate connection
@@ -105,9 +104,9 @@ void testAlternatePolicy(int initialConnection) throws SQLException {
         // - statement: alternate connection (2 statements)
         connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, otherConnectionVersion, List.of(otherConnectionVersion, initialConnectionVersion));
-        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion);
+        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion, CLUSTER_FILE);
         // next statement
-        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion);
+        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion, CLUSTER_FILE);
 
         // Third run:
         // - Factory current connection: initial connection
@@ -116,7 +115,7 @@ void testAlternatePolicy(int initialConnection) throws SQLException {
         connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, initialConnectionVersion, List.of(initialConnectionVersion, otherConnectionVersion));
         // just one statement for this connection
-        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion);
+        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion, CLUSTER_FILE);
 
         // Fourth run:
         // - Factory current connection: alternate connection
@@ -124,10 +123,10 @@ void testAlternatePolicy(int initialConnection) throws SQLException {
         // - statement: alternate connection (3 statements)
         connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
         assertConnection(connection, otherConnectionVersion, List.of(otherConnectionVersion, initialConnectionVersion));
-        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion);
+        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion, CLUSTER_FILE);
         // next statements
-        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion);
-        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion);
+        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion, CLUSTER_FILE);
+        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion, CLUSTER_FILE);
     }
 
     @Test
@@ -144,8 +143,58 @@ void testIllegalInitialConnection() {
                 List.of(dummyConnectionFactory(ALTERNATE_VERSION))));
     }
 
-    private void assertStatement(final RelationalPreparedStatement statement, final SemanticVersion version) throws SQLException {
-        assertEquals("version=" + version, ((RelationalConnection)statement.getConnection()).getPath().getQuery());
+    @ParameterizedTest
+    @CsvSource({"0", "1"})
+    void testDefaultPolicyWithCluster(int initialConnection) throws SQLException {
+        MultiServerConnectionFactory classUnderTest = new MultiServerConnectionFactory(
+                MultiServerConnectionFactory.ConnectionSelectionPolicy.DEFAULT,
+                initialConnection,
+                dummyMultiClusterConnectionFactory(PRIMARY_VERSION, List.of(CLUSTER_FILE, CLUSTER_FILE_1)),
+                List.of(dummyMultiClusterConnectionFactory(ALTERNATE_VERSION, List.of(CLUSTER_FILE, CLUSTER_FILE_1))));
+
+        // Cluster 0 should use the default factory and return CLUSTER_FILE
+        var connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
+        assertConnection(connection, PRIMARY_VERSION, List.of(PRIMARY_VERSION));
+        assertEquals(CLUSTER_FILE, connection.getClusterFile());
+        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION, CLUSTER_FILE);
+
+        // Cluster 1 should use the default factory and return CLUSTER_FILE_1
+        connection = classUnderTest.getNewConnection(URI.create("Blah"), 1);
+        assertConnection(connection, PRIMARY_VERSION, List.of(PRIMARY_VERSION));
+        assertEquals(CLUSTER_FILE_1, connection.getClusterFile());
+        assertStatement(connection.prepareStatement("SQL"), PRIMARY_VERSION, CLUSTER_FILE_1);
+    }
+
+    @ParameterizedTest
+    @CsvSource({"0", "1"})
+    void testAlternatePolicyWithCluster(int initialConnection) throws SQLException {
+        final SemanticVersion[] versions = new SemanticVersion[] { PRIMARY_VERSION, ALTERNATE_VERSION };
+        final SemanticVersion initialConnectionVersion = versions[initialConnection];
+        final SemanticVersion otherConnectionVersion = versions[(initialConnection + 1) % 2];
+
+        MultiServerConnectionFactory classUnderTest = new MultiServerConnectionFactory(
+                MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE,
+                initialConnection,
+                dummyMultiClusterConnectionFactory(PRIMARY_VERSION, List.of(CLUSTER_FILE, CLUSTER_FILE_1)),
+                List.of(dummyMultiClusterConnectionFactory(ALTERNATE_VERSION, List.of(CLUSTER_FILE, CLUSTER_FILE_1))));
+
+        // Cluster 0: alternation works and cluster file is CLUSTER_FILE
+        var connection = classUnderTest.getNewConnection(URI.create("Blah"), 0);
+        assertConnection(connection, initialConnectionVersion, List.of(initialConnectionVersion, otherConnectionVersion));
+        assertEquals(CLUSTER_FILE, connection.getClusterFile());
+        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion, CLUSTER_FILE);
+        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion, CLUSTER_FILE);
+
+        // Cluster 1: alternation works and cluster file is CLUSTER_FILE_1
+        connection = classUnderTest.getNewConnection(URI.create("Blah"), 1);
+        assertConnection(connection, otherConnectionVersion, List.of(otherConnectionVersion, initialConnectionVersion));
+        assertEquals(CLUSTER_FILE_1, connection.getClusterFile());
+        assertStatement(connection.prepareStatement("SQL"), otherConnectionVersion, CLUSTER_FILE_1);
+        assertStatement(connection.prepareStatement("SQL"), initialConnectionVersion, CLUSTER_FILE_1);
+    }
+
+    private void assertStatement(final RelationalPreparedStatement statement, final SemanticVersion version, final String clusterFile) throws SQLException {
+        assertEquals("version=" + version + "&cluster=" + clusterFile, ((RelationalConnection)statement.getConnection()).getPath().getQuery());
     }
 
     private static void assertConnection(final YamlConnection connection, final SemanticVersion initialVersion, final List expectedVersions) {
@@ -154,18 +203,30 @@ private static void assertConnection(final YamlConnection connection, final Sema
     }
 
     YamlConnectionFactory dummyConnectionFactory(@Nonnull SemanticVersion version) {
+        return dummyMultiClusterConnectionFactory(version, List.of(CLUSTER_FILE));
+    }
+
+    YamlConnectionFactory dummyMultiClusterConnectionFactory(@Nonnull SemanticVersion version, @Nonnull List clusterFiles) {
         return new YamlConnectionFactory() {
             @Override
             public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
-                // Add query string to connection so we can tell where it came from
-                URI newPath = URI.create(connectPath + "?version=" + version);
-                return new SimpleYamlConnection(dummyConnection(newPath), version, CLUSTER_FILE);
+                if (clusterIndex < 0 || clusterIndex >= clusterFiles.size()) {
+                    throw new SQLException("Cluster index " + clusterIndex + " not available (only " +
+                            clusterFiles.size() + " clusters configured)");
+                }
+                URI newPath = URI.create(connectPath + "?version=" + version + "&cluster=" + clusterFiles.get(clusterIndex));
+                return new SimpleYamlConnection(dummyConnection(newPath), version, clusterFiles.get(clusterIndex));
             }
 
             @Override
             public Set getVersionsUnderTest() {
                 return Set.of(version);
             }
+
+            @Override
+            public int getAvailableClusterCount() {
+                return clusterFiles.size();
+            }
         };
     }
 

From 47d1f66f6c1ed25f586c819776f9b123964e0775 Mon Sep 17 00:00:00 2001
From: Scott Dugas 
Date: Fri, 3 Apr 2026 08:44:38 -0400
Subject: [PATCH 11/25] Encapsulate list of server+clusterFile in class

---
 .../yamltests/configs/EmbeddedConfig.java     |  39 +++---
 .../configs/ExternalMultiServerConfig.java    |   4 +-
 .../configs/JDBCInProcessConfig.java          |  29 +++--
 .../configs/JDBCMultiServerConfig.java        |  13 +-
 .../yamltests/connectionfactory/Clusters.java | 118 ++++++++++++++++++
 .../EmbeddedYamlConnectionFactory.java        |  49 ++------
 .../ExternalServerYamlConnectionFactory.java  |  26 ++--
 .../JDBCInProcessYamlConnectionFactory.java   |  45 +------
 8 files changed, 190 insertions(+), 133 deletions(-)
 create mode 100644 yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java

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 efe4960575..816b20a3da 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
@@ -21,14 +21,15 @@
 package com.apple.foundationdb.relational.yamltests.configs;
 
 import com.apple.foundationdb.relational.api.Options;
+import com.apple.foundationdb.relational.api.exceptions.RelationalException;
 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.connectionfactory.Clusters;
 import com.apple.foundationdb.relational.yamltests.connectionfactory.EmbeddedYamlConnectionFactory;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -42,7 +43,7 @@ public class EmbeddedConfig implements YamlTestConfig {
     @Nonnull
     private final List clusterFiles;
     @Nonnull
-    private final List frls = new ArrayList<>();
+    private Clusters clusters = Clusters.empty();
 
     public EmbeddedConfig(@Nullable final String clusterFile) {
         this(Collections.singletonList(clusterFile));
@@ -62,38 +63,34 @@ public void beforeAll() throws Exception {
                 .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 10)
                 .build();
         // The primary FRL registers its driver in DriverManager; additional ones do not
-        boolean first = true;
-        for (final String clusterFile : clusterFiles) {
-            if (first) {
-                frls.add(new FRL(options, clusterFile));
-                first = false;
-            } else {
-                frls.add(new FRL(options, clusterFile, false));
-            }
-        }
+        final String registeredCluster = clusterFiles.get(0);
+        clusters = Clusters.mapped(clusterFiles,
+                clusterFile -> {
+                    try {
+                        return new FRL(options, clusterFile, clusterFile == registeredCluster);
+                    } catch (RelationalException e) {
+                        throw e.toUncheckedWrappedException();
+                    }
+                });
     }
 
     @Override
     @SuppressWarnings("PMD.CloseResource") // FRLs are being closed in this loop
     public void afterAll() throws Exception {
-        for (final FRL frl : frls) {
-            frl.close();
+        for (final Clusters.Entry cluster : clusters) {
+            cluster.server().close();
         }
-        frls.clear();
+        clusters = Clusters.empty();
     }
 
     @Override
     public YamlConnectionFactory createConnectionFactory() {
-        final List clusterDrivers = new ArrayList<>();
-        for (int i = 0; i < frls.size(); i++) {
-            clusterDrivers.add(new EmbeddedYamlConnectionFactory.ClusterDriver(
-                    frls.get(i).getDriver(), clusterFiles.get(i)));
-        }
-        return new EmbeddedYamlConnectionFactory(clusterDrivers);
+        return new EmbeddedYamlConnectionFactory(clusters.map(FRL::getDriver));
     }
 
+    @Nonnull
     @Override
-    public @Nonnull YamlExecutionContext.ContextOptions getRunnerOptions() {
+    public YamlExecutionContext.ContextOptions getRunnerOptions() {
         return YamlExecutionContext.ContextOptions.EMPTY_OPTIONS;
     }
 
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java
index f5874ab9e1..824d4066cc 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java
@@ -59,8 +59,8 @@ public YamlConnectionFactory createConnectionFactory() {
         return new MultiServerConnectionFactory(
                 MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE,
                 initialConnection,
-                new ExternalServerYamlConnectionFactory(List.of(server0)),
-                List.of(new ExternalServerYamlConnectionFactory(List.of(server1))));
+                new ExternalServerYamlConnectionFactory(JDBCMultiServerConfig.toExternalClusters(List.of(server0))),
+                List.of(new ExternalServerYamlConnectionFactory(JDBCMultiServerConfig.toExternalClusters(List.of(server1)))));
     }
 
     @Override
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 e956fda0f0..4ab58cac23 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,10 +23,11 @@
 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.connectionfactory.Clusters;
 import com.apple.foundationdb.relational.yamltests.connectionfactory.JDBCInProcessYamlConnectionFactory;
 
 import javax.annotation.Nonnull;
-import java.util.ArrayList;
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -39,7 +40,7 @@ public class JDBCInProcessConfig implements YamlTestConfig {
     @Nonnull
     private final List clusterFiles;
     @Nonnull
-    private final List clusterServers = new ArrayList<>();
+    private Clusters clusters = Clusters.empty();
 
     public JDBCInProcessConfig(@Nonnull final List clusterFiles) {
         this.clusterFiles = clusterFiles;
@@ -48,28 +49,32 @@ 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 {
-        for (final String clusterFile : clusterFiles) {
-            final InProcessRelationalServer server = new InProcessRelationalServer(clusterFile).start();
-            clusterServers.add(new JDBCInProcessYamlConnectionFactory.ClusterServer(server, clusterFile));
-        }
+        clusters = Clusters.mapped(clusterFiles,
+                clusterFile -> {
+                    try {
+                        return new InProcessRelationalServer(clusterFile).start();
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
     }
 
     @Override
     public void afterAll() throws Exception {
-        for (final JDBCInProcessYamlConnectionFactory.ClusterServer cs : clusterServers) {
-            cs.server().close();
+        for (final Clusters.Entry cluster : clusters) {
+            cluster.server().close();
         }
-        clusterServers.clear();
+        clusters = Clusters.empty();
     }
 
     @Override
     public YamlConnectionFactory createConnectionFactory() {
-        return new JDBCInProcessYamlConnectionFactory(clusterServers);
+        return new JDBCInProcessYamlConnectionFactory(clusters);
     }
 
     @Nonnull
-    protected List getClusterServers() {
-        return clusterServers;
+    protected Clusters getClusters() {
+        return clusters;
     }
 
     @Nonnull
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java
index 21783d815d..e8dd8831e0 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java
@@ -21,6 +21,7 @@
 package com.apple.foundationdb.relational.yamltests.configs;
 
 import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory;
+import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters;
 import com.apple.foundationdb.relational.yamltests.connectionfactory.ExternalServerYamlConnectionFactory;
 import com.apple.foundationdb.relational.yamltests.connectionfactory.JDBCInProcessYamlConnectionFactory;
 import com.apple.foundationdb.relational.yamltests.connectionfactory.MultiServerConnectionFactory;
@@ -28,6 +29,7 @@
 
 import javax.annotation.Nonnull;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Run against an embedded JDBC driver, and an external server, alternating commands that go against each.
@@ -58,8 +60,8 @@ public YamlConnectionFactory createConnectionFactory() {
         return new MultiServerConnectionFactory(
                 MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE,
                 initialConnection,
-                new JDBCInProcessYamlConnectionFactory(getClusterServers()),
-                List.of(new ExternalServerYamlConnectionFactory(externalServers)));
+                new JDBCInProcessYamlConnectionFactory(getClusters()),
+                List.of(new ExternalServerYamlConnectionFactory(toExternalClusters(externalServers))));
     }
 
     @Override
@@ -71,4 +73,11 @@ public String toString() {
             return "MultiServer (" + primaryExternal.getVersion() + " then " + super.toString() + ")";
         }
     }
+
+    @Nonnull
+    static Clusters toExternalClusters(@Nonnull List servers) {
+        return new Clusters<>(servers.stream()
+                .map(s -> new Clusters.Entry<>(s, s.getClusterFile()))
+                .collect(Collectors.toList()));
+    }
 }
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java
new file mode 100644
index 0000000000..9e814c9b0d
--- /dev/null
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java
@@ -0,0 +1,118 @@
+/*
+ * Clusters.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.connectionfactory;
+
+import javax.annotation.Nonnull;
+import java.sql.SQLException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * An ordered, immutable collection of cluster entries, each pairing a server (or driver) of type {@code T}
+ * with the cluster file it is connected to. Must contain at least one entry.
+ *
+ * @param  the type of server or driver held by each entry
+ */
+public class Clusters implements Iterable> {
+    @Nonnull
+    private final List> entries;
+
+    public Clusters(@Nonnull List> entries) {
+        if (entries.isEmpty()) {
+            throw new IllegalArgumentException("At least one cluster entry is required");
+        }
+        this.entries = List.copyOf(entries);
+    }
+
+    public static  Clusters empty() {
+        return new Clusters<>(List.of());
+    }
+
+    public static  Clusters mapped(List clusterFiles, Function toServer) {
+        return new Clusters<>(clusterFiles.stream()
+                .map(clusterFile -> new Entry<>(toServer.apply(clusterFile), clusterFile))
+                .collect(Collectors.toList()));
+    }
+
+    public  Clusters map(Function mapper) {
+        return new Clusters<>(entries.stream()
+                .map(entry -> new Entry<>(mapper.apply(entry.server), entry.clusterFile))
+                .collect(Collectors.toList()));
+    }
+
+    /**
+     * Returns the number of clusters.
+     */
+    public int size() {
+        return entries.size();
+    }
+
+    /**
+     * Returns the entry at the given cluster index, after bounds-checking.
+     *
+     * @param clusterIndex the zero-based cluster index
+     * @return the entry at that index
+     * @throws SQLException if the index is out of range
+     */
+    @Nonnull
+    public Entry get(int clusterIndex) throws SQLException {
+        if (clusterIndex < 0 || clusterIndex >= entries.size()) {
+            throw new SQLException("Cluster index " + clusterIndex + " not available (only " +
+                    entries.size() + " clusters configured)");
+        }
+        return entries.get(clusterIndex);
+    }
+
+    @Override
+    @Nonnull
+    public Iterator> iterator() {
+        return entries.iterator();
+    }
+
+    /**
+     * A server (or driver) paired with its cluster file.
+     *
+     * @param  the type of server or driver
+     */
+    public static class Entry {
+        @Nonnull
+        private final T server;
+        @Nonnull
+        private final String clusterFile;
+
+        public Entry(@Nonnull T server, @Nonnull String clusterFile) {
+            this.server = server;
+            this.clusterFile = clusterFile;
+        }
+
+        @Nonnull
+        public T server() {
+            return server;
+        }
+
+        @Nonnull
+        public String clusterFile() {
+            return clusterFile;
+        }
+    }
+}
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java
index 42745a804f..c1cd69ecd6 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java
@@ -31,72 +31,39 @@
 import java.net.URI;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.util.List;
 import java.util.Set;
 
 public class EmbeddedYamlConnectionFactory implements YamlConnectionFactory {
     @Nonnull
-    private final List clusterDrivers;
+    private final Clusters clusters;
 
-    public EmbeddedYamlConnectionFactory(@Nonnull List clusterDrivers) {
-        if (clusterDrivers.isEmpty()) {
-            throw new IllegalArgumentException("At least one cluster driver is required");
-        }
-        this.clusterDrivers = clusterDrivers;
+    public EmbeddedYamlConnectionFactory(@Nonnull Clusters clusters) {
+        this.clusters = clusters;
     }
 
     @Override
     public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
-        if (clusterIndex < 0 || clusterIndex >= clusterDrivers.size()) {
-            throw new SQLException("Cluster index " + clusterIndex + " not available (only " +
-                    clusterDrivers.size() + " clusters configured)");
-        }
+        final Clusters.Entry entry = clusters.get(clusterIndex);
         if (clusterIndex == 0) {
             // The primary cluster's driver is registered in DriverManager, so use that path
             return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()),
-                    SemanticVersion.current(), "Embedded", clusterDrivers.get(0).clusterFile());
+                    SemanticVersion.current(), "Embedded", entry.clusterFile());
         }
         // Non-primary clusters are not registered in DriverManager, so connect via the driver directly
-        final ClusterDriver clusterDriver = clusterDrivers.get(clusterIndex);
         return new SimpleYamlConnection(
-                clusterDriver.driver().connect(connectPath, Options.NONE),
+                entry.server().connect(connectPath, Options.NONE),
                 SemanticVersion.current(),
                 "Embedded[cluster=" + clusterIndex + "]",
-                clusterDriver.clusterFile());
+                entry.clusterFile());
     }
 
     @Override
     public int getAvailableClusterCount() {
-        return clusterDrivers.size();
+        return clusters.size();
     }
 
     @Override
     public Set getVersionsUnderTest() {
         return Set.of(SemanticVersion.current());
     }
-
-    /**
-     * A driver associated with its cluster file.
-     */
-    public static class ClusterDriver {
-        @Nonnull
-        private final RelationalDriver driver;
-        @Nonnull
-        private final String clusterFile;
-
-        public ClusterDriver(@Nonnull RelationalDriver driver, @Nonnull String clusterFile) {
-            this.driver = driver;
-            this.clusterFile = clusterFile;
-        }
-
-        @Nonnull
-        public RelationalDriver driver() {
-            return driver;
-        }
-
-        @Nonnull
-        public String clusterFile() {
-            return clusterFile;
-        }
-    }
 }
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java
index a56a9d12df..1a18e263eb 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java
@@ -34,36 +34,30 @@
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.util.List;
 import java.util.Set;
 
 public class ExternalServerYamlConnectionFactory implements YamlConnectionFactory {
     private static final Logger LOG = LogManager.getLogger(ExternalServerYamlConnectionFactory.class);
     @Nonnull
-    private final List servers;
+    private final Clusters clusters;
 
-    public ExternalServerYamlConnectionFactory(@Nonnull List servers) {
-        if (servers.isEmpty()) {
-            throw new IllegalArgumentException("At least one external server is required");
-        }
-        this.servers = servers;
+    public ExternalServerYamlConnectionFactory(@Nonnull Clusters clusters) {
+        this.clusters = clusters;
     }
 
     @Override
     public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
-        if (clusterIndex < 0 || clusterIndex >= servers.size()) {
-            throw new SQLException("Cluster index " + clusterIndex + " not available (only " +
-                    servers.size() + " clusters configured)");
-        }
-        return createConnection(connectPath, servers.get(clusterIndex));
+        final Clusters.Entry entry = clusters.get(clusterIndex);
+        return createConnection(connectPath, entry.server(), entry.clusterFile());
     }
 
     @Override
     public int getAvailableClusterCount() {
-        return servers.size();
+        return clusters.size();
     }
 
-    private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull ExternalServer server) throws SQLException {
+    private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull ExternalServer server,
+                                            @Nonnull String clusterFile) throws SQLException {
         String uriStr = connectPath.toString().replaceFirst("embed:", "relational://localhost:" + server.getPort());
         if (LOG.isInfoEnabled()) {
             LOG.info(KeyValueLogMessage.of("Rewrote connection string for external server",
@@ -74,12 +68,12 @@ private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull Exter
 
         final Connection connection = DriverManager.getConnection(uriStr);
         server.validateConnectionVersion(connection);
-        return new SimpleYamlConnection(connection, server.getVersion(), server.getClusterFile());
+        return new SimpleYamlConnection(connection, server.getVersion(), clusterFile);
     }
 
     @Override
     public Set getVersionsUnderTest() {
-        return Set.of(servers.get(0).getVersion());
+        return Set.of(clusters.iterator().next().server().getVersion());
     }
 
 }
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java
index a3d20d90c7..0a932a3805 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java
@@ -34,34 +34,26 @@
 import java.net.URI;
 import java.sql.DriverManager;
 import java.sql.SQLException;
-import java.util.List;
 import java.util.Set;
 
 public class JDBCInProcessYamlConnectionFactory implements YamlConnectionFactory {
     private static final Logger LOG = LogManager.getLogger(JDBCInProcessYamlConnectionFactory.class);
     @Nonnull
-    private final List clusterServers;
+    private final Clusters clusters;
 
-    public JDBCInProcessYamlConnectionFactory(@Nonnull List clusterServers) {
-        if (clusterServers.isEmpty()) {
-            throw new IllegalArgumentException("At least one cluster server is required");
-        }
-        this.clusterServers = clusterServers;
+    public JDBCInProcessYamlConnectionFactory(@Nonnull Clusters clusters) {
+        this.clusters = clusters;
     }
 
     @Override
     public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException {
-        if (clusterIndex < 0 || clusterIndex >= clusterServers.size()) {
-            throw new SQLException("Cluster index " + clusterIndex + " not available (only " +
-                    clusterServers.size() + " clusters configured)");
-        }
-        final ClusterServer clusterServer = clusterServers.get(clusterIndex);
-        return createConnection(connectPath, clusterServer.server, clusterServer.clusterFile);
+        final Clusters.Entry entry = clusters.get(clusterIndex);
+        return createConnection(connectPath, entry.server(), entry.clusterFile());
     }
 
     @Override
     public int getAvailableClusterCount() {
-        return clusterServers.size();
+        return clusters.size();
     }
 
     private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull InProcessRelationalServer targetServer,
@@ -81,29 +73,4 @@ private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull InPro
     public Set getVersionsUnderTest() {
         return Set.of(SemanticVersion.current());
     }
-
-    /**
-     * A server associated with its cluster file.
-     */
-    public static class ClusterServer {
-        @Nonnull
-        private final InProcessRelationalServer server;
-        @Nonnull
-        private final String clusterFile;
-
-        public ClusterServer(@Nonnull InProcessRelationalServer server, @Nonnull String clusterFile) {
-            this.server = server;
-            this.clusterFile = clusterFile;
-        }
-
-        @Nonnull
-        public InProcessRelationalServer server() {
-            return server;
-        }
-
-        @Nonnull
-        public String clusterFile() {
-            return clusterFile;
-        }
-    }
 }

From 80a435a489f3396f83cff520b87995cc8be89e96 Mon Sep 17 00:00:00 2001
From: Scott Dugas 
Date: Fri, 3 Apr 2026 10:37:26 -0400
Subject: [PATCH 12/25] Reuse Clusters in more places

---
 .../yamltests/YamlTestExtension.java          | 30 +++++++++++--------
 .../yamltests/configs/EmbeddedConfig.java     |  5 ++--
 .../configs/ExternalMultiServerConfig.java    |  5 ++--
 .../configs/JDBCInProcessConfig.java          |  2 +-
 .../configs/JDBCMultiServerConfig.java        | 22 ++++----------
 .../yamltests/connectionfactory/Clusters.java | 11 ++++++-
 6 files changed, 40 insertions(+), 35 deletions(-)

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 f0cc746e35..e9a91f6694 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
@@ -30,6 +30,7 @@
 import com.apple.foundationdb.relational.yamltests.configs.JDBCMultiServerConfig;
 import com.apple.foundationdb.relational.yamltests.configs.ShowPlanOnDiff;
 import com.apple.foundationdb.relational.yamltests.configs.YamlTestConfig;
+import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters;
 import com.apple.foundationdb.relational.yamltests.server.ExternalServer;
 import com.apple.foundationdb.test.FDBTestEnvironment;
 import com.google.common.collect.Iterables;
@@ -65,9 +66,9 @@ public class YamlTestExtension implements TestTemplateInvocationContextProvider,
     private static final Logger logger = LogManager.getLogger(YamlTestExtension.class);
     private List testConfigs;
     private List maintainConfigs;
-    /** External servers grouped by jar version. Each inner list has one server per cluster file (same order as {@link #clusterFiles}). */
+    /** External servers grouped by jar version. Each {@link Clusters} has one server per cluster file (same order as {@link #clusterFiles}). */
     @Nullable
-    private List> externalServerGroups;
+    private List> externalServerGroups;
     @Nonnull
     private final List clusterFiles;
     private final boolean includeMethodInDescriptions;
@@ -122,12 +123,17 @@ public void beforeAll(final ExtensionContext context) throws Exception {
             externalServerGroups = new ArrayList<>();
             List allExternalServers = new ArrayList<>();
             for (File jar : jars) {
-                List group = new ArrayList<>();
-                for (String cf : clusterFiles) {
-                    group.add(new ExternalServer(jar, cf));
-                }
+                Clusters group = Clusters.fromClusterFiles(clusterFiles, cf -> {
+                    try {
+                        return new ExternalServer(jar, cf);
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                });
                 externalServerGroups.add(group);
-                allExternalServers.addAll(group);
+                for (Clusters.Entry entry : group) {
+                    allExternalServers.add(entry.server());
+                }
             }
             ExternalServer.startMultiple(allExternalServers);
             final boolean mixedModeOnly = Boolean.parseBoolean(System.getProperty("tests.mixedModeOnly", "false"));
@@ -157,7 +163,7 @@ private Stream localConfigs(final boolean mixedModeOnly, final b
     private Stream externalServerConfigs(final boolean singleExternalVersionOnly) {
         if (singleExternalVersionOnly) {
             return externalServerGroups.stream()
-                    .map(group -> group.get(0))
+                    .map(group -> group.iterator().next().server())
                     // Create an ExternalServer config with two servers of the same version for each server
                     // (with and without forced continuations)
                     .flatMap(server ->
@@ -191,13 +197,13 @@ public void afterAll(final ExtensionContext context) throws Exception {
                             }
                         }).filter(Objects::nonNull).findFirst();
         if (externalServerGroups != null) {
-            for (List group : externalServerGroups) {
-                for (ExternalServer server : group) {
+            for (Clusters group : externalServerGroups) {
+                for (Clusters.Entry entry : group) {
                     try {
-                        server.stop();
+                        entry.server().stop();
                     } catch (Exception ex) {
                         if (logger.isWarnEnabled()) {
-                            logger.warn("Failed to stop server " + server.getVersion() + " on " + server.getPort());
+                            logger.warn("Failed to stop server " + entry.server().getVersion() + " on " + entry.server().getPort());
                         }
                     }
                 }
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 816b20a3da..0aa371baed 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
@@ -32,6 +32,7 @@
 import javax.annotation.Nullable;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Run directly against an instance of {@link FRL}.
@@ -64,10 +65,10 @@ public void beforeAll() throws Exception {
                 .build();
         // The primary FRL registers its driver in DriverManager; additional ones do not
         final String registeredCluster = clusterFiles.get(0);
-        clusters = Clusters.mapped(clusterFiles,
+        clusters = Clusters.fromClusterFiles(clusterFiles,
                 clusterFile -> {
                     try {
-                        return new FRL(options, clusterFile, clusterFile == registeredCluster);
+                        return new FRL(options, clusterFile, Objects.equals(clusterFile, registeredCluster));
                     } catch (RelationalException e) {
                         throw e.toUncheckedWrappedException();
                     }
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java
index 824d4066cc..c066d054e2 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java
@@ -22,6 +22,7 @@
 
 import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory;
 import com.apple.foundationdb.relational.yamltests.YamlExecutionContext;
+import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters;
 import com.apple.foundationdb.relational.yamltests.connectionfactory.ExternalServerYamlConnectionFactory;
 import com.apple.foundationdb.relational.yamltests.connectionfactory.MultiServerConnectionFactory;
 import com.apple.foundationdb.relational.yamltests.server.ExternalServer;
@@ -59,8 +60,8 @@ public YamlConnectionFactory createConnectionFactory() {
         return new MultiServerConnectionFactory(
                 MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE,
                 initialConnection,
-                new ExternalServerYamlConnectionFactory(JDBCMultiServerConfig.toExternalClusters(List.of(server0))),
-                List.of(new ExternalServerYamlConnectionFactory(JDBCMultiServerConfig.toExternalClusters(List.of(server1)))));
+                new ExternalServerYamlConnectionFactory(Clusters.fromServers(List.of(server0), ExternalServer::getClusterFile)),
+                List.of(new ExternalServerYamlConnectionFactory(Clusters.fromServers(List.of(server1), ExternalServer::getClusterFile))));
     }
 
     @Override
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 4ab58cac23..ca8b1434b1 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
@@ -49,7 +49,7 @@ 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 {
-        clusters = Clusters.mapped(clusterFiles,
+        clusters = Clusters.fromClusterFiles(clusterFiles,
                 clusterFile -> {
                     try {
                         return new InProcessRelationalServer(clusterFile).start();
diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java
index e8dd8831e0..1f2ef05638 100644
--- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java
+++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java
@@ -29,26 +29,21 @@
 
 import javax.annotation.Nonnull;
 import java.util.List;
-import java.util.stream.Collectors;
 
 /**
  * Run against an embedded JDBC driver, and an external server, alternating commands that go against each.
  * 

* Multi-cluster support (in-process servers for all cluster files) is inherited from - * {@link JDBCInProcessConfig}. The external servers list should have one entry per cluster file + * {@link JDBCInProcessConfig}. The external server clusters should have one entry per cluster file * (matching the in-process servers), so that cluster-specific connections also alternate. */ public class JDBCMultiServerConfig extends JDBCInProcessConfig { @Nonnull - private final List externalServers; + private final Clusters externalServers; private final int initialConnection; - public JDBCMultiServerConfig(final int initialConnection, @Nonnull List externalServers) { - this(initialConnection, externalServers, List.of()); - } - - public JDBCMultiServerConfig(final int initialConnection, @Nonnull List externalServers, + public JDBCMultiServerConfig(final int initialConnection, @Nonnull Clusters externalServers, @Nonnull final List clusterFiles) { super(clusterFiles); this.initialConnection = initialConnection; @@ -61,23 +56,16 @@ public YamlConnectionFactory createConnectionFactory() { MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE, initialConnection, new JDBCInProcessYamlConnectionFactory(getClusters()), - List.of(new ExternalServerYamlConnectionFactory(toExternalClusters(externalServers)))); + List.of(new ExternalServerYamlConnectionFactory(externalServers))); } @Override public String toString() { - final ExternalServer primaryExternal = externalServers.get(0); + final ExternalServer primaryExternal = externalServers.iterator().next().server(); if (initialConnection == 0) { return "MultiServer (" + super.toString() + " then " + primaryExternal.getVersion() + ")"; } else { return "MultiServer (" + primaryExternal.getVersion() + " then " + super.toString() + ")"; } } - - @Nonnull - static Clusters toExternalClusters(@Nonnull List servers) { - return new Clusters<>(servers.stream() - .map(s -> new Clusters.Entry<>(s, s.getClusterFile())) - .collect(Collectors.toList())); - } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index 9e814c9b0d..810bdb4d87 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -48,12 +48,21 @@ public static Clusters empty() { return new Clusters<>(List.of()); } - public static Clusters mapped(List clusterFiles, Function toServer) { + public static Clusters fromClusterFiles(List clusterFiles, Function toServer) { return new Clusters<>(clusterFiles.stream() .map(clusterFile -> new Entry<>(toServer.apply(clusterFile), clusterFile)) .collect(Collectors.toList())); } + /** + * Create a {@code Clusters} from a list of items that already carry their cluster file. + */ + public static Clusters fromServers(@Nonnull List items, @Nonnull Function clusterFileExtractor) { + return new Clusters<>(items.stream() + .map(item -> new Entry<>(item, clusterFileExtractor.apply(item))) + .collect(Collectors.toList())); + } + public Clusters map(Function mapper) { return new Clusters<>(entries.stream() .map(entry -> new Entry<>(mapper.apply(entry.server), entry.clusterFile)) From 7d047a33288b71d8afc5b91347628af7fbf4d99c Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 3 Apr 2026 10:42:20 -0400 Subject: [PATCH 13/25] Add Clusters.primary() --- .../relational/yamltests/YamlTestExtension.java | 2 +- .../yamltests/configs/JDBCMultiServerConfig.java | 2 +- .../relational/yamltests/connectionfactory/Clusters.java | 8 ++++++++ .../ExternalServerYamlConnectionFactory.java | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) 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 e9a91f6694..db474133c3 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 @@ -163,7 +163,7 @@ private Stream localConfigs(final boolean mixedModeOnly, final b private Stream externalServerConfigs(final boolean singleExternalVersionOnly) { if (singleExternalVersionOnly) { return externalServerGroups.stream() - .map(group -> group.iterator().next().server()) + .map(group -> group.primary().server()) // Create an ExternalServer config with two servers of the same version for each server // (with and without forced continuations) .flatMap(server -> diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java index 1f2ef05638..5313e662a2 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java @@ -61,7 +61,7 @@ public YamlConnectionFactory createConnectionFactory() { @Override public String toString() { - final ExternalServer primaryExternal = externalServers.iterator().next().server(); + final ExternalServer primaryExternal = externalServers.primary().server(); if (initialConnection == 0) { return "MultiServer (" + super.toString() + " then " + primaryExternal.getVersion() + ")"; } else { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index 810bdb4d87..e149f5a303 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -69,6 +69,14 @@ public Clusters map(Function mapper) { .collect(Collectors.toList())); } + /** + * Returns the first (primary) entry. + */ + @Nonnull + public Entry primary() { + return entries.get(0); + } + /** * Returns the number of clusters. */ diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java index 1a18e263eb..affb2abf61 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java @@ -73,7 +73,7 @@ private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull Exter @Override public Set getVersionsUnderTest() { - return Set.of(clusters.iterator().next().server().getVersion()); + return Set.of(clusters.primary().server().getVersion()); } } From 1ac4cdaa4a74983683001b6378a8a1fa084a4e7c Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 3 Apr 2026 10:49:19 -0400 Subject: [PATCH 14/25] Cleanup JDBCMultiServerConfig Clusters usage --- .../relational/yamltests/YamlTestExtension.java | 8 ++++---- .../yamltests/configs/JDBCMultiServerConfig.java | 5 ++--- .../relational/yamltests/connectionfactory/Clusters.java | 8 ++++++++ 3 files changed, 14 insertions(+), 7 deletions(-) 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 db474133c3..2c5dc105c9 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 @@ -172,10 +172,10 @@ private Stream externalServerConfigs(final boolean singleExterna } else { return externalServerGroups.stream().flatMap(group -> // (4 configs for each server version available) - Stream.of(new JDBCMultiServerConfig(0, group, clusterFiles), - new ForceContinuations(new JDBCMultiServerConfig(0, group, clusterFiles)), - new JDBCMultiServerConfig(1, group, clusterFiles), - new ForceContinuations(new JDBCMultiServerConfig(1, group, clusterFiles)))); + Stream.of(new JDBCMultiServerConfig(0, group), + new ForceContinuations(new JDBCMultiServerConfig(0, group)), + new JDBCMultiServerConfig(1, group), + new ForceContinuations(new JDBCMultiServerConfig(1, group)))); } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java index 5313e662a2..a43f3fd8ce 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java @@ -43,9 +43,8 @@ public class JDBCMultiServerConfig extends JDBCInProcessConfig { private final Clusters externalServers; private final int initialConnection; - public JDBCMultiServerConfig(final int initialConnection, @Nonnull Clusters externalServers, - @Nonnull final List clusterFiles) { - super(clusterFiles); + public JDBCMultiServerConfig(final int initialConnection, @Nonnull Clusters externalServers) { + super(externalServers.clusterFiles()); this.initialConnection = initialConnection; this.externalServers = externalServers; } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index e149f5a303..2a1dbaac85 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -77,6 +77,14 @@ public Entry primary() { return entries.get(0); } + /** + * Returns the cluster files in order. + */ + @Nonnull + public List clusterFiles() { + return entries.stream().map(Entry::clusterFile).collect(Collectors.toList()); + } + /** * Returns the number of clusters. */ From 07d0adead0988b7310d83db9c90a43d94c421493 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 3 Apr 2026 10:51:16 -0400 Subject: [PATCH 15/25] Remove unnecessary constructor --- .../foundationdb/relational/yamltests/ConnectionTarget.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java index 23752e2fb0..8703fa2ca9 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java @@ -43,10 +43,6 @@ public ConnectionTarget(@Nonnull URI uri, int clusterIndex) { this.clusterIndex = clusterIndex; } - public ConnectionTarget(@Nonnull URI uri) { - this(uri, 0); - } - @Nonnull public URI getUri() { return uri; From e024ded3bc2ac1c5df39f1086dafa42c1f63f766 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 3 Apr 2026 11:08:17 -0400 Subject: [PATCH 16/25] Shuffle the order of the cluster files for yaml tests --- .../com/apple/foundationdb/test/FDBTestEnvironment.java | 7 +++++++ .../relational/yamltests/YamlTestExtension.java | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/fdb-test-utils/src/main/java/com/apple/foundationdb/test/FDBTestEnvironment.java b/fdb-test-utils/src/main/java/com/apple/foundationdb/test/FDBTestEnvironment.java index f3d628d793..b8b94ca377 100644 --- a/fdb-test-utils/src/main/java/com/apple/foundationdb/test/FDBTestEnvironment.java +++ b/fdb-test-utils/src/main/java/com/apple/foundationdb/test/FDBTestEnvironment.java @@ -26,6 +26,7 @@ import javax.annotation.Nullable; import java.io.FileInputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -73,6 +74,12 @@ public static List allClusterFiles() { return clusterFiles; } + public static List allClusterFilesInRandomOrder() { + final List randomized = new ArrayList<>(clusterFiles); + Collections.shuffle(randomized); + return List.copyOf(randomized); + } + public static String randomClusterFile() { return clusterFiles.get(ThreadLocalRandom.current().nextInt(clusterFiles.size())); } 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 2c5dc105c9..bbaaa2f6d1 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 @@ -75,7 +75,7 @@ public class YamlTestExtension implements TestTemplateInvocationContextProvider, @SuppressWarnings("unused") // Used implicitly with @ExtendWith(YamlTestExtension.class) public YamlTestExtension() { - this(FDBTestEnvironment.allClusterFiles(), false); + this(FDBTestEnvironment.allClusterFilesInRandomOrder(), false); } /** From caf94fa7a28ffb231eff80efc5630c2557475cba Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 3 Apr 2026 11:08:30 -0400 Subject: [PATCH 17/25] Fix Clusters.empty() --- .../relational/yamltests/connectionfactory/Clusters.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index 2a1dbaac85..3efaf4a088 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -37,6 +37,11 @@ public class Clusters implements Iterable> { @Nonnull private final List> entries; + /** Private constructor to support the static {@link Clusters#empty()} **/ + private Clusters() { + this.entries = List.of(); + } + public Clusters(@Nonnull List> entries) { if (entries.isEmpty()) { throw new IllegalArgumentException("At least one cluster entry is required"); @@ -45,7 +50,7 @@ public Clusters(@Nonnull List> entries) { } public static Clusters empty() { - return new Clusters<>(List.of()); + return new Clusters<>(); } public static Clusters fromClusterFiles(List clusterFiles, Function toServer) { From 7c693f1c31c1db767ba8dd85d2f0b52a0b8309d9 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 3 Apr 2026 13:40:10 -0400 Subject: [PATCH 18/25] Fix javadoc --- .../relational/yamltests/connectionfactory/Clusters.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index 3efaf4a088..03d8c71254 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -37,7 +37,7 @@ public class Clusters implements Iterable> { @Nonnull private final List> entries; - /** Private constructor to support the static {@link Clusters#empty()} **/ + /** Private constructor to support the static {@link Clusters#empty()}. */ private Clusters() { this.entries = List.of(); } From 1683e92b87d2bcf42f3ceb606f3becb9736a6662 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 3 Apr 2026 14:00:56 -0400 Subject: [PATCH 19/25] Fix singleVersionTest --- .../yamltests/YamlTestExtension.java | 9 ++++---- .../configs/ExternalMultiServerConfig.java | 22 +++++++++++-------- 2 files changed, 17 insertions(+), 14 deletions(-) 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 bbaaa2f6d1..5081746eee 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 @@ -163,12 +163,11 @@ private Stream localConfigs(final boolean mixedModeOnly, final b private Stream externalServerConfigs(final boolean singleExternalVersionOnly) { if (singleExternalVersionOnly) { return externalServerGroups.stream() - .map(group -> group.primary().server()) - // Create an ExternalServer config with two servers of the same version for each server + // Create an ExternalServer config with two connections to the same servers for each version // (with and without forced continuations) - .flatMap(server -> - Stream.of(new ExternalMultiServerConfig(0, server, server), - new ForceContinuations(new ExternalMultiServerConfig(0, server, server)))); + .flatMap(group -> + Stream.of(new ExternalMultiServerConfig(0, group, group), + new ForceContinuations(new ExternalMultiServerConfig(0, group, group)))); } else { return externalServerGroups.stream().flatMap(group -> // (4 configs for each server version available) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java index c066d054e2..123832d829 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java @@ -37,14 +37,18 @@ public class ExternalMultiServerConfig implements YamlTestConfig { private final int initialConnection; - private final ExternalServer server0; - private final ExternalServer server1; + @Nonnull + private final Clusters servers0; + @Nonnull + private final Clusters servers1; - public ExternalMultiServerConfig(final int initialConnection, ExternalServer server0, ExternalServer server1) { + public ExternalMultiServerConfig(final int initialConnection, + @Nonnull Clusters servers0, + @Nonnull Clusters servers1) { super(); this.initialConnection = initialConnection; - this.server0 = server0; - this.server1 = server1; + this.servers0 = servers0; + this.servers1 = servers1; } @Override @@ -60,16 +64,16 @@ public YamlConnectionFactory createConnectionFactory() { return new MultiServerConnectionFactory( MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE, initialConnection, - new ExternalServerYamlConnectionFactory(Clusters.fromServers(List.of(server0), ExternalServer::getClusterFile)), - List.of(new ExternalServerYamlConnectionFactory(Clusters.fromServers(List.of(server1), ExternalServer::getClusterFile)))); + new ExternalServerYamlConnectionFactory(servers0), + List.of(new ExternalServerYamlConnectionFactory(servers1))); } @Override public String toString() { if (initialConnection == 0) { - return "MultiServer (" + server0.getVersion() + " then " + server1.getVersion() + ")"; + return "MultiServer (" + servers0.primary().server().getVersion() + " then " + servers1.primary().server().getVersion() + ")"; } else { - return "MultiServer (" + server1.getVersion() + " then " + server0.getVersion() + ")"; + return "MultiServer (" + servers1.primary().server().getVersion() + " then " + servers0.primary().server().getVersion() + ")"; } } From fa0d4e1467165589fde934734c0576018e229dec Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 3 Apr 2026 14:41:13 -0400 Subject: [PATCH 20/25] Extract helper to decrease nesting --- .../yamltests/YamlTestExtension.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 5081746eee..b2459edc0b 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 @@ -198,13 +198,7 @@ public void afterAll(final ExtensionContext context) throws Exception { if (externalServerGroups != null) { for (Clusters group : externalServerGroups) { for (Clusters.Entry entry : group) { - try { - entry.server().stop(); - } catch (Exception ex) { - if (logger.isWarnEnabled()) { - logger.warn("Failed to stop server " + entry.server().getVersion() + " on " + entry.server().getPort()); - } - } + stopServerSafely(entry); } } externalServerGroups = null; @@ -214,6 +208,16 @@ public void afterAll(final ExtensionContext context) throws Exception { } } + private static void stopServerSafely(final Clusters.Entry entry) { + try { + entry.server().stop(); + } catch (Exception ex) { + if (logger.isWarnEnabled()) { + logger.warn("Failed to stop server " + entry.server().getVersion() + " on " + entry.server().getPort()); + } + } + } + @Override public boolean supportsTestTemplate(final ExtensionContext context) { return true; // TODO check type From eb5448623343be73abdadac96b51ec542591adfd Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 10 Apr 2026 10:49:13 -0400 Subject: [PATCH 21/25] Address easy PR comments Fixing names, javadoc, nullability annotations, removing unused code, using identical super method. --- .../apple/foundationdb/relational/server/FRL.java | 14 +++++++------- .../foundationdb/test/FDBTestEnvironment.java | 2 +- .../relational/yamltests/ConnectionTarget.java | 8 ++++++++ .../relational/yamltests/YamlTestExtension.java | 5 ++--- .../yamltests/configs/EmbeddedConfig.java | 6 ++---- .../yamltests/configs/JDBCMultiServerConfig.java | 3 +-- .../yamltests/connectionfactory/Clusters.java | 9 --------- 7 files changed, 21 insertions(+), 26 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 3c485a5785..bc07f7b266 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,7 +79,7 @@ @API(API.Status.EXPERIMENTAL) public class FRL implements AutoCloseable { private final FdbConnection fdbDatabase; - private final RelationalDriver registeredDriver; + private final RelationalDriver driver; private boolean registeredJDBCEmbedDriver; public FRL() throws RelationalException { @@ -118,7 +118,7 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis .setStoreCatalog(storeCatalog).build(); try { - this.registeredDriver = new EmbeddedRelationalDriver(RecordLayerEngine.makeEngine( + this.driver = new EmbeddedRelationalDriver(RecordLayerEngine.makeEngine( rlConfig, Collections.singletonList(fdbDb), keySpace, @@ -135,7 +135,7 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis .build())); if (registerDriver) { - DriverManager.registerDriver(this.registeredDriver); + DriverManager.registerDriver(this.driver); this.registeredJDBCEmbedDriver = true; } } catch (SQLException ve) { @@ -144,7 +144,7 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } public RelationalDriver getDriver() { - return registeredDriver; + return driver; } @SuppressWarnings("AbbreviationAsWordInName") // allow JDBCURI, though perhaps we should update this to make it clearer @@ -214,7 +214,7 @@ public Response execute(String database, String schema, String sql, List allClusterFiles() { public static List allClusterFilesInRandomOrder() { final List randomized = new ArrayList<>(clusterFiles); Collections.shuffle(randomized); - return List.copyOf(randomized); + return randomized; } public static String randomClusterFile() { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java index 8703fa2ca9..1a19d90191 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/ConnectionTarget.java @@ -32,6 +32,14 @@ *

{@code
  * connect: { cluster: 1, uri: 0 }
  * }
+ * The uri component can be a few different things: + *
    + *
  • An index, in which case {@code 0} is the catalog, and other positive numbers refer to the schemas created + * automatically with the {@code schema_template:} block
  • + *
  • A fully qualified uri such as {@code "jdbc:embed:/FRL/MCI_DB?schema=S1"} or + * {@code "jdbc:embed:/__SYS?schema=CATALOG"}. The scheme should always be {@code jdbc:embed:}, and the framework + * will update it to control whether it goes to the embedded connection, or one of the servers.
  • + *
*/ public class ConnectionTarget { @Nonnull 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 b2459edc0b..28f2229d70 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 @@ -80,13 +80,12 @@ public YamlTestExtension() { /** * Create a new extension with some configuration. - * @param clusterFile a custom cluster file to use as the primary, or {@code null} to inherit it from the - * environment, namely {@code FDB_CLUSTER_FILE}. + * @param clusterFile a custom cluster file to use as the primary. * @param includeMethodInDescriptions Set this to {@code true} if publishing test results to something that cannot * handle complex test hierarchies. In the record layer we maintain the full hierarchy in the output, so this is not * necessary, but if integrating some other tools this might be necessary. */ - public YamlTestExtension(@Nullable final String clusterFile, final boolean includeMethodInDescriptions) { + public YamlTestExtension(@Nonnull final String clusterFile, final boolean includeMethodInDescriptions) { this(List.of(clusterFile), includeMethodInDescriptions); } 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 0aa371baed..ca32e967a9 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 @@ -29,8 +29,6 @@ import com.apple.foundationdb.relational.yamltests.connectionfactory.EmbeddedYamlConnectionFactory; import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -46,8 +44,8 @@ public class EmbeddedConfig implements YamlTestConfig { @Nonnull private Clusters clusters = Clusters.empty(); - public EmbeddedConfig(@Nullable final String clusterFile) { - this(Collections.singletonList(clusterFile)); + public EmbeddedConfig(@Nonnull final String clusterFile) { + this(List.of(clusterFile)); } public EmbeddedConfig(@Nonnull final List clusterFiles) { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java index a43f3fd8ce..8468d3598c 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java @@ -23,7 +23,6 @@ import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.apple.foundationdb.relational.yamltests.connectionfactory.ExternalServerYamlConnectionFactory; -import com.apple.foundationdb.relational.yamltests.connectionfactory.JDBCInProcessYamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.connectionfactory.MultiServerConnectionFactory; import com.apple.foundationdb.relational.yamltests.server.ExternalServer; @@ -54,7 +53,7 @@ public YamlConnectionFactory createConnectionFactory() { return new MultiServerConnectionFactory( MultiServerConnectionFactory.ConnectionSelectionPolicy.ALTERNATE, initialConnection, - new JDBCInProcessYamlConnectionFactory(getClusters()), + super.createConnectionFactory(), List.of(new ExternalServerYamlConnectionFactory(externalServers))); } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index 03d8c71254..1ebbc7e61d 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -59,15 +59,6 @@ public static Clusters fromClusterFiles(List clusterFiles, Functi .collect(Collectors.toList())); } - /** - * Create a {@code Clusters} from a list of items that already carry their cluster file. - */ - public static Clusters fromServers(@Nonnull List items, @Nonnull Function clusterFileExtractor) { - return new Clusters<>(items.stream() - .map(item -> new Entry<>(item, clusterFileExtractor.apply(item))) - .collect(Collectors.toList())); - } - public Clusters map(Function mapper) { return new Clusters<>(entries.stream() .map(entry -> new Entry<>(mapper.apply(entry.server), entry.clusterFile)) From c552435e1030ebd3e611f7b5ede45032d504f9b7 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 10 Apr 2026 11:25:31 -0400 Subject: [PATCH 22/25] Make ExternalServer.clusterFile @Nonnull --- .../relational/yamltests/server/ExternalServer.java | 4 ++-- .../relational/yamltests/server/ExternalServerTest.java | 3 ++- 2 files changed, 4 insertions(+), 3 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 7daed914e9..54be89ca5d 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 @@ -65,7 +65,7 @@ public class ExternalServer { private int httpPort; private final SemanticVersion version; private Process serverProcess; - @Nullable + @Nonnull private final String clusterFile; /** @@ -74,7 +74,7 @@ public class ExternalServer { * @param serverJar the path to the jar to run */ public ExternalServer(@Nonnull final File serverJar, - @Nullable final String clusterFile) throws IOException { + @Nonnull final String clusterFile) throws IOException { this.clusterFile = clusterFile; this.serverJar = serverJar; 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 a171bf70d7..183a5acbb5 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 @@ -48,7 +48,8 @@ void setUp() throws IOException { private static File getCurrentServerPath() throws IOException { final List availableServers = ExternalServer.getAvailableServers(); for (File path : availableServers) { - if (new ExternalServer(path, null).getVersion().equals(SemanticVersion.current())) { + if (new ExternalServer(path, FDBTestEnvironment.randomClusterFile()) + .getVersion().equals(SemanticVersion.current())) { return path; } } From 3bc08b420035ad7532dd1efb8ef0231455aa48ef Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 10 Apr 2026 11:25:57 -0400 Subject: [PATCH 23/25] Clarify Clusters javadoc --- .../relational/yamltests/connectionfactory/Clusters.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index 1ebbc7e61d..c8fe9cf6b1 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -29,7 +29,8 @@ /** * An ordered, immutable collection of cluster entries, each pairing a server (or driver) of type {@code T} - * with the cluster file it is connected to. Must contain at least one entry. + * with the cluster file it is connected to. Each server (or driver) must be identical, except that it is pointing to a + * different cluster file. Must contain at least one entry. * * @param the type of server or driver held by each entry */ From ce4969ea2a25f1307a726ed311009634d4c264c7 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Fri, 10 Apr 2026 11:30:51 -0400 Subject: [PATCH 24/25] Add interface for things bound to a cluster This ends up being slightly less useful than maybe desired, because most of the things that Clusters holds are production classes, and I don't want to add a new interface there (and we're not writing swift code) --- .../yamltests/YamlTestExtension.java | 14 +++--- .../yamltests/configs/EmbeddedConfig.java | 6 +-- .../configs/ExternalMultiServerConfig.java | 4 +- .../configs/JDBCInProcessConfig.java | 6 +-- .../configs/JDBCMultiServerConfig.java | 2 +- .../yamltests/connectionfactory/Clusters.java | 44 +++++++++++++------ .../EmbeddedYamlConnectionFactory.java | 4 +- .../ExternalServerYamlConnectionFactory.java | 10 ++--- .../JDBCInProcessYamlConnectionFactory.java | 4 +- .../yamltests/server/ExternalServer.java | 6 ++- 10 files changed, 59 insertions(+), 41 deletions(-) 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 28f2229d70..320b4e3c4c 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 @@ -130,8 +130,8 @@ public void beforeAll(final ExtensionContext context) throws Exception { } }); externalServerGroups.add(group); - for (Clusters.Entry entry : group) { - allExternalServers.add(entry.server()); + for (ExternalServer server : group) { + allExternalServers.add(server); } } ExternalServer.startMultiple(allExternalServers); @@ -196,8 +196,8 @@ public void afterAll(final ExtensionContext context) throws Exception { }).filter(Objects::nonNull).findFirst(); if (externalServerGroups != null) { for (Clusters group : externalServerGroups) { - for (Clusters.Entry entry : group) { - stopServerSafely(entry); + for (ExternalServer server : group) { + stopServerSafely(server); } } externalServerGroups = null; @@ -207,12 +207,12 @@ public void afterAll(final ExtensionContext context) throws Exception { } } - private static void stopServerSafely(final Clusters.Entry entry) { + private static void stopServerSafely(final ExternalServer server) { try { - entry.server().stop(); + server.stop(); } catch (Exception ex) { if (logger.isWarnEnabled()) { - logger.warn("Failed to stop server " + entry.server().getVersion() + " on " + entry.server().getPort()); + logger.warn("Failed to stop server " + server.getVersion() + " on " + server.getPort()); } } } 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 ca32e967a9..5a1d20b175 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 @@ -42,7 +42,7 @@ public class EmbeddedConfig implements YamlTestConfig { @Nonnull private final List clusterFiles; @Nonnull - private Clusters clusters = Clusters.empty(); + private Clusters> clusters = Clusters.empty(); public EmbeddedConfig(@Nonnull final String clusterFile) { this(List.of(clusterFile)); @@ -63,7 +63,7 @@ public void beforeAll() throws Exception { .build(); // The primary FRL registers its driver in DriverManager; additional ones do not final String registeredCluster = clusterFiles.get(0); - clusters = Clusters.fromClusterFiles(clusterFiles, + clusters = Clusters.fromClusterFilesAsEntries(clusterFiles, clusterFile -> { try { return new FRL(options, clusterFile, Objects.equals(clusterFile, registeredCluster)); @@ -84,7 +84,7 @@ public void afterAll() throws Exception { @Override public YamlConnectionFactory createConnectionFactory() { - return new EmbeddedYamlConnectionFactory(clusters.map(FRL::getDriver)); + return new EmbeddedYamlConnectionFactory(clusters.map(e -> Clusters.mapEntry(e, FRL::getDriver))); } @Nonnull diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java index 123832d829..9c05045121 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java @@ -71,9 +71,9 @@ public YamlConnectionFactory createConnectionFactory() { @Override public String toString() { if (initialConnection == 0) { - return "MultiServer (" + servers0.primary().server().getVersion() + " then " + servers1.primary().server().getVersion() + ")"; + return "MultiServer (" + servers0.primary().getVersion() + " then " + servers1.primary().getVersion() + ")"; } else { - return "MultiServer (" + servers1.primary().server().getVersion() + " then " + servers0.primary().server().getVersion() + ")"; + return "MultiServer (" + servers1.primary().getVersion() + " then " + servers0.primary().getVersion() + ")"; } } 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 ca8b1434b1..d7bef4b69e 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 @@ -40,7 +40,7 @@ public class JDBCInProcessConfig implements YamlTestConfig { @Nonnull private final List clusterFiles; @Nonnull - private Clusters clusters = Clusters.empty(); + private Clusters> clusters = Clusters.empty(); public JDBCInProcessConfig(@Nonnull final List clusterFiles) { this.clusterFiles = clusterFiles; @@ -49,7 +49,7 @@ 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 { - clusters = Clusters.fromClusterFiles(clusterFiles, + clusters = Clusters.fromClusterFilesAsEntries(clusterFiles, clusterFile -> { try { return new InProcessRelationalServer(clusterFile).start(); @@ -73,7 +73,7 @@ public YamlConnectionFactory createConnectionFactory() { } @Nonnull - protected Clusters getClusters() { + protected Clusters> getClusters() { return clusters; } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java index 8468d3598c..1515b4372a 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java @@ -59,7 +59,7 @@ public YamlConnectionFactory createConnectionFactory() { @Override public String toString() { - final ExternalServer primaryExternal = externalServers.primary().server(); + final ExternalServer primaryExternal = externalServers.primary(); if (initialConnection == 0) { return "MultiServer (" + super.toString() + " then " + primaryExternal.getVersion() + ")"; } else { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index c8fe9cf6b1..32a4b7cb94 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -34,43 +34,52 @@ * * @param the type of server or driver held by each entry */ -public class Clusters implements Iterable> { +public class Clusters implements Iterable { @Nonnull - private final List> entries; + private final List entries; /** Private constructor to support the static {@link Clusters#empty()}. */ private Clusters() { this.entries = List.of(); } - public Clusters(@Nonnull List> entries) { + private Clusters(@Nonnull List entries) { if (entries.isEmpty()) { throw new IllegalArgumentException("At least one cluster entry is required"); } this.entries = List.copyOf(entries); } - public static Clusters empty() { + public static Clusters empty() { return new Clusters<>(); } - public static Clusters fromClusterFiles(List clusterFiles, Function toServer) { + public static Clusters fromClusterFiles(List clusterFiles, Function toServer) { return new Clusters<>(clusterFiles.stream() - .map(clusterFile -> new Entry<>(toServer.apply(clusterFile), clusterFile)) + .map(toServer) .collect(Collectors.toList())); } - public Clusters map(Function mapper) { + public static Clusters> fromClusterFilesAsEntries(List clusterFiles, Function toServer) { + return fromClusterFiles(clusterFiles, + clusterFile -> new Clusters.Entry<>(toServer.apply(clusterFile), clusterFile)); + } + + public Clusters map(Function mapper) { return new Clusters<>(entries.stream() - .map(entry -> new Entry<>(mapper.apply(entry.server), entry.clusterFile)) + .map(mapper) .collect(Collectors.toList())); } + public static Entry mapEntry(Clusters.Entry existing, Function mapper) { + return new Clusters.Entry(mapper.apply(existing.server), existing.clusterFile); + } + /** * Returns the first (primary) entry. */ @Nonnull - public Entry primary() { + public T primary() { return entries.get(0); } @@ -79,7 +88,7 @@ public Entry primary() { */ @Nonnull public List clusterFiles() { - return entries.stream().map(Entry::clusterFile).collect(Collectors.toList()); + return entries.stream().map(BoundToCluster::clusterFile).collect(Collectors.toList()); } /** @@ -97,7 +106,7 @@ public int size() { * @throws SQLException if the index is out of range */ @Nonnull - public Entry get(int clusterIndex) throws SQLException { + public T get(int clusterIndex) throws SQLException { if (clusterIndex < 0 || clusterIndex >= entries.size()) { throw new SQLException("Cluster index " + clusterIndex + " not available (only " + entries.size() + " clusters configured)"); @@ -107,7 +116,7 @@ public Entry get(int clusterIndex) throws SQLException { @Override @Nonnull - public Iterator> iterator() { + public Iterator iterator() { return entries.iterator(); } @@ -116,7 +125,7 @@ public Iterator> iterator() { * * @param the type of server or driver */ - public static class Entry { + public static class Entry implements BoundToCluster { @Nonnull private final T server; @Nonnull @@ -133,8 +142,17 @@ public T server() { } @Nonnull + @Override public String clusterFile() { return clusterFile; } } + + /** + * An interface for a server (or driver) and its associated cluster file. + */ + public interface BoundToCluster { + @Nonnull + String clusterFile(); + } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java index c1cd69ecd6..769b56f2ab 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/EmbeddedYamlConnectionFactory.java @@ -35,9 +35,9 @@ public class EmbeddedYamlConnectionFactory implements YamlConnectionFactory { @Nonnull - private final Clusters clusters; + private final Clusters> clusters; - public EmbeddedYamlConnectionFactory(@Nonnull Clusters clusters) { + public EmbeddedYamlConnectionFactory(@Nonnull Clusters> clusters) { this.clusters = clusters; } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java index affb2abf61..99e22c57d9 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java @@ -47,8 +47,7 @@ public ExternalServerYamlConnectionFactory(@Nonnull Clusters clu @Override public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { - final Clusters.Entry entry = clusters.get(clusterIndex); - return createConnection(connectPath, entry.server(), entry.clusterFile()); + return createConnection(connectPath, clusters.get(clusterIndex)); } @Override @@ -56,8 +55,7 @@ public int getAvailableClusterCount() { return clusters.size(); } - private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull ExternalServer server, - @Nonnull String clusterFile) throws SQLException { + private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull ExternalServer server) throws SQLException { String uriStr = connectPath.toString().replaceFirst("embed:", "relational://localhost:" + server.getPort()); if (LOG.isInfoEnabled()) { LOG.info(KeyValueLogMessage.of("Rewrote connection string for external server", @@ -68,12 +66,12 @@ private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull Exter final Connection connection = DriverManager.getConnection(uriStr); server.validateConnectionVersion(connection); - return new SimpleYamlConnection(connection, server.getVersion(), clusterFile); + return new SimpleYamlConnection(connection, server.getVersion(), server.clusterFile()); } @Override public Set getVersionsUnderTest() { - return Set.of(clusters.primary().server().getVersion()); + return Set.of(clusters.primary().getVersion()); } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java index 0a932a3805..13abbcc783 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/JDBCInProcessYamlConnectionFactory.java @@ -39,9 +39,9 @@ public class JDBCInProcessYamlConnectionFactory implements YamlConnectionFactory { private static final Logger LOG = LogManager.getLogger(JDBCInProcessYamlConnectionFactory.class); @Nonnull - private final Clusters clusters; + private final Clusters> clusters; - public JDBCInProcessYamlConnectionFactory(@Nonnull Clusters clusters) { + public JDBCInProcessYamlConnectionFactory(@Nonnull Clusters> clusters) { this.clusters = clusters; } 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 54be89ca5d..b87b52caf0 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 @@ -25,6 +25,7 @@ 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; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -54,7 +55,7 @@ /** * Class to manage running an external server. */ -public class ExternalServer { +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"; @@ -128,7 +129,8 @@ public SemanticVersion getVersion() { return version; } - public String getClusterFile() { + @Override + public String clusterFile() { return clusterFile; } From 5effd12169b431bc759fc85a5c796e1c05c18ad7 Mon Sep 17 00:00:00 2001 From: Scott Dugas Date: Mon, 20 Apr 2026 10:04:53 -0400 Subject: [PATCH 25/25] Address PR Comments I also cleaned up a couple of the yaml test tests that were more complicated than they needed to be. --- .../yamltests/YamlExecutionContext.java | 32 +++++++------------ .../yamltests/YamlTestExtension.java | 2 ++ .../yamltests/configs/EmbeddedConfig.java | 4 +++ .../configs/ExternalMultiServerConfig.java | 7 ++-- .../configs/JDBCMultiServerConfig.java | 7 ++-- .../yamltests/connectionfactory/Clusters.java | 12 ++++--- .../ExternalServerYamlConnectionFactory.java | 2 +- .../yamltests/server/ExternalServer.java | 1 + yaml-tests/src/test/java/CustomTagTest.java | 4 +-- .../src/test/java/IncludeBlockTest.java | 30 ++--------------- .../src/test/java/InitialVersionTest.java | 3 +- .../src/test/java/SupportedVersionTest.java | 3 +- .../src/test/java/TransactionSetupTest.java | 30 ++--------------- .../resources/multi-cluster-isolation.yamsql | 2 -- 14 files changed, 49 insertions(+), 90 deletions(-) diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java index f85717f6f8..1f5475dddf 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java @@ -286,33 +286,25 @@ public void registerConnectionURI(@Nonnull YamlReference.YamlResource resource, } /** - * Infers the URI of the database to which a block should connect to. + * Infers the connection target (URI and cluster index) for a block. *
- * A block can declare a connection in multiple ways: - * 1. no explicit declaration: Try to connect to the only registered connection URI in the local {@link YamlReference.YamlResource}. + *
    + *
  • no explicit declaration: Try to connect to the only registered connection URI in the local {@link YamlReference.YamlResource}. * If not, try to connect to the only connection across all parent resources. * A URI can be registered by defining a "schema_template" block before that, which sets up the database and schema for a provided schema template. - * 2. Parameter 0: connects to the system tables (catalog). - * 3. Parameter One-based Number: connects to the registered connection URI, number denotes the sequence of definitions in the local YamlResource. + *
  • + *
  • Parameter 0: connects to the system tables (catalog).
  • + *
  • Parameter One-based Number: connects to the registered connection URI, number denotes the sequence of definitions in the local YamlResource. * To access parent connection URIs, this number should be prepended by `(global)` tag. - * 4. Parameter String: connects to the defined String - * - * @param connectObject can be {@code null}, an {@link Integer} value or a {@link String}. - * - * @return a valid connection URI - */ - public URI inferConnectionURI(@Nonnull final YamlReference.YamlResource resource, @Nullable Object connectObject) { - return inferConnectionTarget(resource, connectObject).getUri(); - } - - /** - * Infers the connection target (URI and cluster index) for a block. - *
    - * Supports all the forms of {@link #inferConnectionURI}, plus a map form for specifying the cluster: + *
  • + *
  • Parameter String: connects to the defined String
  • + *
  • A map form for specifying the cluster: *
    {@code
          * connect: { cluster: 1, uri: 0 }
          * connect: { cluster: 1 }
          * }
    + *
  • + *
* * @param connectObject can be {@code null}, an {@link Integer}, a {@link String}, or a {@link Map} with * optional {@code cluster} and {@code uri} keys. @@ -337,7 +329,7 @@ private URI resolveConnectionURI(@Nonnull final YamlReference.YamlResource resou } else if (connectObject instanceof Integer) { return getConnectionFromConnectionURIList(resource, false, (Integer) connectObject, false); } else { - final var stringURI = Matchers.string(connectObject); + final var stringURI = Matchers.string(connectObject, "connection object"); if (stringURI.startsWith("(global)")) { return getConnectionFromConnectionURIList(resource, false, Integer.parseInt(stringURI.substring(8).trim()), true); } 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 320b4e3c4c..31662cd74d 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 @@ -20,6 +20,7 @@ package com.apple.foundationdb.relational.yamltests; +import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.relational.yamltests.configs.CorrectExplains; import com.apple.foundationdb.relational.yamltests.configs.CorrectExplainsAndMetrics; import com.apple.foundationdb.relational.yamltests.configs.CorrectMetrics; @@ -85,6 +86,7 @@ public YamlTestExtension() { * handle complex test hierarchies. In the record layer we maintain the full hierarchy in the output, so this is not * necessary, but if integrating some other tools this might be necessary. */ + @API(API.Status.DEPRECATED) public YamlTestExtension(@Nonnull final String clusterFile, final boolean includeMethodInDescriptions) { this(List.of(clusterFile), includeMethodInDescriptions); } 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 5a1d20b175..d3c50b6b77 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 @@ -20,6 +20,7 @@ package com.apple.foundationdb.relational.yamltests.configs; +import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.server.FRL; @@ -44,6 +45,7 @@ public class EmbeddedConfig implements YamlTestConfig { @Nonnull private Clusters> clusters = Clusters.empty(); + @API(API.Status.DEPRECATED) public EmbeddedConfig(@Nonnull final String clusterFile) { this(List.of(clusterFile)); } @@ -62,6 +64,8 @@ public void beforeAll() throws Exception { .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 10) .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 + // the extent that is validated in the yaml test framework. final String registeredCluster = clusterFiles.get(0); clusters = Clusters.fromClusterFilesAsEntries(clusterFiles, clusterFile -> { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java index 9c05045121..24faecff4e 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/ExternalMultiServerConfig.java @@ -26,6 +26,7 @@ import com.apple.foundationdb.relational.yamltests.connectionfactory.ExternalServerYamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.connectionfactory.MultiServerConnectionFactory; import com.apple.foundationdb.relational.yamltests.server.ExternalServer; +import com.apple.foundationdb.relational.yamltests.server.SemanticVersion; import javax.annotation.Nonnull; import java.util.List; @@ -70,10 +71,12 @@ public YamlConnectionFactory createConnectionFactory() { @Override public String toString() { + final SemanticVersion version0 = servers0.getInfo(ExternalServer::getVersion); + final SemanticVersion version1 = servers1.getInfo(ExternalServer::getVersion); if (initialConnection == 0) { - return "MultiServer (" + servers0.primary().getVersion() + " then " + servers1.primary().getVersion() + ")"; + return "MultiServer (" + version0 + " then " + version1 + ")"; } else { - return "MultiServer (" + servers1.primary().getVersion() + " then " + servers0.primary().getVersion() + ")"; + return "MultiServer (" + version1 + " then " + version0 + ")"; } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java index 1515b4372a..65a57a687f 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCMultiServerConfig.java @@ -25,6 +25,7 @@ import com.apple.foundationdb.relational.yamltests.connectionfactory.ExternalServerYamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.connectionfactory.MultiServerConnectionFactory; import com.apple.foundationdb.relational.yamltests.server.ExternalServer; +import com.apple.foundationdb.relational.yamltests.server.SemanticVersion; import javax.annotation.Nonnull; import java.util.List; @@ -59,11 +60,11 @@ public YamlConnectionFactory createConnectionFactory() { @Override public String toString() { - final ExternalServer primaryExternal = externalServers.primary(); + final SemanticVersion externalVersion = externalServers.getInfo(ExternalServer::getVersion); if (initialConnection == 0) { - return "MultiServer (" + super.toString() + " then " + primaryExternal.getVersion() + ")"; + return "MultiServer (" + super.toString() + " then " + externalVersion + ")"; } else { - return "MultiServer (" + primaryExternal.getVersion() + " then " + super.toString() + ")"; + return "MultiServer (" + externalVersion + " then " + super.toString() + ")"; } } } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java index 32a4b7cb94..e282e13fa4 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/Clusters.java @@ -76,11 +76,15 @@ public static Entry mapEntry(Clusters.Entry existing, Function the type of the extracted information. + * @return the information */ - @Nonnull - public T primary() { - return entries.get(0); + public R getInfo(Function getter) { + return entries.stream().map(getter) + .findFirst() + .orElseThrow(() -> new IndexOutOfBoundsException("No Clusters found")); } /** diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java index 99e22c57d9..ec47c5a130 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/connectionfactory/ExternalServerYamlConnectionFactory.java @@ -71,7 +71,7 @@ private YamlConnection createConnection(@Nonnull URI connectPath, @Nonnull Exter @Override public Set getVersionsUnderTest() { - return Set.of(clusters.primary().getVersion()); + return Set.of(clusters.getInfo(ExternalServer::getVersion)); } } 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 b87b52caf0..562c29e9f3 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 @@ -129,6 +129,7 @@ public SemanticVersion getVersion() { return version; } + @Nonnull @Override public String clusterFile() { return clusterFile; diff --git a/yaml-tests/src/test/java/CustomTagTest.java b/yaml-tests/src/test/java/CustomTagTest.java index 1735930b8f..237b6de955 100644 --- a/yaml-tests/src/test/java/CustomTagTest.java +++ b/yaml-tests/src/test/java/CustomTagTest.java @@ -29,14 +29,14 @@ import org.junit.jupiter.params.provider.MethodSource; import javax.annotation.Nonnull; +import java.util.List; import java.util.stream.Stream; /** * Tests for custom YAML tags such as {@link com.apple.foundationdb.relational.yamltests.tags.IgnoreTag}. */ class CustomTagTest { - private static final String CLUSTER_FILE = FDBTestEnvironment.randomClusterFile(); - private static final EmbeddedConfig config = new EmbeddedConfig(CLUSTER_FILE); + private static final EmbeddedConfig config = new EmbeddedConfig(List.of(FDBTestEnvironment.randomClusterFile())); @BeforeAll static void beforeAll() throws Exception { diff --git a/yaml-tests/src/test/java/IncludeBlockTest.java b/yaml-tests/src/test/java/IncludeBlockTest.java index ed304c9fa8..ac5c581876 100644 --- a/yaml-tests/src/test/java/IncludeBlockTest.java +++ b/yaml-tests/src/test/java/IncludeBlockTest.java @@ -18,25 +18,17 @@ * limitations under the License. */ -import com.apple.foundationdb.relational.yamltests.SimpleYamlConnection; -import com.apple.foundationdb.relational.yamltests.YamlConnection; -import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; import com.apple.foundationdb.relational.yamltests.YamlRunner; import com.apple.foundationdb.relational.yamltests.configs.EmbeddedConfig; import com.apple.foundationdb.relational.yamltests.configs.YamlTestConfig; -import com.apple.foundationdb.relational.yamltests.server.SemanticVersion; import com.apple.foundationdb.test.FDBTestEnvironment; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import javax.annotation.Nonnull; -import java.net.URI; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.Set; +import java.util.List; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -46,10 +38,8 @@ */ public class IncludeBlockTest { - private static final SemanticVersion VERSION = SemanticVersion.parse("4.4.8.0"); - private static final YamlTestConfig config = new EmbeddedConfig(FDBTestEnvironment.randomClusterFile()); + private static final YamlTestConfig config = new EmbeddedConfig(List.of(FDBTestEnvironment.randomClusterFile())); private static final boolean CORRECT_METRICS = false; - private static final String CLUSTER_FILE = FDBTestEnvironment.randomClusterFile(); @BeforeAll static void beforeAll() throws Exception { @@ -68,21 +58,7 @@ private void doRun(String fileName) throws Exception { } else { options = YamlExecutionContext.ContextOptions.EMPTY_OPTIONS; } - new YamlRunner(fileName, createConnectionFactory(), options).run(); - } - - YamlConnectionFactory createConnectionFactory() { - return new YamlConnectionFactory() { - @Override - public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { - return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()), VERSION, CLUSTER_FILE); - } - - @Override - public Set getVersionsUnderTest() { - return Set.of(VERSION); - } - }; + new YamlRunner(fileName, config.createConnectionFactory(), options).run(); } // yamsql files that works if they are rightly included in their parent yamsql file (that is, the parent file has diff --git a/yaml-tests/src/test/java/InitialVersionTest.java b/yaml-tests/src/test/java/InitialVersionTest.java index 5ece7f3411..987dd6d48f 100644 --- a/yaml-tests/src/test/java/InitialVersionTest.java +++ b/yaml-tests/src/test/java/InitialVersionTest.java @@ -35,6 +35,7 @@ import java.net.URI; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -46,7 +47,7 @@ public class InitialVersionTest { private static final SemanticVersion VERSION = SemanticVersion.parse("3.0.18.0"); private static final String CLUSTER_FILE = FDBTestEnvironment.randomClusterFile(); - private static final EmbeddedConfig config = new EmbeddedConfig(CLUSTER_FILE); + private static final EmbeddedConfig config = new EmbeddedConfig(List.of(CLUSTER_FILE)); @BeforeAll static void beforeAll() throws Exception { diff --git a/yaml-tests/src/test/java/SupportedVersionTest.java b/yaml-tests/src/test/java/SupportedVersionTest.java index 920a1cf33e..6056b3aabc 100644 --- a/yaml-tests/src/test/java/SupportedVersionTest.java +++ b/yaml-tests/src/test/java/SupportedVersionTest.java @@ -35,6 +35,7 @@ import java.net.URI; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -47,7 +48,7 @@ public class SupportedVersionTest { private static final SemanticVersion VERSION = SemanticVersion.parse("3.0.18.0"); private static final String CLUSTER_FILE = FDBTestEnvironment.randomClusterFile(); - private static final EmbeddedConfig config = new EmbeddedConfig(CLUSTER_FILE); + private static final EmbeddedConfig config = new EmbeddedConfig(List.of(CLUSTER_FILE)); @BeforeAll static void beforeAll() throws Exception { diff --git a/yaml-tests/src/test/java/TransactionSetupTest.java b/yaml-tests/src/test/java/TransactionSetupTest.java index eb256bfa9d..137f391186 100644 --- a/yaml-tests/src/test/java/TransactionSetupTest.java +++ b/yaml-tests/src/test/java/TransactionSetupTest.java @@ -18,25 +18,17 @@ * limitations under the License. */ -import com.apple.foundationdb.relational.yamltests.SimpleYamlConnection; -import com.apple.foundationdb.relational.yamltests.YamlConnection; -import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; import com.apple.foundationdb.relational.yamltests.YamlRunner; import com.apple.foundationdb.relational.yamltests.configs.EmbeddedConfig; import com.apple.foundationdb.relational.yamltests.configs.YamlTestConfig; -import com.apple.foundationdb.relational.yamltests.server.SemanticVersion; import com.apple.foundationdb.test.FDBTestEnvironment; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import javax.annotation.Nonnull; -import java.net.URI; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.Set; +import java.util.List; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -46,10 +38,8 @@ */ public class TransactionSetupTest { - private static final SemanticVersion VERSION = SemanticVersion.parse("4.4.8.0"); - private static final YamlTestConfig config = new EmbeddedConfig(FDBTestEnvironment.randomClusterFile()); + private static final YamlTestConfig config = new EmbeddedConfig(List.of(FDBTestEnvironment.randomClusterFile())); private static final boolean CORRECT_METRICS = false; - private static final String CLUSTER_FILE = FDBTestEnvironment.randomClusterFile(); @BeforeAll static void beforeAll() throws Exception { @@ -68,21 +58,7 @@ private void doRun(String fileName) throws Exception { } else { options = YamlExecutionContext.ContextOptions.EMPTY_OPTIONS; } - new YamlRunner(fileName, createConnectionFactory(), options).run(); - } - - YamlConnectionFactory createConnectionFactory() { - return new YamlConnectionFactory() { - @Override - public YamlConnection getNewConnection(@Nonnull URI connectPath, int clusterIndex) throws SQLException { - return new SimpleYamlConnection(DriverManager.getConnection(connectPath.toString()), VERSION, CLUSTER_FILE); - } - - @Override - public Set getVersionsUnderTest() { - return Set.of(VERSION); - } - }; + new YamlRunner(fileName, config.createConnectionFactory(), options).run(); } static Stream shouldFail() { diff --git a/yaml-tests/src/test/resources/multi-cluster-isolation.yamsql b/yaml-tests/src/test/resources/multi-cluster-isolation.yamsql index 674a5f50e0..a9988e987b 100644 --- a/yaml-tests/src/test/resources/multi-cluster-isolation.yamsql +++ b/yaml-tests/src/test/resources/multi-cluster-isolation.yamsql @@ -55,7 +55,6 @@ setup: # Verify cluster 1 does not have the database test_block: connect: { cluster: 1, uri: "jdbc:embed:/__SYS?schema=CATALOG" } - preset: single_repetition_ordered tests: - - query: select count(*) from "DATABASES" where database_id = '/FRL/MCI_DB' @@ -80,7 +79,6 @@ setup: # Verify cluster 1 has only its own data test_block: connect: { cluster: 1, uri: "jdbc:embed:/FRL/MCI_DB?schema=S1" } - preset: single_repetition_ordered tests: - - query: select * from T1