diff --git a/fdb-record-layer-core/fdb-record-layer-core.gradle b/fdb-record-layer-core/fdb-record-layer-core.gradle index 75708cffd9..7e5e6b5179 100644 --- a/fdb-record-layer-core/fdb-record-layer-core.gradle +++ b/fdb-record-layer-core/fdb-record-layer-core.gradle @@ -25,6 +25,13 @@ plugins { apply from: rootProject.file('gradle/proto.gradle') apply from: rootProject.file('gradle/publishing.gradle') +configurations { + // Resolved to the mockito-core jar so it can be installed as a -javaagent on the test JVM. + // Required on JDK 21+ because Mockito's inline mock maker can no longer self-attach. + // See https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3 + mockitoAgent +} + dependencies { annotationProcessor project(':fdb-java-annotations') api project(':fdb-extensions') @@ -60,6 +67,12 @@ dependencies { testFixturesImplementation(libs.protobuf.util) testFixturesCompileOnly(libs.jsr305) testFixturesAnnotationProcessor(libs.autoService) + + mockitoAgent(libs.mockito) { transitive = false } +} + +tasks.withType(Test).configureEach { + jvmArgs("-javaagent:${configurations.mockitoAgent.asPath}") } sourceSets { diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java index 2a135af001..7f8d414db4 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStore.java @@ -1799,12 +1799,16 @@ public static void deleteStore(FDBRecordContext context, KeySpacePath path) { * the store existed. * *

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

* * @param context the transactional context in which to delete the record store @@ -1812,9 +1816,25 @@ public static void deleteStore(FDBRecordContext context, KeySpacePath path) { */ @SuppressWarnings("PMD.CloseResource") public static void deleteStore(FDBRecordContext context, Subspace subspace) { - // In theory, we only need to set the meta-data version stamp if the record store's - // meta-data is cacheable, but we can't know that from here. - context.setMetaDataVersionStamp(); + // Read the store header at snapshot isolation so we don't add a needless read conflict: + // the clear below already creates a write conflict on the whole range, which is what + // actually serialises us against any concurrent writer of this store's header. + final byte[] headerKey = subspace.pack(STORE_INFO_KEY); + final byte[] headerBytes = context.asyncToSync(FDBStoreTimer.Waits.WAIT_LOAD_RECORD_STORE_STATE, + context.readTransaction(true).get(headerKey)); + if (headerBytes != null) { + boolean shouldBump = true; + try { + shouldBump = RecordMetaDataProto.DataStoreInfo.parseFrom(headerBytes).getCacheable(); + } catch (InvalidProtocolBufferException e) { + // If we can't parse the header, fall back to the pre-change conservative + // behavior and bump. This preserves the "delete a store I know nothing about" + // contract this method advertises. + } + if (shouldBump) { + context.setMetaDataVersionStamp(); + } + } context.setDirtyStoreState(true); context.clear(subspace.range()); } @@ -3019,9 +3039,34 @@ private CompletableFuture updateStoreHeaderAsync(@Nonnull UnaryOperator readStoreFirstKey(@Nonnull FDBRecordContext context, @Nonnull Subspace subspace, @Nonnull IsolationLevel isolationLevel) { - final AsyncIterator iterator = context.readTransaction(isolationLevel.isSnapshot()).getRange(subspace.range(), 1).iterator(); + // Always read at snapshot isolation so FDB does not add an implicit read-conflict range + // spanning the whole subspace (which can pull in unrelated concurrent writers deep inside + // the store's subspace and produce spurious SERIALIZATION_FAILUREs). When the caller + // wanted SERIALIZABLE isolation, add back a narrower, deterministic read-conflict range + // ourselves: from the beginning of the subspace up to and including the first key we + // actually found. When the store is empty (no keys at all) we still have to cover the + // whole range to correctly serialise against a concurrent creator, so we widen back to + // the full subspace in that case. + final Range subspaceRange = subspace.range(); + final AsyncIterator iterator = context.readTransaction(true).getRange(subspaceRange, 1).iterator(); return context.instrument(FDBStoreTimer.Events.LOAD_RECORD_STORE_INFO, - iterator.onHasNext().thenApply(hasNext -> hasNext ? iterator.next() : null)); + iterator.onHasNext().thenApply(hasNext -> { + final KeyValue firstKey = hasNext ? iterator.next() : null; + if (!isolationLevel.isSnapshot()) { + final byte[] end; + if (firstKey == null) { + // Store is empty — we need to serialise against anyone else who might + // write into this subspace between our read and commit. + end = subspaceRange.end; + } else { + // Cover [subspaceRange.begin, firstKey + \x00) — i.e. the found key + // itself and everything before it, but nothing after. + end = ByteArrayUtil.join(firstKey.getKey(), new byte[]{0}); + } + context.ensureActive().addReadConflictRange(subspaceRange.begin, end); + } + return firstKey; + })); } /** diff --git a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/InstrumentedTransaction.java b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/InstrumentedTransaction.java index 942b749962..3745a2e6f5 100644 --- a/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/InstrumentedTransaction.java +++ b/fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/InstrumentedTransaction.java @@ -30,6 +30,10 @@ import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.async.AsyncUtil; import com.apple.foundationdb.record.provider.common.StoreTimer; +import com.apple.foundationdb.tuple.ByteArrayUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -39,10 +43,21 @@ /** * Wrapper around {@link Transaction} that instruments certain calls to expose their behavior with * {@link FDBStoreTimer} metrics. + * + *

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

*/ @API(API.Status.INTERNAL) public class InstrumentedTransaction extends InstrumentedReadTransaction implements Transaction { + private static final Logger LOGGER = LoggerFactory.getLogger(InstrumentedTransaction.class); + @Nullable protected ReadTransaction snapshot; // lazily cached snapshot wrapper @@ -77,6 +92,7 @@ public void addReadConflictKey(byte[] key) { @Override public void addWriteConflictRange(byte[] keyBegin, byte[] keyEnd) { + traceMutation("addWriteConflictRange", keyBegin, keyEnd); underlying.addWriteConflictRange(checkKey(keyBegin), checkKey(keyEnd)); } @@ -94,12 +110,14 @@ public void set(byte[] key, byte[] value) { @Override public void clear(byte[] key) { + traceMutation("clear(key)", key, null); underlying.clear(checkKey(key)); increment(FDBStoreTimer.Counts.DELETES); } @Override public void clear(byte[] keyBegin, byte[] keyEnd) { + traceMutation("clear(range)", keyBegin, keyEnd); underlying.clear(checkKey(keyBegin), checkKey(keyEnd)); increment(FDBStoreTimer.Counts.DELETES); increment(FDBStoreTimer.Counts.RANGE_DELETES); @@ -107,6 +125,7 @@ public void clear(byte[] keyBegin, byte[] keyEnd) { @Override public void clear(Range range) { + traceMutation("clear(Range)", range.begin, range.end); checkKey(range.begin); checkKey(range.end); @@ -118,11 +137,28 @@ public void clear(Range range) { @Override @Deprecated public void clearRangeStartsWith(byte[] prefix) { + traceMutation("clearRangeStartsWith", prefix, null); underlying.clearRangeStartsWith(checkKey(prefix)); increment(FDBStoreTimer.Counts.DELETES); increment(FDBStoreTimer.Counts.RANGE_DELETES); } + /** + * Diagnostic hook: when this class's SLF4J logger is at TRACE, emit the operation name, the + * printable form of the affected key(s), and a stack trace. Callers pay only the cost of the + * {@code isTraceEnabled()} check at other log levels. Intended for hunting down subspace-wide + * range writes that cause serialization failures on concurrent readers. + */ + private static void traceMutation(@Nonnull String op, @Nullable byte[] begin, @Nullable byte[] end) { + if (!LOGGER.isTraceEnabled()) { + return; + } + final String beginStr = begin == null ? "null" : ByteArrayUtil.printable(begin); + final String endStr = end == null ? "" : ".." + ByteArrayUtil.printable(end); + LOGGER.trace("InstrumentedTransaction mutation: {} [{}{}]", op, beginStr, endStr, + new Throwable("stack for " + op)); + } + @Override public void mutate(MutationType opType, byte[] key, byte[] param) { underlying.mutate(opType, checkKey(key), param); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java index 2e0a108c04..fd7ebe80ae 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBDatabaseTest.java @@ -107,12 +107,19 @@ void cachedVersionMaintenanceOnReadsTest() throws Exception { RecordMetaData metaData = RecordMetaData.build(TestRecords1Proto.getDescriptor()); + // Create the path up-front so that the directory-layer resolution done by + // TestKeySpacePathManager.createPath has already warmed FDBDatabase.lastSeenFDBVersion + // by the time we capture readVersion1 below. Otherwise the cached version captured + // here would be stale relative to the one createPath puts in the cache, and the + // weak-read-semantics assertion at "assertEquals(readVersion1, context.getReadVersion())" + // would return the newer (post-createPath) cached version and fail. + final KeySpacePath path = pathManager.createPath(TestKeySpace.RECORD_STORE); + // First time, does a GRV from FDB - long readVersion1 = getReadVersion(database, 0L, 2_000L); + long readVersion1 = getReadVersion(database, 0L, 60_000L); // Store a record (advances future GRV, but not cached version) - final KeySpacePath path = pathManager.createPath(TestKeySpace.RECORD_STORE); - final FDBDatabase.WeakReadSemantics weakReadSemantics = new FDBDatabase.WeakReadSemantics(0L, 2_000L, false); + final FDBDatabase.WeakReadSemantics weakReadSemantics = new FDBDatabase.WeakReadSemantics(0L, 60_000L, false); try (FDBRecordContext context = database.openContext(FDBRecordContextConfig.newBuilder().setWeakReadSemantics(weakReadSemantics).setMdcContext(MDC.getCopyOfContextMap()).build())) { assertEquals(readVersion1, context.getReadVersion()); FDBRecordStore recordStore = FDBRecordStore.newBuilder() @@ -126,8 +133,8 @@ void cachedVersionMaintenanceOnReadsTest() throws Exception { context.commit(); } - // We're fine with any version obtained up to 2s ago, so will get the original readVersion - assertEquals(readVersion1, getReadVersion(database, 0L, 2000L)); + // We're fine with any version obtained up to 60s ago, so will get the original readVersion + assertEquals(readVersion1, getReadVersion(database, 0L, 60_000L)); Thread.sleep(10L); @@ -135,19 +142,32 @@ void cachedVersionMaintenanceOnReadsTest() throws Exception { long readVersion2 = getReadVersion(database, 0L, 11L); assertThat(readVersion1, lessThan(readVersion2)); - // Store another record - testStoreAndRetrieveSimpleRecord(database, metaData, path); + // Store another record. Disable read-tracking for the duration so the fresh GRV + // done inside testStoreAndRetrieveSimpleRecord (which uses database.run(null, ...) + // and therefore bypasses weak-read-semantics) doesn't repopulate the cached + // version with a newer value than readVersion2 and break the assertions below. + database.setTrackLastSeenVersionOnRead(false); + try { + testStoreAndRetrieveSimpleRecord(database, metaData, path); + } finally { + database.setTrackLastSeenVersionOnRead(true); + } - assertEquals(readVersion2, getReadVersion(database, 0L, 2000L)); - assertEquals(readVersion2, getReadVersion(database, readVersion2, 2000L)); + assertEquals(readVersion2, getReadVersion(database, 0L, 60_000L)); + assertEquals(readVersion2, getReadVersion(database, readVersion2, 60_000L)); // Now we want at least readVersion2 + 1, so this will cause a GRV - long readVersion3 = getReadVersion(database, readVersion2 + 1, 2000L); + long readVersion3 = getReadVersion(database, readVersion2 + 1, 60_000L); assertTrue(readVersion2 < readVersion3); - // Store another record - testStoreAndRetrieveSimpleRecord(database, metaData, path); + // Store another record (same read-tracking-disable shield as above) + database.setTrackLastSeenVersionOnRead(false); + try { + testStoreAndRetrieveSimpleRecord(database, metaData, path); + } finally { + database.setTrackLastSeenVersionOnRead(true); + } // Don't use a stored version assertTrue(readVersion3 < getReadVersion(database, null, null)); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreReplaceIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreReplaceIndexTest.java index 2b76e6e1bb..78f30992bb 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreReplaceIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBRecordStoreReplaceIndexTest.java @@ -47,7 +47,6 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.stream.Collectors; -import java.util.stream.IntStream; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -235,20 +234,20 @@ public void buildReplacementsInMultipleStores() { final Index newIndex = new Index("MySimpleRecord$(num_value_2, repeater)", Key.Expressions.concat(Key.Expressions.field("num_value_2"), Key.Expressions.field("repeater", KeyExpression.FanType.FanOut))); final RecordMetaDataHook allIndexesHook = composeHooks(addIndexHook(recordTypeName, origIndex), addIndexHook(recordTypeName, newIndex)); - final List stores = IntStream.range(0, 10).mapToObj(i -> "store_" + i).collect(Collectors.toList()); + final int storeCount = 10; try (FDBRecordContext context = openContext()) { openSimpleRecordStore(context, allIndexesHook); // Create a bunch of stores and disable the new index in all of them - forEachStore(multiStoreRoot, stores, (storePathName, subStore) -> { + forEachStore(multiStoreRoot, storeCount, (index, subStore) -> { assertTrue(context.asyncToSync(FDBStoreTimer.Waits.WAIT_DROP_INDEX, subStore.markIndexDisabled(newIndex))); subStore.saveRecord(TestRecords1Proto.MySimpleRecord.newBuilder() .setRecNo(1066L) .addRepeater(42) .addRepeater(800) - .setStrValueIndexed(storePathName) + .setStrValueIndexed("String " + index) .build()); }); @@ -260,7 +259,7 @@ public void buildReplacementsInMultipleStores() { try (FDBRecordContext context = openContext()) { openSimpleRecordStore(context, withReplacementHook); - forEachStore(multiStoreRoot, stores, (storePathName, subStore) -> { + forEachStore(multiStoreRoot, storeCount, (index, subStore) -> { final List> records = context.asyncToSync(FDBStoreTimer.Waits.WAIT_SCAN_RECORDS, subStore.scanIndexRecords(origIndex.getName()).asList()); assertThat(records, hasSize(2)); records.stream() @@ -270,7 +269,7 @@ public void buildReplacementsInMultipleStores() { assertThat(fieldValue, instanceOf(String.class)); return (String)fieldValue; }) - .forEach(strValue -> assertEquals(storePathName, strValue)); + .forEach(strValue -> assertEquals("String " + index, strValue)); context.asyncToSync(FDBStoreTimer.Waits.WAIT_ONLINE_BUILD_INDEX, subStore.rebuildIndex(subStore.getRecordMetaData().getIndex(newIndex.getName()))); }); @@ -281,14 +280,14 @@ public void buildReplacementsInMultipleStores() { // Validate that each store has had the original index removed (because the replacement index was built) try (FDBRecordContext context = openContext()) { openSimpleRecordStore(context, withReplacementHook); - forEachStore(multiStoreRoot, stores, (storePathName, subStore) -> assertTrue(subStore.isIndexDisabled(origIndex.getName()))); + forEachStore(multiStoreRoot, storeCount, (storePathName, subStore) -> assertTrue(subStore.isIndexDisabled(origIndex.getName()))); commit(context); } // Validate each store has had the old index data cleaned out try (FDBRecordContext context = openContext()) { openSimpleRecordStore(context, composeHooks(allIndexesHook, bumpMetaDataVersionHook(), bumpMetaDataVersionHook())); - forEachStore(multiStoreRoot, stores, (storePathName, subStore) -> { + forEachStore(multiStoreRoot, storeCount, (storePathName, subStore) -> { assertTrue(context.asyncToSync(FDBStoreTimer.Waits.WAIT_ADD_INDEX, subStore.uncheckedMarkIndexReadable(origIndex.getName()))); assertEquals(Collections.emptyList(), context.asyncToSync(FDBStoreTimer.Waits.WAIT_SCAN_INDEX_RECORDS, subStore.scanIndexRecords(origIndex.getName()).asList())); }); @@ -302,13 +301,13 @@ public void buildReplacementsInMultipleStores() { } } - private void forEachStore(@Nonnull KeySpacePath root, @Nonnull List storePaths, @Nonnull BiConsumer subStoreConsumer) { - for (String storePathName : storePaths) { - final KeySpacePath storePath = root.add(TestKeySpace.STORE_PATH, storePathName); + private void forEachStore(@Nonnull KeySpacePath root, int count, @Nonnull BiConsumer subStoreConsumer) { + for (int i = 0; i < count; i++) { + final KeySpacePath storePath = root.add(TestKeySpace.STORE_PATH, (long) i); final FDBRecordStore subStore = recordStore.asBuilder() .setKeySpacePath(storePath) .createOrOpen(); - subStoreConsumer.accept(storePathName, subStore); + subStoreConsumer.accept(i, subStore); } } diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBReverseDirectoryCacheTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBReverseDirectoryCacheTest.java index 5f8e48964a..887a7c2fdc 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBReverseDirectoryCacheTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/FDBReverseDirectoryCacheTest.java @@ -34,8 +34,8 @@ import com.apple.foundationdb.record.test.FDBDatabaseExtension; import com.apple.foundationdb.record.test.TestKeySpace; import com.apple.foundationdb.record.test.TestKeySpacePathManagerExtension; -import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.foundationdb.record.util.pair.Pair; +import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.foundationdb.tuple.Tuple; import com.apple.test.Tags; import com.google.common.collect.BiMap; @@ -59,6 +59,7 @@ import java.util.Optional; import java.util.Random; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ForkJoinPool; @@ -658,12 +659,40 @@ private Pair[] createRandomDirectoryEntries(FDBDatabase database, keys.add("dir_" + Math.abs(random.nextInt())); } List values = keys.stream() - .map(key -> globalScope.resolve(context.getTimer(), key).join()) + .map(key -> resolveWithRetry(key, context.getTimer())) .collect(Collectors.toList()); return zipKeysAndValues(keys, values); } } + /** + * Resolve a name in {@link #globalScope} with a retry loop around the specific + * "database already has keys in allocation range" / "database has keys stored at the prefix + * chosen by the automatic prefix allocator" failures that the root + * {@link com.apple.foundationdb.directory.DirectoryLayer.HighContentionAllocator} + * throws when a randomly-chosen candidate byte prefix happens to already have keys under it. + * Under parallel test execution any sibling test committing writes at low-integer prefixes + * can trip this check on a fresh candidate — the allocation is fine to retry, since the HCA + * picks a new random candidate each attempt. + */ + private Long resolveWithRetry(String key, FDBStoreTimer timer) { + final int maxAttempts = 20; + RuntimeException lastFailure = null; + for (int attempt = 0; attempt < maxAttempts; attempt++) { + try { + return globalScope.resolve(timer, key).join(); + } catch (CompletionException | IllegalStateException e) { + Throwable cause = e instanceof CompletionException ? e.getCause() : e; + if (cause instanceof IllegalStateException) { + lastFailure = e; + continue; + } + throw e; + } + } + throw new AssertionError("globalScope.resolve failed after " + maxAttempts + " attempts", lastFailure); + } + @SuppressWarnings({"rawtypes", "unchecked"}) private static Pair[] zipKeysAndValues(List keys, List values) { final Iterator valuesIter = values.iterator(); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexScrubberTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexScrubberTest.java index 644f21f68f..475217a591 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexScrubberTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexScrubberTest.java @@ -40,6 +40,7 @@ import com.apple.foundationdb.tuple.Tuple; import com.google.protobuf.Message; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; @@ -63,6 +64,12 @@ /** * Tests for scrubbing readable indexes with {@link OnlineIndexer}. */ +// Scrubber tests issue many batch-priority GRVs per method and still trip the +// FDB cluster's batch-GRV rate limit even when serialised with other indexer +// tests via the inherited @ResourceLock from OnlineIndexerTest. @Isolated +// escalates to full cross-class isolation so no other tests compete for FDB +// resources while the scrubber runs. +@Isolated("Scrubber tests inherently issue many batch-priority GRVs and trip FDB's batch-GRV rate limit when sharing the cluster with other parallel tests") class OnlineIndexScrubberTest extends OnlineIndexerTest { private FDBRecordStoreTestBase.RecordMetaDataHook myHook(Index index) { diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerBuildGroupedCountIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerBuildGroupedCountIndexTest.java index 4afb2f5f5d..93f28394c4 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerBuildGroupedCountIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerBuildGroupedCountIndexTest.java @@ -36,6 +36,7 @@ import com.google.common.collect.Maps; import com.google.protobuf.Message; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -61,6 +62,11 @@ /** * Tests for building a grouped count index. */ +// Parameterized invocations of buildWhileUpdatingGroupedCount issue many batch-priority +// GRVs and trip the FDB cluster's batch-GRV rate limit even when the class is serialised +// with other indexer tests via the inherited @ResourceLock from OnlineIndexerTest. +// Escalate to full cross-class isolation, matching OnlineIndexScrubberTest. +@Isolated("Grouped-count indexer tests issue many batch-priority GRVs and trip FDB's batch-GRV rate limit when sharing the cluster with other parallel tests") public class OnlineIndexerBuildGroupedCountIndexTest extends OnlineIndexerTest { private static final KeyExpression PRIMARY_KEY = concatenateFields("num_value_2", "rec_no"); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerMergeTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerMergeTest.java index 188c030fe2..adc193b768 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerMergeTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerMergeTest.java @@ -53,6 +53,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -80,6 +82,9 @@ * Tests specifically of {@link OnlineIndexer#mergeIndex()}. */ @Tag(Tags.RequiresFDB) +// Matches OnlineIndexerTest's @ResourceLock so this class serialises with the rest of the +// OnlineIndexer* test classes, avoiding FDB batch-GRV throttle when run in parallel. +@ResourceLock(value = "ONLINE_INDEXER_BATCH_GRV", mode = ResourceAccessMode.READ_WRITE) public class OnlineIndexerMergeTest extends FDBRecordStoreConcurrentTestBase { private static final String INDEX_NAME = "mergableIndex"; diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerTest.java index c7a0ed0ef8..faf75ce645 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/OnlineIndexerTest.java @@ -43,6 +43,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLock; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -62,6 +64,13 @@ * Tests for {@link OnlineIndexer}. */ @Tag(Tags.RequiresFDB) +// Online indexer operations use batch-priority GRV (OnlineIndexOperationBaseBuilder +// defaults to FDBTransactionPriority.BATCH). When many indexer test classes run in +// parallel they overrun the FDB cluster's batch-GRV rate limit, causing flaky +// "Batch GRV request rate limit exceeded" failures. This @ResourceLock is inherited +// by all 15 subclasses of OnlineIndexerTest, serialising indexer tests with each +// other while still allowing them to run in parallel with non-indexer tests. +@ResourceLock(value = "ONLINE_INDEXER_BATCH_GRV", mode = ResourceAccessMode.READ_WRITE) public abstract class OnlineIndexerTest { @RegisterExtension final FDBDatabaseExtension dbExtension = new FDBDatabaseExtension(); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/SynchronizedSessionTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/SynchronizedSessionTest.java index ee42087d61..167afefc6a 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/SynchronizedSessionTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/SynchronizedSessionTest.java @@ -32,6 +32,7 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -352,6 +353,7 @@ private void waitLongEnough() throws InterruptedException { /** * Run {@link SynchronizedSessionRunner} synchronously. */ + @Isolated("Lease-renewal tests Thread.sleep then assert the lease was renewed; under parallel CPU starvation the renewing thread misses its window and the assertion fails") public static class Run extends SynchronizedSessionTest { Run() { super(false); @@ -361,6 +363,7 @@ public static class Run extends SynchronizedSessionTest { /** * Run {@link SynchronizedSessionRunner} asynchronously. */ + @Isolated("Lease-renewal tests Thread.sleep then assert the lease was renewed; under parallel CPU starvation the renewing thread misses its window and the assertion fails") public static class RunAsync extends SynchronizedSessionTest { RunAsync() { super(true); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexIndexingTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexIndexingTest.java index a57a861403..837ff78b0d 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexIndexingTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexIndexingTest.java @@ -34,6 +34,7 @@ import com.google.common.collect.ImmutableSet; import com.google.protobuf.Message; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; import java.util.List; @@ -49,6 +50,8 @@ * Tests online indexing of vector indexes. * These tests verify that vector indexes can be built asynchronously after records are saved. */ +// TODO maybe change this to the same resource lock as the indexing tests +@Isolated("Heavy vector index-builder trips FDB cluster's batch-priority GRV rate limit when run concurrently with other indexer tests") class VectorIndexIndexingTest extends VectorIndexTestBase { @Test diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexTest.java index 922109400f..2eb0436799 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VectorIndexTest.java @@ -276,7 +276,7 @@ private void checkResultsGrouped(@Nonnull final RecordQueryIndexPlan indexPlan, void insertReadDeleteReadGroupedWithContinuationTest(final long seed, final boolean useAsync, final int limit) throws Exception { final int size = 1000; Assertions.assertThat(size % 2).isEqualTo(0); // needs to be even - final int updateBatchSize = 50; + final int updateBatchSize = 25; Assertions.assertThat(size % updateBatchSize).isEqualTo(0); // needs to be divisible final int k = 100; diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VersionIndexTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VersionIndexTest.java index b0227434ed..1db8aa2d54 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VersionIndexTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/indexes/VersionIndexTest.java @@ -3045,9 +3045,11 @@ void deleteStoreWithUncommittedVersionData(FormatVersion testFormatVersion, bool splitLongRecords = testSplitLongRecords; final KeySpacePath baseMultiPath = pathManager.createPath(TestKeySpace.MULTI_RECORD_STORE); - final KeySpacePath path1 = baseMultiPath.add(TestKeySpace.STORE_PATH, "path1"); - final KeySpacePath path2 = baseMultiPath.add(TestKeySpace.STORE_PATH, "path2"); - final KeySpacePath path3 = baseMultiPath.add(TestKeySpace.STORE_PATH, "path3"); + // STORE_PATH is a KeyType.LONG KeySpaceDirectory (see TestKeySpace) — pass numeric ids + // rather than strings so no shared root-DL allocation happens here. + final KeySpacePath path1 = baseMultiPath.add(TestKeySpace.STORE_PATH, 1L); + final KeySpacePath path2 = baseMultiPath.add(TestKeySpace.STORE_PATH, 2L); + final KeySpacePath path3 = baseMultiPath.add(TestKeySpace.STORE_PATH, 3L); final List multiPaths = List.of(path1, path2, path3); final Map version1ByStore = new HashMap<>(); diff --git a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java index 2e866a2a47..13f8cc17a5 100644 --- a/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java +++ b/fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/storestate/FDBRecordStoreStateCacheTest.java @@ -45,6 +45,7 @@ import com.apple.foundationdb.record.provider.foundationdb.RecordStoreStaleMetaDataVersionException; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; import com.apple.foundationdb.record.test.FakeClusterFileUtil; +import com.apple.foundationdb.subspace.Subspace; import com.apple.foundationdb.tuple.ByteArrayUtil; import com.apple.foundationdb.tuple.ByteArrayUtil2; import com.apple.foundationdb.tuple.Tuple; @@ -766,6 +767,314 @@ public void storeDeletionAcrossContexts(@Nonnull StateCacheTestContext testConte } } + /** + * Deleting a non-cacheable store must NOT bump the meta-data version stamp: no other + * client can possibly hold a cached copy of a non-cacheable header, so the version stamp + * (a JVM-wide bottleneck on the {@code \xff/metadataVersion} key) shouldn't be touched. + * This is the low-level property that lets parallel tests share the SYS catalog without + * conflicting on catalog teardown. + */ + @Test + void deleteNonCacheableStoreDoesNotBumpMetaDataVersionStamp() throws Exception { + // Bootstrap: ensure the meta-data version stamp key has a value before the test runs, + // so we can distinguish "unchanged" from "was never set". The store below is opened + // with cacheability disabled (which is the default from setStateCacheability(false)). + try (FDBRecordContext context = fdb.openContext()) { + if (context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT) == null) { + context.setMetaDataVersionStamp(); + } + commit(context); + } + + Subspace subspace; + try (FDBRecordContext context = openContext()) { + openSimpleRecordStore(context); + assertFalse(recordStore.getRecordStoreState().getStoreHeader().getCacheable(), + "test presumes the store is non-cacheable by default"); + subspace = recordStore.getSubspace(); + commit(context); + } + + // Snapshot the version stamp before deletion — reading via SNAPSHOT so this txn doesn't + // conflict with the delete-txn below and skew the result. + final byte[] beforeStamp; + try (FDBRecordContext context = fdb.openContext()) { + beforeStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + } + assertNotNull(beforeStamp, "bootstrap should have populated the meta-data version stamp"); + + try (FDBRecordContext context = openContext()) { + FDBRecordStore.deleteStore(context, subspace); + commit(context); + } + + try (FDBRecordContext context = fdb.openContext()) { + final byte[] afterStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + assertArrayEquals(beforeStamp, afterStamp, + "deleting a non-cacheable store should not have bumped the meta-data version stamp"); + } + } + + /** + * Complement of {@link #deleteNonCacheableStoreDoesNotBumpMetaDataVersionStamp()}: deleting + * a cacheable store MUST bump the stamp — otherwise sibling clients could keep serving + * reads out of a stale cached header long after the store is gone. + */ + @Test + void deleteCacheableStoreBumpsMetaDataVersionStamp() throws Exception { + try (FDBRecordContext context = fdb.openContext()) { + if (context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT) == null) { + context.setMetaDataVersionStamp(); + } + commit(context); + } + + Subspace subspace; + try (FDBRecordContext context = openContext()) { + openSimpleRecordStore(context); + assertTrue(recordStore.setStateCacheability(true), "flipping to cacheable should have changed something"); + subspace = recordStore.getSubspace(); + commit(context); + } + // Commit above already bumped the stamp (transition to cacheable). Snapshot after that. + final byte[] beforeStamp; + try (FDBRecordContext context = fdb.openContext()) { + beforeStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + } + assertNotNull(beforeStamp); + + try (FDBRecordContext context = openContext()) { + FDBRecordStore.deleteStore(context, subspace); + commit(context); + } + + try (FDBRecordContext context = fdb.openContext()) { + final byte[] afterStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + assertNotNull(afterStamp); + assertFalse(java.util.Arrays.equals(beforeStamp, afterStamp), + "deleting a cacheable store should have bumped the meta-data version stamp"); + } + } + + /** + * Deleting an empty subspace (no store header present) must not bump the stamp either — + * there's no cached header to invalidate. + */ + @Test + void deleteMissingStoreDoesNotBumpMetaDataVersionStamp() throws Exception { + try (FDBRecordContext context = fdb.openContext()) { + if (context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT) == null) { + context.setMetaDataVersionStamp(); + } + commit(context); + } + + // Use the test's per-instance path, but never open a store there. + final Subspace subspace; + try (FDBRecordContext context = openContext()) { + subspace = path.toSubspace(context); + } + final byte[] beforeStamp; + try (FDBRecordContext context = fdb.openContext()) { + beforeStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + } + assertNotNull(beforeStamp); + + try (FDBRecordContext context = openContext()) { + FDBRecordStore.deleteStore(context, subspace); + commit(context); + } + + try (FDBRecordContext context = fdb.openContext()) { + final byte[] afterStamp = context.getMetaDataVersionStamp(IsolationLevel.SNAPSHOT); + assertArrayEquals(beforeStamp, afterStamp, + "deleting an empty subspace (no header) should not have bumped the meta-data version stamp"); + } + } + + /** + * Regression test that pins the current behaviour: on a state-cache miss, opening a + * cacheable store issues a {@code SERIALIZABLE getRange(subspace.range(), 1)} inside + * {@code FDBRecordStore.loadStoreHeaderAsync}, which registers a read-conflict range that + * covers (at minimum) the store header key. Any concurrent transaction that writes to the + * store header — including the very common case of a second opener that itself hits a cache + * miss and updates the header via {@code checkVersion} — will therefore serialise-fail the + * first transaction if the writer commits first. + * + *

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

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

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

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

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

+ */ + @Test + void concurrentCacheMissOpensBothInsertingRecordsDoNotConflict() throws Exception { + fdb.setStoreStateCache(metaDataVersionStampCacheFactory.getCache(fdb)); + + FDBRecordStore.Builder storeBuilder; + try (FDBRecordContext context = openContext()) { + openSimpleRecordStore(context); + assertTrue(recordStore.setStateCacheability(true)); + storeBuilder = recordStore.asBuilder(); + commit(context); + } + // Invalidate any cached entries so both concurrent opens below are guaranteed to miss. + try (FDBRecordContext context = fdb.openContext()) { + context.setMetaDataVersionStamp(); + commit(context); + } + + try (FDBRecordContext firstContext = fdb.openContext(null, new FDBStoreTimer()); + FDBRecordContext secondContext = fdb.openContext(null, new FDBStoreTimer())) { + // Pin both read versions before either commits, so conflict resolution runs on both. + firstContext.getReadVersion(); + secondContext.getReadVersion(); + + FDBRecordStore first = storeBuilder.copyBuilder().setContext(firstContext).open(); + FDBRecordStore second = storeBuilder.copyBuilder().setContext(secondContext).open(); + + // Both insert distinct records — no header touched, distinct primary keys, distinct + // index entries (different str_value_indexed). + first.saveRecord(TestRecords1Proto.MySimpleRecord.newBuilder() + .setRecNo(1001L) + .setStrValueIndexed("first-writer") + .build()); + second.saveRecord(TestRecords1Proto.MySimpleRecord.newBuilder() + .setRecNo(1002L) + .setStrValueIndexed("second-writer") + .build()); + + // Both commit cleanly — the cache-miss read conflict does not extend far enough to + // overlap a plain record-write, so this is not by itself the yaml-tests failure mode. + commit(secondContext); + commit(firstContext); + } + } + /** * Verify that updating a header user field will be updated if the store state is cached. */ diff --git a/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/FDBDatabaseExtension.java b/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/FDBDatabaseExtension.java index 144395abbc..a20fc4455b 100644 --- a/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/FDBDatabaseExtension.java +++ b/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/FDBDatabaseExtension.java @@ -43,6 +43,8 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -84,6 +86,16 @@ public FDBDatabaseExtension() { @Nonnull private static final Executor threadPoolExecutor = TestExecutors.newThreadPool("fdb-record-layer-test"); + // Override the per-factory scheduled executor so parallel tests don't fight over the + // JVM-wide single-thread ScheduledThreadPoolExecutor that MoreAsyncUtil installs by + // default. When the suite runs N tests concurrently and they each schedule deadline + // timers on a single shared thread, the timers fire late and trigger spurious + // DeadlineExceededException from places like AsyncLoadingCache (e.g. + // FDBDatabase.resolverStateCache during KeySpacePath.toTuple cleanup). + @Nonnull + private static final ScheduledExecutorService scheduledThreadPoolExecutor = Executors.newScheduledThreadPool( + Math.max(Runtime.getRuntime().availableProcessors(), 4), + new TestExecutors.TestThreadFactory("fdb-record-layer-test-scheduled")); public static APIVersion getAPIVersion() { String apiVersionStr = System.getProperty(API_VERSION_PROPERTY); @@ -149,6 +161,7 @@ public FDBDatabaseFactory getDatabaseFactory() { } setupBlockingInAsyncDetection(databaseFactory); databaseFactory.setExecutor(threadPoolExecutor); + databaseFactory.setScheduledExecutor(scheduledThreadPoolExecutor); } return databaseFactory; } diff --git a/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/TestKeySpace.java b/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/TestKeySpace.java index a00012f24c..9903509e63 100644 --- a/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/TestKeySpace.java +++ b/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/TestKeySpace.java @@ -55,7 +55,16 @@ public class TestKeySpace { .addSubdirectory(new DirectoryLayerDirectory(META_DATA_STORE, META_DATA_STORE)) .addSubdirectory(new DirectoryLayerDirectory(RAW_DATA, RAW_DATA)) .addSubdirectory(new DirectoryLayerDirectory(MULTI_RECORD_STORE, MULTI_RECORD_STORE) - .addSubdirectory(new DirectoryLayerDirectory(STORE_PATH)) + // STORE_PATH is a plain LONG-typed KeySpaceDirectory rather than a + // DirectoryLayerDirectory. It used to be a DirectoryLayerDirectory, + // which meant every unique test-supplied sub-store key (e.g. "store_0") + // triggered a fresh allocation against the JVM-wide root + // HighContentionAllocator. Under parallel test execution that racy + // allocator was hitting hasConflictAtRoot failures with sibling tests. + // A LONG here lets each test supply its own integer sub-store id + // (0, 1, 2, ...) which encodes directly into the tuple with no shared + // allocator involved. + .addSubdirectory(new KeySpaceDirectory(STORE_PATH, KeySpaceDirectory.KeyType.LONG)) ) .addSubdirectory(new DirectoryLayerDirectory(RESOLVER_MAPPING_REPLICATOR, RESOLVER_MAPPING_REPLICATOR) .addSubdirectory(new KeySpaceDirectory("to", KeySpaceDirectory.KeyType.STRING, "to") diff --git a/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java b/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java index 7f953ee8da..8a9815a4e5 100644 --- a/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java +++ b/fdb-record-layer-core/src/testFixtures/java/com/apple/foundationdb/record/test/TestKeySpacePathManager.java @@ -20,7 +20,9 @@ package com.apple.foundationdb.record.test; +import com.apple.foundationdb.async.MoreAsyncUtil; import com.apple.foundationdb.record.RecordCoreException; +import com.apple.foundationdb.record.RecordCoreRetriableTransactionException; import com.apple.foundationdb.record.logging.KeyValueLogMessage; import com.apple.foundationdb.record.logging.LogMessageKeys; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; @@ -89,6 +91,50 @@ public KeySpacePath createPath(String... pathElements) { LogMessageKeys.KEY_SPACE_PATH, path)); } assertTrue(paths.add(path), "UUID collision"); + + // Eagerly resolve the path under a JVM-wide lock. Path resolution goes through + // FDBDatabase.resolverStateCache, an AsyncLoadingCache with a hardcoded 5-second + // deadline (AsyncLoadingCache.DEFAULT_DEADLINE_TIME_MILLIS). When many parallel + // tests resolve directory-layer entries concurrently, the FDB-side work appears + // to contend with itself (retries triggered by conflicting commits on the + // resolver state) and the 5-second deadline fires from random call sites in + // test bodies and afterEach hooks. Serialising the resolution here means the + // resolver-state cache is warm by the time the test actually uses the path, + // so KeySpacePath.toTuple / FDBRecordStore.createOrOpen / deleteAllData calls + // hit the cache instead of racing on directory-layer reads. + // + // Use db.newRunner so we get the runner's standard retry loop for free. + // DeadlineExceededException extends LoggableException (not FDBException nor + // RecordCoreRetriableTransactionException) so the runner doesn't consider it + // retriable by default; wrap it explicitly to opt in. + // + // TODO: investigate whether the directory layer / LocatableResolver has a bug + // where concurrent resolutions of distinct keys fail to make progress on + // retry (rather than just being slow). If yes, the production-side fix would + // remove the need for this workaround entirely. See e.g. + // FDBDatabase.resolverStateCache, LocatableResolver.loadResolverState. + synchronized (TestKeySpacePathManager.class) { + final FDBRecordContextConfig.Builder config = FDBRecordContextConfig.newBuilder() + .setTransactionId("pathManager_createPath_" + UUID.randomUUID()) + .setLogTransaction(true) + .setMdcContext(MDC.getCopyOfContextMap()); + try (FDBDatabaseRunner runner = db.newRunner(config)) { + final KeySpacePath finalPath = path; + runner.run(context -> { + try { + finalPath.toTuple(context); + } catch (MoreAsyncUtil.DeadlineExceededException e) { + // The 5-second AsyncLoadingCache deadline occasionally fires + // under heavy parallel test load even though the work would + // succeed on retry. Wrap so the runner's retry loop picks it up. + throw new RecordCoreRetriableTransactionException( + "deadline exceeded resolving test key space path", e); + } + return null; + }); + } + } + return path; } @@ -105,12 +151,22 @@ public void close() { .setMdcContext(MDC.getCopyOfContextMap()); try (FDBDatabaseRunner runner = db.newRunner(config)) { runner.run(context -> { - for (KeySpacePath path : paths) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug(KeyValueLogMessage.of("deleting test key space path", - LogMessageKeys.KEY_SPACE_PATH, path.toString(path.toTuple(context)))); + try { + for (KeySpacePath path : paths) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(KeyValueLogMessage.of("deleting test key space path", + LogMessageKeys.KEY_SPACE_PATH, path.toString(path.toTuple(context)))); + } + path.deleteAllData(context); } - path.deleteAllData(context); + } catch (MoreAsyncUtil.DeadlineExceededException e) { + // Same reason as in createPath: the 5-second AsyncLoadingCache + // deadline on FDBDatabase.resolverStateCache occasionally fires + // under heavy parallel test load. Wrap as a retriable exception + // so the runner's retry loop picks it up instead of failing the + // afterEach cleanup. + throw new RecordCoreRetriableTransactionException( + "deadline exceeded resolving test key space path during cleanup", e); } return null; }); diff --git a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexMaintenanceTest.java b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexMaintenanceTest.java index 607845f974..601d031814 100644 --- a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexMaintenanceTest.java +++ b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexMaintenanceTest.java @@ -72,6 +72,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -139,6 +141,9 @@ * Tests of the consistency of the Lucene Index. */ @Tag(Tags.RequiresFDB) +// Runs SAME_THREAD because this class is FDB-heavy and saturates the shared cluster's Batch GRV +// quota when run concurrently with itself or other FDB-heavy tests. +@Execution(ExecutionMode.SAME_THREAD) public class LuceneIndexMaintenanceTest extends FDBRecordStoreConcurrentTestBase { private static final Logger LOGGER = LoggerFactory.getLogger(LuceneIndexMaintenanceTest.class); diff --git a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexTest.java b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexTest.java index bff8ec1788..0ee2e7679e 100644 --- a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexTest.java +++ b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/LuceneIndexTest.java @@ -101,6 +101,8 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -226,6 +228,11 @@ */ @SuppressWarnings({"resource", "SameParameterValue"}) @Tag(Tags.RequiresFDB) +// Runs SAME_THREAD because this class (and its subclass below) is FDB-heavy and saturates the +// shared cluster's Batch GRV quota when run concurrently with itself or other FDB-heavy tests +// (LuceneIndexMaintenanceTest, AgilityContextTest). @Execution is not @Inherited, so the nested +// LuceneIndexWithLuceneAsyncToSyncTest below repeats this annotation. +@Execution(ExecutionMode.SAME_THREAD) public class LuceneIndexTest extends FDBLuceneTestBase { private static final Logger LOGGER = LoggerFactory.getLogger(LuceneIndexTest.class); @@ -6313,6 +6320,7 @@ void luceneIndexAttributesNotOptimizedForMutualIndexing() { /** * A version of the tests that runs with the new version of asyncToSync. */ + @Execution(ExecutionMode.SAME_THREAD) public static class LuceneIndexWithLuceneAsyncToSyncTest extends LuceneIndexTest { @Override protected RecordLayerPropertyStorage.Builder addDefaultProps(final RecordLayerPropertyStorage.Builder props) { diff --git a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java index 1c8635802a..0201c33f51 100644 --- a/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java +++ b/fdb-record-layer-lucene/src/test/java/com/apple/foundationdb/record/lucene/directory/AgilityContextTest.java @@ -45,6 +45,8 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.CsvSource; @@ -74,6 +76,9 @@ * Tests for AgilityContext. */ @Tag(Tags.RequiresFDB) +// Runs SAME_THREAD because this class commits to the shared FDB cluster and races (write-write +// conflicts, GRV throttling) with other FDB-heavy lucene tests when run concurrently. +@Execution(ExecutionMode.SAME_THREAD) class AgilityContextTest extends FDBRecordStoreTestBase { enum AgilityContextType { AGILE, NON_AGILE, READ_ONLY } @@ -626,6 +631,14 @@ void testReadOnlyHasCallerGrv() throws Exception { byte[] key; FDBRecordContext earlyContext = openContext(); + // Pin earlyContext's read version *now*, before laterContext writes. Otherwise the GRV + // is deferred until the first FDB read against earlyContext — and if path.toSubspace() + // below is served from an in-JVM directory-layer cache (which happens whenever other + // tests in the same JVM have already warmed that resolver), no FDB round-trip occurs + // and earlyContext's GRV ends up being grabbed later — after laterContext commits — + // via `callerContext.getReadVersion()` inside ReadOnlyNonAgileContext.ctor. That would + // let earlyContext see the "Hello" write and break the invariant this test is checking. + earlyContext.getReadVersion(); final Subspace subspace = path.toSubspace(earlyContext); key = subspace.pack(Tuple.from("something")); diff --git a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/Options.java b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/Options.java index a159ee60d9..ad78486be4 100644 --- a/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/Options.java +++ b/fdb-relational-api/src/main/java/com/apple/foundationdb/relational/api/Options.java @@ -257,6 +257,15 @@ public enum Name { * A boolean indicating whether to (attempt to) compress records when saving. */ COMPRESS_WHEN_SERIALIZING, + + /** + * A boolean indicating whether the underlying {@code FDBRecordContext} should be configured to + * populate its list of not-committed conflicting keys after a failed commit. Useful for + * diagnosing serialization failures — when enabled, the connection's commit failure message + * will include the concrete FDB key ranges that conflicted. Off by default (there is a small + * extra read against the FDB special-key-space on commit failure when it is on). + */ + REPORT_CONFLICTING_KEYS, } public enum IndexFetchMethod { @@ -300,6 +309,7 @@ public enum IndexFetchMethod { builder.put(Name.ENCRYPT_WHEN_SERIALIZING, false); builder.put(Name.ENCRYPTION_KEY_PASSWORD, ""); builder.put(Name.COMPRESS_WHEN_SERIALIZING, true); + builder.put(Name.REPORT_CONFLICTING_KEYS, false); OPTIONS_DEFAULT_VALUES = builder.build(); } @@ -560,6 +570,7 @@ private static Map> makeContracts() { data.put(Name.ENCRYPTION_KEY_ENTRY_LIST, List.of(new OrderedCollectionContract<>(TypeContract.stringType()))); data.put(Name.ENCRYPTION_KEY_PASSWORD, List.of(TypeContract.nullableStringType())); data.put(Name.COMPRESS_WHEN_SERIALIZING, List.of(TypeContract.booleanType())); + data.put(Name.REPORT_CONFLICTING_KEYS, List.of(TypeContract.booleanType())); return Collections.unmodifiableMap(data); } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java index bb9c0671f5..ed4ed623fa 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalConnection.java @@ -20,10 +20,12 @@ package com.apple.foundationdb.relational.recordlayer; +import com.apple.foundationdb.Range; import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.record.ExecuteProperties; import com.apple.foundationdb.record.IsolationLevel; import com.apple.foundationdb.record.RecordCoreException; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; import com.apple.foundationdb.relational.api.EmbeddedRelationalDriver; import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; import com.apple.foundationdb.relational.api.Options; @@ -48,11 +50,15 @@ import com.apple.foundationdb.relational.recordlayer.metric.StoreTimerMetricCollector; import com.apple.foundationdb.relational.recordlayer.structuredsql.expression.ExpressionFactoryImpl; import com.apple.foundationdb.relational.recordlayer.structuredsql.statement.StatementBuilderFactoryImpl; +import com.apple.foundationdb.relational.recordlayer.util.ConflictKeyFormatter; import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; import com.apple.foundationdb.relational.util.SpotBugsSuppressWarnings; import com.apple.foundationdb.relational.util.Supplier; import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.URI; @@ -62,6 +68,7 @@ import java.sql.SQLWarning; import java.sql.Struct; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.stream.Collectors; @@ -81,6 +88,8 @@ @API(API.Status.EXPERIMENTAL) public class EmbeddedRelationalConnection implements RelationalConnection { + private static final Logger LOGGER = LoggerFactory.getLogger(EmbeddedRelationalConnection.class); + /** * Chose a transaction level that we *pretend* to support (Rather than throw an exception that stops * all processing). @@ -199,6 +208,10 @@ void commitInternal() throws SQLException { getTransaction().commit(); } catch (RuntimeException | RelationalException re) { err = ExceptionUtil.toRelationalException(re); + // Best-effort: if the underlying FDB context was configured with + // Options.Name.REPORT_CONFLICTING_KEYS, pull the not-committed conflict ranges out and + // enrich the exception (and log) so we can see which key(s) actually collided. + err = attachConflictingKeys(err); } try { getTransaction().close(); @@ -215,6 +228,45 @@ void commitInternal() throws SQLException { } } + /** + * If the transaction's FDB context had {@code reportConflictingKeys} enabled and the commit + * failed with NOT_COMMITTED, rewrap the exception with a message that includes the concrete + * conflict ranges (so they appear in every stack trace) and also attach them to the exception's + * context map. Never throws — instrumentation must not itself fail the commit path. + * + * @param err the original exception; returned unchanged if extraction fails or reporting is off + * @return the (possibly-rewrapped) exception with conflict information baked into its message + */ + @Nonnull + private RelationalException attachConflictingKeys(@Nonnull RelationalException err) { + try { + final Transaction txn = transaction; + if (txn == null) { + return err; + } + final FDBRecordContext ctx = txn.unwrap(FDBRecordContext.class); + final List ranges = ctx.getNotCommittedConflictingKeys(); + if (ranges == null) { + return err; // reporting was not enabled, or commit did not fail with NOT_COMMITTED + } + final String rendered = ConflictKeyFormatter.formatRanges(ranges); + final String enrichedMessage = (err.getMessage() == null ? "" : err.getMessage()) + + " [conflicting_keys_count=" + ranges.size() + ", conflicting_keys=" + rendered + "]"; + LOGGER.warn("FDB transaction commit failed with conflicting keys: count={} ranges={}", + ranges.size(), rendered); + final RelationalException rewrapped = new RelationalException(enrichedMessage, err.getErrorCode(), err); + rewrapped.addContext("conflicting_keys", rendered); + rewrapped.addContext("conflicting_keys_count", ranges.size()); + // Preserve any context that was already on the original exception. + rewrapped.withContext(err.getContext()); + return rewrapped; + } catch (Exception e) { + // Never let diagnostic extraction mask the real error. + LOGGER.debug("Failed to extract conflicting keys from failed commit", e); + return err; + } + } + @Override public void rollback() throws SQLException { checkOpen(); diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordLayerTransactionManager.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordLayerTransactionManager.java index 8cf472ece2..92e5a70996 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordLayerTransactionManager.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RecordLayerTransactionManager.java @@ -66,6 +66,10 @@ private FDBRecordContextConfig getFDBRecordContextConfig(@Nonnull Options option if (transactionTimeout != null) { builder.setTransactionTimeoutMillis(transactionTimeout); } + Boolean reportConflictingKeys = options.getOption(Options.Name.REPORT_CONFLICTING_KEYS); + if (Boolean.TRUE.equals(reportConflictingKeys)) { + builder.setReportConflictingKeys(true); + } return builder.build(); } } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RelationalKeyspaceProvider.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RelationalKeyspaceProvider.java index ef82783624..5fa6769c6b 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RelationalKeyspaceProvider.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/RelationalKeyspaceProvider.java @@ -188,7 +188,14 @@ private static KeySpaceDirectory getSystemDirectory() { .addSubdirectory(new KeySpaceDirectory(INTERNING_LAYER, KeySpaceDirectory.KeyType.STRING, INTERNING_LAYER_VALUE)); } - public void registerDomainIfNotExists(@Nonnull String domainName) { + public synchronized void registerDomainIfNotExists(@Nonnull String domainName) { + // Synchronized because this mutates the singleton KeySpace's tree via + // addSubdirectory. Without the lock, two concurrent callers can both observe "not + // present" and both add the same domain, leaving the tree with two subdirectories + // matching the same name — every subsequent path resolution then throws + // "<...> is ambigous" from KeySpaceUtils#matchPathToSubdirectories. That surfaces + // once multiple test-class @BeforeAll hooks (or parallel FRL constructions) hit + // this on the same JVM. final var keySpaceRoot = getKeySpace().getRoot(); final var exists = keySpaceRoot.getSubdirectories().stream() .map(KeySpaceDirectory::getName) diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java index 35e93a0574..64a193b706 100644 --- a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalog.java @@ -180,8 +180,14 @@ StoreCatalog initialize(@Nonnull final Transaction createTxn, @Nonnull final Sch schemaTemplateCatalog.createTemplate(createTxn, this.catalogSchemaTemplate); } this.schemaTemplateCatalog = schemaTemplateCatalog; - // Persist our hard-coded catalog schema. - saveSchema(createTxn, this.catalogSchema, true); + // Persist our hard-coded catalog schema — but only if it isn't already there. Every + // FRL construction and every proactive test-time initialiser calls this method; + // writing the same schema row on every call turned every parallel init into a + // write-write commit conflict on the catalog table. Gate on doesSchemaExist so the + // second and subsequent inits become read-only and can commit concurrently. + if (!doesSchemaExist(createTxn, URI.create(this.catalogSchema.getDatabaseName()), this.catalogSchema.getName())) { + saveSchema(createTxn, this.catalogSchema, true); + } } catch (RecordCoreStorageException ex) { throw ExceptionUtil.toRelationalException(ex); } diff --git a/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/ConflictKeyFormatter.java b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/ConflictKeyFormatter.java new file mode 100644 index 0000000000..da82689411 --- /dev/null +++ b/fdb-relational-core/src/main/java/com/apple/foundationdb/relational/recordlayer/util/ConflictKeyFormatter.java @@ -0,0 +1,103 @@ +/* + * ConflictKeyFormatter.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2021-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.relational.recordlayer.util; + +import com.apple.foundationdb.Range; +import com.apple.foundationdb.annotation.API; +import com.apple.foundationdb.tuple.ByteArrayUtil; +import com.apple.foundationdb.tuple.Tuple; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Utilities for rendering FDB conflict ranges (returned by + * {@code FDBRecordContext.getNotCommittedConflictingKeys()}) into strings suitable for logging + * and inclusion in exception messages. + * + *

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

+ */ +@API(API.Status.EXPERIMENTAL) +public final class ConflictKeyFormatter { + + private ConflictKeyFormatter() { + } + + /** + * Render a list of conflict ranges as a compact human-readable string. + * Returns {@code "[]"} for an empty list, {@code "null"} for null. + * + * @param ranges the conflict ranges, or null + * @return a comma-separated description of the ranges enclosed in brackets + */ + @Nonnull + public static String formatRanges(@Nullable List ranges) { + if (ranges == null) { + return "null"; + } + if (ranges.isEmpty()) { + return "[]"; + } + return ranges.stream() + .map(ConflictKeyFormatter::formatRange) + .collect(Collectors.joining(", ", "[", "]")); + } + + /** + * Render a single conflict range as {@code "begin..end"} using {@link #formatKey(byte[])} for each bound. + * @param range the range + * @return a printable description + */ + @Nonnull + public static String formatRange(@Nonnull Range range) { + return formatKey(range.begin) + ".." + formatKey(range.end); + } + + /** + * Render a single FDB key. First attempts to decode as a {@link Tuple}; if that fails, falls back to + * {@link ByteArrayUtil#printable(byte[])}. + * @param key the raw key bytes, may be null + * @return a printable description + */ + @Nonnull + public static String formatKey(@Nullable byte[] key) { + if (key == null) { + return "null"; + } + if (key.length == 0) { + return "\"\""; + } + // System-key-space keys start with 0xff; don't try to Tuple-decode them (they aren't tuples). + if ((key[0] & 0xff) != 0xff) { + try { + final Tuple t = Tuple.fromBytes(key); + return t.toString(); + } catch (RuntimeException ignored) { + // Fall through to raw byte rendering. + } + } + return ByteArrayUtil.printable(key); + } +} diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java index 64bf2a3a16..3ad776d668 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/SqlInsertTest.java @@ -41,8 +41,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.sql.DriverManager; import java.util.Map; +import java.net.URI; public class SqlInsertTest { @@ -52,7 +52,7 @@ public class SqlInsertTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(BasicMetadataTest.class, + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, BasicMetadataTest.class, """ CREATE TABLE simple (rest_no bigint, name string, primary key(rest_no)) CREATE TYPE AS STRUCT location (address string, latitude string, longitude string) @@ -63,7 +63,7 @@ CREATE TABLE with_loc (rest_no bigint, name string, loc location, primary key(re @ParameterizedTest @ValueSource(booleans = {true, false}) void canInsertUsingSqlSyntaxAndSingleQuotes(boolean useNamed) throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (RelationalStatement stmt = conn.createStatement()) { @@ -83,7 +83,7 @@ void canInsertUsingSqlSyntaxAndSingleQuotes(boolean useNamed) throws Exception { @Test void canInsertStructUnnamed() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (RelationalStatement stmt = conn.createStatement()) { @@ -103,7 +103,7 @@ void canInsertStructUnnamed() throws Exception { @Test void canInsertStructNamed() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (RelationalStatement stmt = conn.createStatement()) { @@ -123,7 +123,7 @@ void canInsertStructNamed() throws Exception { @Test void cannotInsertWithInsertRuleDisabled() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { // Turn off the planner rule that allows for inserts to be planned conn.setOption(Options.Name.DISABLED_PLANNER_RULES, ImmutableSet.of(ImplementInsertRule.class.getSimpleName())); conn.setSchema(database.getSchemaName()); @@ -143,7 +143,7 @@ void cannotInsertWithInsertRuleDisabled() throws Exception { @Test @Disabled() void canInsertStructWithStructFieldsNamed() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (RelationalStatement stmt = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java index 7474d9a106..7113ec0633 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/DriverManagerTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; @@ -34,6 +35,17 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +/** + * This is the only test in the relational-core test suite that interacts with + * {@link DriverManager} directly. Every other test gets its driver via + * {@code relationalExtension.getDriver()}. + *

+ * Marked {@link Isolated} because {@code @BeforeEach} clears the entire JVM-wide DriverManager + * registry. Without isolation, running this concurrently with any other test would race and pull + * the driver out from under it. {@link Isolated} tells JUnit Jupiter to suspend all other tests + * while this class runs. + */ +@Isolated class DriverManagerTest { RelationalDriver driver1 = Mockito.mock(RelationalDriver.class); RelationalDriver driver2 = Mockito.mock(RelationalDriver.class); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java index ba708364bd..e5a4d71b97 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/RelationalConnectionTest.java @@ -29,8 +29,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import java.net.URI; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; class RelationalConnectionTest { @@ -40,26 +40,27 @@ class RelationalConnectionTest { public final EmbeddedRelationalExtension relationalExtension = new EmbeddedRelationalExtension(); @Test - void wrongScheme() { - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("foo")) - .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("foo:foo")) - .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:foo")) - .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed")) - .hasErrorCode(ErrorCode.UNABLE_TO_ESTABLISH_SQL_CONNECTION); - - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed:/i_am_not_a_database")) + void wrongScheme() throws SQLException { + // The JDBC Driver contract says connect() returns null for URLs the driver doesn't + // accept (so DriverManager can chain to another driver). Calling driver.connect() with + // no-scheme / wrong-scheme URLs directly therefore returns null rather than throwing — + // which is correct per-spec. (Going through DriverManager.getConnection() would convert + // the null into a "No suitable driver found" SQLException; we deliberately avoid that + // here because DriverManagerTest is the single test that exercises DriverManager.) + Assertions.assertThat(relationalExtension.getDriver().connect(URI.create("foo"))).isNull(); + Assertions.assertThat(relationalExtension.getDriver().connect(URI.create("foo:foo"))).isNull(); + Assertions.assertThat(relationalExtension.getDriver().connect(URI.create("jdbc:foo"))).isNull(); + Assertions.assertThat(relationalExtension.getDriver().connect(URI.create("jdbc:embed"))).isNull(); + + // The URL IS accepted (it starts with "jdbc:embed:"), but the path resolves to a + // database that doesn't exist. The driver throws here rather than returning null. + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:/i_am_not_a_database"))) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE); } @Test void missingLeadingSlash() { - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed:i_am_not_a_database")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:i_am_not_a_database"))) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE) .containsInMessage("") .doesNotContainInMessage("") @@ -69,7 +70,7 @@ void missingLeadingSlash() { @Test void setWrongSchema() throws SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS"))) { RelationalAssertions.assertThrowsSqlException(() -> conn.setSchema("foo")) .hasErrorCode(ErrorCode.UNDEFINED_SCHEMA); } @@ -77,26 +78,26 @@ void setWrongSchema() throws SQLException { @Test void canConnectDirectlyToSchema() throws SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS?schema=CATALOG")) { + try (final var conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS?schema=CATALOG"))) { Assertions.assertThat(conn.getSchema()).isEqualTo("CATALOG"); } } @Test void connectDirectlyToNonexistentDatabaseBlowsUp() { - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed:/notADatabase?schema=CATALOG")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:/notADatabase?schema=CATALOG"))) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE); } @Test void connectDirectlyToNonexistentSchemaBlowsUp() { - RelationalAssertions.assertThrowsSqlException(() -> DriverManager.getConnection("jdbc:embed:/__SYS?schema=noSuchSchema")) + RelationalAssertions.assertThrowsSqlException(() -> relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS?schema=noSuchSchema"))) .hasErrorCode(ErrorCode.UNDEFINED_SCHEMA); } @Test void setIsolationLevel() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { // Default isolation level Assertions.assertThat(conn.getTransactionIsolation()).isEqualTo(Connection.TRANSACTION_SERIALIZABLE); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java index 0b544bfddd..a789298462 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/DdlStatementParsingTest.java @@ -106,12 +106,12 @@ public class DdlStatementParsingTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DdlStatementParsingTest.class, TestSchemas.books(), + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DdlStatementParsingTest.class, TestSchemas.books(), Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build(), null); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA") .withOptions(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java index e17f22f4d4..2b1edf5738 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/IndexTest.java @@ -78,11 +78,11 @@ public class IndexTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DdlStatementParsingTest.class, TestSchemas.books()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DdlStatementParsingTest.class, TestSchemas.books()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @BeforeAll diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/SqlFunctionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/SqlFunctionTest.java index 15fc4e46ca..54c29057cd 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/SqlFunctionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/api/ddl/SqlFunctionTest.java @@ -64,11 +64,11 @@ public class SqlFunctionTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DdlStatementParsingTest.class, TestSchemas.books()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DdlStatementParsingTest.class, TestSchemas.books()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @BeforeAll diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java index 8d7a6c2cad..710b55f29a 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/DirectPrimaryKeyScanTest.java @@ -46,7 +46,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import java.net.URI; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; @@ -78,7 +77,7 @@ public class DirectPrimaryKeyScanTest { public Connector relationalConnector = new Connector() { @Override public RelationalConnection connect(URI dbUri) throws SQLException { - return DriverManager.getConnection("jdbc:embed:" + dbUri.getPath()).unwrap(RelationalConnection.class); + return relational.getDriver().connect(URI.create("jdbc:embed:" + dbUri.getPath())).unwrap(RelationalConnection.class); } @Override diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java index 576b70408c..9df702c5c0 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/autotest/cases/KnownPkGetTest.java @@ -46,7 +46,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import java.net.URI; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; @@ -82,7 +81,7 @@ public KnownPkGetTest() { public Connector relationalConnector = new Connector() { @Override public RelationalConnection connect(URI dbUri) throws SQLException { - return DriverManager.getConnection("jdbc:embed:" + dbUri.getPath()).unwrap(RelationalConnection.class); + return relational.getDriver().connect(URI.create("jdbc:embed:" + dbUri.getPath())).unwrap(RelationalConnection.class); } @Override diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java index 9924ae453e..83383568fa 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCEmbedDriverTest.java @@ -20,38 +20,22 @@ package com.apple.foundationdb.relational.jdbc; -import com.apple.foundationdb.record.provider.foundationdb.APIVersion; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; -import com.apple.foundationdb.test.FDBTestEnvironment; -import com.apple.foundationdb.relational.api.EmbeddedRelationalDriver; -import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalDriver; -import com.apple.foundationdb.relational.api.catalog.StoreCatalog; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; -import com.apple.foundationdb.relational.recordlayer.DirectFdbConnection; -import com.apple.foundationdb.relational.recordlayer.RecordLayerConfig; -import com.apple.foundationdb.relational.recordlayer.RecordLayerEngine; +import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; -import com.apple.foundationdb.relational.recordlayer.catalog.StoreCatalogProvider; -import com.apple.foundationdb.relational.recordlayer.ddl.RecordLayerMetadataOperationsFactory; -import com.apple.foundationdb.relational.recordlayer.query.cache.RelationalPlanCache; import com.apple.foundationdb.relational.util.BuildVersion; +import com.apple.foundationdb.relational.utils.CatalogOperations; -import com.codahale.metrics.MetricRegistry; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.Driver; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.sql.Types; -import java.util.Collections; /** * Run some simple Statement updates/executes against the JDBC Embed JDBC Driver. @@ -61,54 +45,15 @@ */ public class JDBCEmbedDriverTest { - private static RelationalDriver driver; - - @BeforeAll - public static void beforeAll() throws SQLException, RelationalException { - RelationalKeyspaceProvider.instance().registerDomainIfNotExists("FRL"); - - RecordLayerConfig rlCfg = RecordLayerConfig.getDefault(); - //here we are extending the StorageCluster so that we can track which internal Databases were - // connected to and we can validate that they were all closed properly - - // This needs to be done prior to the first call to factory.getDatabase() - FDBDatabaseFactory.instance().setAPIVersion(APIVersion.API_VERSION_7_1); - - final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(FDBTestEnvironment.randomClusterFile()); - StoreCatalog storeCatalog; - try (var txn = new DirectFdbConnection(database).getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, RelationalKeyspaceProvider.instance().getKeySpace()); - txn.commit(); - } - - RecordLayerMetadataOperationsFactory ddlFactory = RecordLayerMetadataOperationsFactory.defaultFactory() - .setBaseKeySpace(RelationalKeyspaceProvider.instance().getKeySpace()) - .setRlConfig(rlCfg) - .setStoreCatalog(storeCatalog) - .build(); - driver = new EmbeddedRelationalDriver(RecordLayerEngine.makeEngine( - rlCfg, - Collections.singletonList(database), - RelationalKeyspaceProvider.instance().getKeySpace(), - storeCatalog, - new MetricRegistry(), - ddlFactory, - RelationalPlanCache.buildWithDefaults())); - DriverManager.registerDriver(driver); //register the engine driver - } - - static Driver getDriver() throws SQLException { - // Use ANY valid URl to get hold of the driver. When we 'connect' we'll - // more specific about where we want to connect to. - return DriverManager.getDriver("jdbc:embed:" + SYSDBPATH); - } + @RegisterExtension + @Order(0) + static final EmbeddedRelationalExtension relationalExtension = new EmbeddedRelationalExtension(); private static final String SYSDBPATH = "/" + RelationalKeyspaceProvider.SYS; private static final String TESTDB = "/FRL/jdbc_test_db"; - @AfterAll - public static void afterAll() throws SQLException { - DriverManager.deregisterDriver(driver); + private static RelationalDriver getDriver() { + return relationalExtension.getDriver(); } @Test @@ -117,17 +62,17 @@ public void testGetPropertyInfo() throws SQLException { } @Test - public void testGetMajorVersion() throws SQLException, RelationalException { + public void testGetMajorVersion() throws Exception { Assertions.assertEquals(getDriver().getMajorVersion(), BuildVersion.getInstance().getMajorVersion()); } @Test - public void testGetMinorVersion() throws SQLException, RelationalException { + public void testGetMinorVersion() throws Exception { Assertions.assertEquals(getDriver().getMinorVersion(), BuildVersion.getInstance().getMinorVersion()); } @Test - public void testJDBCCompliant() throws SQLException { + public void testJDBCCompliant() { Assertions.assertFalse(getDriver().jdbcCompliant()); } @@ -139,8 +84,13 @@ public void testGetParentLogger() { @Test public void simpleStatement() throws SQLException { - var jdbcStr = "jdbc:embed:" + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; - try (final var connection = getDriver().connect(jdbcStr, null)) { + // Register the FRL domain for this test's CREATE DATABASE call below. + RelationalKeyspaceProvider.instance().registerDomainIfNotExists("FRL"); + // Catalog mutations + selects from system tables, all serialised + retried via + // CatalogOperations so we don't race other test classes' /__SYS work. + // Not all of this is DDL, but this is easier, and not worth trying to + // better parallelize our tests + CatalogOperations.runOnCatalog(getDriver(), connection -> { try (Statement statement = connection.createStatement()) { // Make this better... currently returns zero how ever many rows we touch. Assertions.assertEquals(0, statement.executeUpdate("Drop database if exists \"" + TESTDB + "\"")); @@ -183,7 +133,7 @@ public void simpleStatement() throws SQLException { statement.executeUpdate("Drop schema template if exists test_template"); } } - } + }); } private void checkSelectStarFromDatabasesResultSet(ResultSet resultSet) throws SQLException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java index f3ab88316d..bb8d1b46f0 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/AutoCommitTests.java @@ -54,11 +54,11 @@ public class AutoCommitTests { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(AutoCommitTests.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, AutoCommitTests.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); @@ -461,7 +461,7 @@ public void catalogMetadata(TransactionType transactionType) throws SQLException conn = getConnectionWithExistingTransaction(conn, database.getConnectionUri(), alternateDriver); } setAutoCommit(conn, transactionType); - try (final var rs = conn.getMetaData().getTables("/TEST/AutoCommitTests", "TEST_SCHEMA", null, null)) { + try (final var rs = conn.getMetaData().getTables(database.getDatabasePath().getPath(), "TEST_SCHEMA", null, null)) { ResultSetAssert.assertThat(rs) .hasNextRow() .hasNextRow() diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java index 3f67c03c99..a5e151947e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/BasicMetadataTest.java @@ -54,12 +54,12 @@ public class BasicMetadataTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, BasicMetadataTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule dbConn = new RelationalConnectionRule(database::getConnectionUri); + public final RelationalConnectionRule dbConn = new RelationalConnectionRule(relationalExtension, database::getConnectionUri); @Test void canGetPrimaryKeysForTable() throws SQLException { @@ -162,10 +162,10 @@ void canGetTableColumns() throws SQLException { void canGetTableIndexes() throws SQLException { final RelationalDatabaseMetaData metaData = dbConn.getMetaData(); Assertions.assertNotNull(metaData, "Null metadata returned"); - try (final RelationalResultSet tableData = metaData.getIndexInfo("/TEST/BasicMetadataTest", "TEST_SCHEMA", "RESTAURANT_REVIEWER", false, false)) { + try (final RelationalResultSet tableData = metaData.getIndexInfo(database.getDatabasePath().getPath(), "TEST_SCHEMA", "RESTAURANT_REVIEWER", false, false)) { ResultSetAssert.assertThat(tableData).hasNextRow() .isRowExactly( - "/TEST/BasicMetadataTest", //table_cat + database.getDatabasePath().getPath(), //table_cat "TEST_SCHEMA", //table_schem "RESTAURANT_REVIEWER", //table_name false, //non_unique diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java index 5e0ea17305..1ca8df5cee 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitiveDbObjectsTest.java @@ -35,14 +35,22 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; -import java.sql.DriverManager; import java.sql.SQLException; +import java.net.URI; /** * Test case-sensitive db object connection option. */ +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class CaseSensitiveDbObjectsTest { @Nonnull private static final String SCHEMA_TEMPLATE = @@ -56,13 +64,13 @@ public class CaseSensitiveDbObjectsTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(CaseSensitiveDbObjectsTest.class, + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, CaseSensitiveDbObjectsTest.class, SCHEMA_TEMPLATE, Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build(), new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()) .withSchema("TEST_SCHEMA"); @@ -153,8 +161,8 @@ void planIsProperlyCachedAndReusedAcrossCaseOptionVariation() throws SQLExceptio Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("select 'id' from 't1' where 'group' = ?"); Assertions.assertThat(logAppender.getLastLogEventMessage()).contains("planCache=\"miss\""); - try (RelationalConnection caseInsensitiveConn = DriverManager.getConnection(database.getConnectionUri() - .toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection caseInsensitiveConn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri() + .toString())).unwrap(RelationalConnection.class)) { caseInsensitiveConn.setSchema("TEST_SCHEMA"); try (RelationalStatement caseInsensitiveStatement = caseInsensitiveConn.createStatement()) { try (RelationalResultSet resultSet = caseInsensitiveStatement.executeQuery( diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java index 918b6bb167..ed9b79e0d5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CaseSensitivityTest.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.Ddl; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.RelationalAssertions; @@ -41,7 +42,6 @@ import org.junit.jupiter.params.provider.ValueSource; import java.net.URI; -import java.sql.DriverManager; import java.sql.Statement; import java.util.ArrayList; import java.util.List; @@ -62,7 +62,7 @@ public CaseSensitivityTest() { @Test void selectFromCaseInsensitiveTable() throws Exception { final String schema = "CREATE TABLE tbl1 (id bigint, value bigint, PRIMARY KEY(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/CaseSensitivity")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { for (String tableName : List.of("tbl1", "TBL1", "TbL1", "\"TBL1\"")) { Assertions.assertDoesNotThrow(() -> statement.execute("SELECT * FROM " + tableName)); @@ -77,8 +77,7 @@ void selectFromCaseInsensitiveTable() throws Exception { @Test public void databaseWithSameUpperName() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a database statement.executeUpdate("CREATE DATABASE /test/upper"); @@ -91,7 +90,8 @@ public void databaseWithSameUpperName() throws Exception { statement.executeUpdate("DROP DATABASE /test/upper"); } } - } + }); + } private String quote(String dbObject, boolean quote) { @@ -106,8 +106,7 @@ private String quote(String dbObject, boolean quote) { @ValueSource(booleans = {true, false}) public void variousDatabases(boolean quoted) throws Exception { List databases = List.of("/TEST/ABC1", "/TEST/def2", "/TEST/Ghi3", "/TEST/jKL4"); - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try { try (final var statement = conn.createStatement()) { // Quoted databases @@ -135,15 +134,15 @@ public void variousDatabases(boolean quoted) throws Exception { } } } - } + }); + } @ParameterizedTest @ValueSource(booleans = {true, false}) public void variousSchemas(boolean quoted) throws Exception { List schemas = List.of("ABC2", "def3", "Ghi4", "jKL5"); - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try { try (final var statement = conn.createStatement()) { statement.executeUpdate("DROP DATABASE if exists /test/various_schemas"); @@ -168,7 +167,8 @@ public void variousSchemas(boolean quoted) throws Exception { statement.executeUpdate("DROP SCHEMA TEMPLATE temp_various_schemas"); } } - } + }); + } @ParameterizedTest @@ -179,8 +179,8 @@ public void variousSchemas(boolean quoted) throws Exception { "SchemaTemplateCatalog))") public void variousStructs(boolean quoted) throws Exception { List structs = List.of(/*"ABC1",*/ "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { for (String struct : structs) { @@ -197,15 +197,16 @@ public void variousStructs(boolean quoted) throws Exception { } } } - } + }); + } @ParameterizedTest @ValueSource(booleans = {true, false}) public void variousTables(boolean quoted) throws Exception { List tables = List.of("ABC1", "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { statement.executeUpdate("DROP DATABASE if exists /test/various_tables_db"); @@ -238,15 +239,16 @@ public void variousTables(boolean quoted) throws Exception { } } } - } + }); + } @ParameterizedTest @ValueSource(booleans = {true, false}) public void variousColumns(boolean quoted) throws Exception { List columns = List.of("ABC1", "def2", "Ghi3", "jKL4"); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { statement.executeUpdate("CREATE DATABASE /test/various_columns_db"); @@ -277,7 +279,8 @@ public void variousColumns(boolean quoted) throws Exception { } } } - } + }); + } @Test @@ -286,8 +289,8 @@ public void overload() throws Exception { List schemas = List.of("schema", "SCHEMA", "Schema", "ScHeMa"); List tables = List.of("table", "TABLE", "Table", "TaBlE"); List columns = List.of("column", "COLUMN", "Column", "CoLuMn"); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try { try (RelationalStatement statement = conn.createStatement()) { for (String schema : schemas) { @@ -315,7 +318,7 @@ public void overload() throws Exception { } long value = 0; // Data insertion - try (RelationalConnection dbConn = DriverManager.getConnection("jdbc:embed:" + database).unwrap(RelationalConnection.class)) { + try (RelationalConnection dbConn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:" + database)).unwrap(RelationalConnection.class)) { try (RelationalStatement statement = dbConn.createStatement()) { for (String schema : schemas) { dbConn.setSchema(schema); @@ -331,7 +334,7 @@ public void overload() throws Exception { } value = 0; // Data retrieval - try (RelationalConnection dbConn = DriverManager.getConnection("jdbc:embed:" + database).unwrap(RelationalConnection.class)) { + try (RelationalConnection dbConn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:" + database)).unwrap(RelationalConnection.class)) { try (RelationalStatement statement = dbConn.createStatement()) { for (String schema : schemas) { dbConn.setSchema(schema); @@ -357,6 +360,7 @@ public void overload() throws Exception { } } } - } + }); + } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java index b4968acdda..8af55bfc88 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/CursorTest.java @@ -26,7 +26,6 @@ import com.apple.foundationdb.relational.api.Row; import com.apple.foundationdb.relational.api.StructMetaData; import com.apple.foundationdb.relational.api.RelationalConnection; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.RelationalStruct; @@ -41,11 +40,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.function.BiConsumer; +import java.net.URI; public class CursorTest { @RegisterExtension @@ -54,7 +53,7 @@ public class CursorTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, CursorTest.class, TestSchemas.restaurant()); @@ -199,7 +198,7 @@ public void continuationWithScanRowLimit() throws SQLException, RelationalExcept Continuation continuation; int numRowsReturned = 0; // 1. Iterate over and count the rows returned before the scan rows limit is hit - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (final var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 3).build())) { conn.setSchema(database.getSchemaName()); try (final var resultSet = conn.createStatement().executeQuery("select * from RESTAURANT")) { @@ -216,7 +215,7 @@ public void continuationWithScanRowLimit() throws SQLException, RelationalExcept } } // 2. Further count the rows in other execution without limits and see if total number of rows is 10 - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema(database.getSchemaName()); try (final var preparedStatement = conn.prepareStatement("EXECUTE CONTINUATION ?param")) { preparedStatement.setBytes("param", continuation.serialize()); @@ -241,7 +240,7 @@ public void continuationWithScanRowLimit() throws SQLException, RelationalExcept private void insertRecordsAndTest(int numRecords, BiConsumer, RelationalConnection> test) throws SQLException, RelationalException { final var records = insertAndReturnRecords(numRecords); - try (final var con = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (final var con = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { con.setSchema(database.getSchemaName()); test.accept(records, con); } @@ -249,7 +248,7 @@ private void insertRecordsAndTest(int numRecords, private List insertAndReturnRecords(int numRecords) throws SQLException { List records; - try (final var con = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (final var con = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { con.setSchema(database.getSchemaName()); final var statement = con.createStatement(); records = Utils.generateRestaurantRecords(numRecords); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java index f05c2d255c..51c1e79f05 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeNoMetadataKeyTest.java @@ -38,7 +38,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.sql.SQLException; import java.util.List; @@ -59,11 +58,11 @@ CREATE TABLE t2 (id bigint, a string, b string, e bigint, f boolean, PRIMARY KEY @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DeleteRangeNoMetadataKeyTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DeleteRangeNoMetadataKeyTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); @@ -397,7 +396,7 @@ void testDeleteWithIndexSamePrefixButDeleteGoesBeyondIndex() throws Exception { private Ddl getDdl(String templateSuffix) throws Exception { return Ddl.builder() - .database(URI.create("/TEST/QT")) + .database() .relationalExtension(relationalExtension) .schemaTemplate(SCHEMA_TEMPLATE + templateSuffix) .schemaTemplateOptions(new SchemaTemplateRule.SchemaTemplateOptions(true, true)) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java index 29f8933c3d..193224bd05 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/DeleteRangeTest.java @@ -37,7 +37,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.util.List; /** @@ -52,11 +51,11 @@ public class DeleteRangeTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(DeleteRangeTest.class, SCHEMA_TEMPLATE); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, DeleteRangeTest.class, SCHEMA_TEMPLATE); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); @@ -221,7 +220,7 @@ void deleteUsingNonKeyColumns() throws Exception { @Test void testDeleteWithIndexWithSamePrefix() throws Exception { final String schemaTemplate = SCHEMA_TEMPLATE + " CREATE INDEX idx1 ON t1(id, a)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var stmt = ddl.setSchemaAndGetConnection().createStatement()) { insertData(stmt); KeySet toDelete = new KeySet() @@ -251,7 +250,7 @@ void testDeleteWithIndexWithSamePrefix() throws Exception { @Test void testDeleteWithIndexSamePrefixButDeleteGoesBeyondIndex() throws Exception { final String schemaTemplate = SCHEMA_TEMPLATE + " CREATE INDEX idx1 ON t1(id)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var stmt = ddl.setSchemaAndGetConnection().createStatement()) { insertData(stmt); KeySet toDelete = new KeySet() diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java index 7944579ff0..bd382294b9 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/EmbeddedRelationalExtension.java @@ -35,6 +35,7 @@ import com.apple.foundationdb.relational.recordlayer.catalog.StoreCatalogProvider; import com.apple.foundationdb.relational.recordlayer.ddl.RecordLayerMetadataOperationsFactory; import com.apple.foundationdb.relational.recordlayer.query.cache.RelationalPlanCache; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.test.FDBTestEnvironment; import com.codahale.metrics.MetricRegistry; import org.junit.jupiter.api.extension.AfterEachCallback; @@ -44,7 +45,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.Closeable; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.Collections; @@ -67,34 +67,31 @@ public class EmbeddedRelationalExtension implements RelationalExtension, BeforeE private StoreCatalog storeCatalog; private FDBDatabase database; private final String clusterFile; - private final boolean register; public EmbeddedRelationalExtension() { this(Options.none()); } public EmbeddedRelationalExtension(@Nonnull final Options options) { - this(FDBTestEnvironment.randomClusterFile(), true, options); + this(FDBTestEnvironment.randomClusterFile(), options); } - public EmbeddedRelationalExtension(final String clusterFile, final boolean register, final Options options) { + public EmbeddedRelationalExtension(final String clusterFile, final Options options) { final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); keyspaceProvider.registerDomainIfNotExists("TEST"); this.keySpace = keyspaceProvider.getKeySpace(); this.storeTimer = new MetricRegistry(); this.clusterFile = clusterFile; - this.register = register; this.options = options; } @Override public void afterEach(ExtensionContext ignored) throws Exception { - if (driver != null) { - if (register) { - DriverManager.deregisterDriver(driver); - } - driver = null; - } + // Drop the per-instance driver reference. Nothing to do with java.sql.DriverManager — + // the extension never registers its driver there. Tests should use this extension's + // {@link #getDriver()} (via field injection into the rules and {@code Ddl}), not + // {@code DriverManager.getConnection}. + driver = null; } @Override @@ -115,9 +112,6 @@ private void setup() throws RelationalException, SQLException { // most likely touch the catalog, and could affect mixed-mode tests engine = makeEngine(database, FormatVersion.getDefaultFormatVersion()); driver = new EmbeddedRelationalDriver(engine); - if (register) { - DriverManager.registerDriver(driver); - } } public EmbeddedRelationalDriver getDriver(@Nonnull final FormatVersion formatVersion) throws SQLException { @@ -126,11 +120,26 @@ public EmbeddedRelationalDriver getDriver(@Nonnull final FormatVersion formatVer private void makeDatabase(String clusterFile) throws RelationalException { database = FDBDatabaseFactory.instance().getDatabase(clusterFile); - try (var connection = new DirectFdbConnection(database); - Transaction txn = connection.getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); - } + // The catalog-bootstrap transaction below writes the cluster-wide catalog metadata, which + // races with every other test class's @BeforeEach. Without the JVM-wide lock, parallel + // class execution surfaces these races as `FDBStoreTransactionConflictException`. See + // CatalogOperations for the lock contract. + // + // Deliberately NOT try-with-resources on the DirectFdbConnection — its close() calls + // fdb.close() on the shared FDBDatabaseFactory handle that every other test in this + // JVM is holding. Closing it here (once per @BeforeEach) invalidates the native pointer + // and any sibling test with an in-flight transaction fails with + // `IllegalStateException: Cannot access closed object` from NativeObjectWrapper.getPtr. + // We only need the Transaction closed; the connection is a lightweight wrapper we can + // leak. + // Probably DirectFdbConnection should not close the underlying database + CatalogOperations.runLockedWithRelationalRetry(() -> { + final var connection = new DirectFdbConnection(database); + try (Transaction txn = connection.getTransactionManager().createTransaction(Options.NONE)) { + storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + } + }); } private EmbeddedRelationalEngine makeEngine(final @Nonnull FDBDatabase database, final @Nonnull FormatVersion formatVersion) { @@ -181,7 +190,7 @@ public EmbeddedRelationalExtension extensionForOtherCluster() throws SQLExceptio final String otherClusterFile = FDBTestEnvironment.allClusterFiles().stream() .filter(clusterFile -> !clusterFile.equals(database.getClusterFile())) .findAny().orElseThrow(); - final EmbeddedRelationalExtension embeddedRelationalExtension = new EmbeddedRelationalExtension(otherClusterFile, false, options); + final EmbeddedRelationalExtension embeddedRelationalExtension = new EmbeddedRelationalExtension(otherClusterFile, options); embeddedRelationalExtension.setup(); return embeddedRelationalExtension; } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java index ec27af321d..c7462a8209 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/InsertTest.java @@ -41,10 +41,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.List; import java.util.Map; +import java.net.URI; public class InsertTest { @RegisterExtension @@ -53,14 +53,14 @@ public class InsertTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(InsertTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, InsertTest.class, TestSchemas.restaurant()); @Test void canInsertWithMultipleRecordTypes() throws SQLException { /* * We want to make sure that we don't accidentally pick up data from different tables */ - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { long id = System.currentTimeMillis(); @@ -150,7 +150,7 @@ void cannotInsertWithIncorrectTypeForRecord() throws SQLException { /* * We want to make sure that we don't accidentally pick up data from different tables */ - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { long id = System.currentTimeMillis(); @@ -164,7 +164,7 @@ final var record = EmbeddedRelationalStruct.newBuilder().addLong("REST_NO", id). @Test void canDifferentiateNullAndDefaultValue() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { long id = System.currentTimeMillis(); @@ -199,7 +199,7 @@ void canDifferentiateNullAndDefaultValue() throws SQLException { @Test void cannotInsertDuplicatePrimaryKey() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { final var struct = EmbeddedRelationalStruct.newBuilder().addLong("REST_NO", 0).build(); @@ -224,7 +224,7 @@ void canInsertNullableArray() throws SQLException { DataType.StructType.Field.from("PRICE", DataType.Primitives.NULLABLE_FLOAT.type(), 1) ), false), false); - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { @@ -282,7 +282,7 @@ void canInsertNullableArray() throws SQLException { @Test void replaceOnInsert() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct record = EmbeddedRelationalStruct.newBuilder() @@ -310,7 +310,7 @@ record = EmbeddedRelationalStruct.newBuilder() @Test void replaceOnFirstInsert() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct record = EmbeddedRelationalStruct.newBuilder().addLong("REST_NO", 0).build(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IntermingledTablesTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IntermingledTablesTest.java index 15f01e5922..104d77691c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IntermingledTablesTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/IntermingledTablesTest.java @@ -55,11 +55,11 @@ CREATE TABLE t2 (group bigint, id string, val2 string, PRIMARY KEY(group, id)) @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(IntermingledTablesTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, IntermingledTablesTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/JoinWithLimitTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/JoinWithLimitTest.java index 7849a88605..7878f9601d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/JoinWithLimitTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/JoinWithLimitTest.java @@ -49,11 +49,11 @@ CREATE TABLE Q(qpk bigint, d D array, PRIMARY KEY(qpk)) @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(UniqueIndexTests.class, getTemplate_definition); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, UniqueIndexTests.class, getTemplate_definition); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(db::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, db::getConnectionUri) .withSchema(db.getSchemaName()); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/LogAppenderRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/LogAppenderRule.java index 3270134982..72729750b1 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/LogAppenderRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/LogAppenderRule.java @@ -36,6 +36,16 @@ import java.util.ArrayList; import java.util.List; +/** + * Test rule that captures Log4j events emitted on a target logger for the duration of a test. + *

+ * The appender is attached to a JVM-global logger, so the rule cannot meaningfully separate its + * own events from events emitted by concurrently-running sibling tests — and a naive thread-id + * filter doesn't help either, because the relational engine dispatches work onto async pools + * (FDB callbacks, CompletableFuture stages, etc.). Test classes that use this rule and assert + * on captured log content should therefore mark themselves {@code @Isolated} so JUnit suspends + * all other tests while they run. See {@code QueryLoggingTest} for an example. + */ public class LogAppenderRule implements BeforeEachCallback, AfterEachCallback, AutoCloseable { private LogAppender logAppender; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java index 557b85efcd..1d8e24bd91 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/NullsInResultSetTest.java @@ -31,7 +31,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.nio.charset.StandardCharsets; public class NullsInResultSetTest { @@ -50,7 +49,7 @@ public NullsInResultSetTest() { @Test void nullValues() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (RelationalConnection conn = ddl.setSchemaAndGetConnection()) { try (RelationalStatement s = conn.createStatement()) { s.execute("INSERT INTO T(PK) VALUES(100)"); @@ -93,7 +92,7 @@ void nullValues() throws Exception { @Test void notNullValues() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (RelationalConnection conn = ddl.setSchemaAndGetConnection()) { try (RelationalStatement s = conn.createStatement()) { final var struct = EmbeddedRelationalStruct.newBuilder() diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OfflinePlanGenerationTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OfflinePlanGenerationTest.java index d3d93166fa..3578de699b 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OfflinePlanGenerationTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OfflinePlanGenerationTest.java @@ -58,12 +58,12 @@ class OfflinePlanGenerationTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, OfflinePlanGenerationTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java index ce8c92d5ae..efaf108712 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/OptionScopeTest.java @@ -21,7 +21,6 @@ package com.apple.foundationdb.relational.recordlayer; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.utils.ResultSetAssert; @@ -34,10 +33,10 @@ import org.junit.jupiter.api.extension.RegisterExtension; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.net.URI; public class OptionScopeTest { @@ -51,11 +50,11 @@ public class OptionScopeTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(UniqueIndexTests.class, TestSchemas.books()); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, UniqueIndexTests.class, TestSchemas.books()); @Test public void optionTakenFromConnection() throws SQLException, RelationalException { - final var driver = (RelationalDriver) DriverManager.getDriver(db.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (Connection conn = driver.connect(db.getConnectionUri(), Options.builder().withOption(Options.Name.DRY_RUN, true).build())) { conn.setSchema(db.getSchemaName()); try (Statement statement = conn.createStatement()) { @@ -69,7 +68,7 @@ public void optionTakenFromConnection() throws SQLException, RelationalException @Test public void optionTakenFromQuery() throws SQLException { - try (Connection conn = DriverManager.getConnection(db.getConnectionUri().toString())) { + try (Connection conn = relationalExtension.getDriver().connect(URI.create(db.getConnectionUri().toString()))) { conn.setSchema(db.getSchemaName()); try (Statement statement = conn.createStatement()) { Assertions.assertThat(statement.executeUpdate(INSERT_QUERY_DRY_RUN)).isOne(); @@ -82,7 +81,7 @@ public void optionTakenFromQuery() throws SQLException { @Test public void optionSetInConnectionButOverriddenInQuery() throws SQLException, RelationalException { - final var driver = (RelationalDriver) DriverManager.getDriver(db.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (Connection conn = driver.connect(db.getConnectionUri(), Options.builder().withOption(Options.Name.DRY_RUN, false).build())) { conn.setSchema(db.getSchemaName()); try (Statement statement = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/PlanGenerationStackTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/PlanGenerationStackTest.java index ac08ab221d..1eec5c9055 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/PlanGenerationStackTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/PlanGenerationStackTest.java @@ -63,11 +63,11 @@ public class PlanGenerationStackTest { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(PlanGenerationStackTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, PlanGenerationStackTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java index b10eb913b4..71d693326f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryLoggingTest.java @@ -25,7 +25,6 @@ import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalPreparedStatement; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.recordlayer.query.AstNormalizer; @@ -39,10 +38,10 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -52,6 +51,13 @@ /** * Testing basic query logging: plan, time, cache hits, etc. */ +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class QueryLoggingTest { @RegisterExtension @Order(0) @@ -59,11 +65,11 @@ public class QueryLoggingTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(QueryLoggingTest.class, TestSchemas.restaurantWithCoveringIndex()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, QueryLoggingTest.class, TestSchemas.restaurantWithCoveringIndex()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); @@ -133,7 +139,7 @@ void testNoLogIfMissing() throws Exception { @Test void testRelationalConnectionOptionPreparedStatement() throws Exception { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.LOG_QUERY, true).build())) { conn.setSchema(database.getSchemaName()); try (PreparedStatement ps = conn.prepareStatement("SELECT name from restaurant where rest_no = ?")) { @@ -154,7 +160,7 @@ void testRelationalConnectionOptionPreparedStatement() throws Exception { @Test void testRelationalConnectionOptionExplicitlyDisabled() throws Exception { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.LOG_QUERY, false).build())) { conn.setSchema(database.getSchemaName()); try (PreparedStatement ps = conn.prepareStatement("SELECT name from restaurant where rest_no = ?")) { @@ -175,7 +181,7 @@ void testRelationalConnectionOptionExplicitlyDisabled() throws Exception { @Test void testRelationalConnectionSetLogOnThenOff() throws Exception { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.NONE)) { conn.setSchema(database.getSchemaName()); try (Statement stmt = conn.createStatement()) { @@ -208,7 +214,7 @@ void testRelationalConnectionSetLogOnThenOff() throws Exception { @Test void testRelationalConnectionSetLogIsOverriddenByQueryOption() throws Exception { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.NONE)) { conn.setSchema(database.getSchemaName()); conn.setOption(Options.Name.LOG_QUERY, false); @@ -226,7 +232,7 @@ void testRelationalConnectionSetLogIsOverriddenByQueryOption() throws Exception @Test void testRelationalConnectionSetLogIsOverriddenByExecuteContinuationQueryOption() throws Exception { insertRows(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.NONE)) { Continuation continuation; conn.setSchema(database.getSchemaName()); @@ -254,7 +260,7 @@ void testRelationalConnectionSetLogIsOverriddenByExecuteContinuationQueryOption( @ValueSource(booleans = {true, false}) void testRelationalConnectionSetLogWithExecuteContinuation(boolean setLogging) throws Exception { insertRows(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.NONE)) { Continuation continuation; conn.setSchema(database.getSchemaName()); @@ -298,7 +304,7 @@ void testLogQueryBecauseQueryIsSlow() throws Exception { resultSet.next(); } Assertions.assertThat(logAppender.getLogEvents()).isEmpty(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.LOG_SLOW_QUERY_THRESHOLD_MICROS, 1L).build())) { conn.setSchema(database.getSchemaName()); try (PreparedStatement ps = conn.prepareStatement("SELECT NAME FROM RESTAURANT")) { @@ -316,7 +322,7 @@ void testLogQueryBecauseQueryIsSlowRemovesLiterals() throws Exception { resultSet.next(); } Assertions.assertThat(logAppender.getLogEvents()).isEmpty(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.LOG_SLOW_QUERY_THRESHOLD_MICROS, 1L).build())) { conn.setSchema(database.getSchemaName()); try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM RESTAURANT WHERE \"NAME\" = 'restaurant 1'")) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java index 8e6d94fe6b..e95ffcb7b8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/QueryPropertiesTest.java @@ -31,7 +31,6 @@ import com.apple.foundationdb.relational.api.KeySet; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.RelationalException; @@ -45,7 +44,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -57,7 +55,7 @@ public class QueryPropertiesTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(QueryPropertiesTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, QueryPropertiesTest.class, TestSchemas.restaurant()); @Test void verifyExecuteAndScanPropertiesGivenQueryProperties() throws SQLException { @@ -93,7 +91,7 @@ void scanWithLimit() throws RelationalException, SQLException { } List testScan(Options options, long firstRestNo) throws RelationalException, SQLException { - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (RelationalConnection conn = driver.connect(database.getConnectionUri(), options)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeKeyTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeKeyTest.java index 21cd11b2cb..4eca57ee5d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeKeyTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeKeyTest.java @@ -43,7 +43,7 @@ public class RecordTypeKeyTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, RecordTypeKeyTest.class, """ CREATE TABLE restaurant_review (reviewer bigint, rating bigint, SINGLE ROW ONLY) @@ -54,7 +54,7 @@ CREATE TABLE restaurant_tag (tag string, weight bigint, PRIMARY KEY(tag)) @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTableSerDeTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTableSerDeTest.java index 6236e5c082..52e4a321db 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTableSerDeTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RecordTypeTableSerDeTest.java @@ -45,14 +45,14 @@ class RecordTypeTableSerDeTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(RecordTypeTableSerDeTest.class, + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, RecordTypeTableSerDeTest.class, """ CREATE TABLE t1(a bigint, b string, c bytes, d boolean, e integer, f float, g double, h uuid, PRIMARY KEY(a)) """); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(db::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, db::getConnectionUri) .withOptions(Options.NONE) .withSchema(db.getSchemaName()); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java index 7b7d08030c..3be3c3fdad 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalArrayTest.java @@ -22,7 +22,6 @@ import com.apple.foundationdb.relational.api.EmbeddedRelationalArray; import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; -import com.apple.foundationdb.relational.api.RelationalPreparedStatement; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.metadata.DataType; @@ -39,7 +38,6 @@ import org.junit.jupiter.params.provider.MethodSource; import javax.annotation.Nonnull; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; @@ -47,6 +45,7 @@ import java.util.List; import java.util.UUID; import java.util.stream.Stream; +import java.net.URI; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -82,10 +81,10 @@ CREATE TABLE B(pk integer, uuid_null uuid array, primary key(pk)) @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(RelationalArrayTest.class, SCHEMA_TEMPLATE); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, RelationalArrayTest.class, SCHEMA_TEMPLATE); public void insertQuery(@Nonnull String q) throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); try (final var s = conn.createStatement()) { @@ -158,10 +157,10 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { "?string_not_null, ?bytes_null, ?bytes_not_null, ?struct_null, ?struct_not_null, ?uuid_null, " + "?uuid_not_null)"; - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement(statement))) { + try (final var ps = conn.prepareStatement(statement)) { ps.setInt("pk", 1); ps.setArray("boolean_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); @@ -185,10 +184,10 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { } } - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement(statement))) { + try (final var ps = conn.prepareStatement(statement)) { ps.setInt("pk", 2); ps.setNull("boolean_null", Types.ARRAY); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); @@ -212,10 +211,10 @@ void testInsertArraysViaQueryPreparedStatement() throws SQLException { } } - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement(statement))) { + try (final var ps = conn.prepareStatement(statement)) { ps.setInt("pk", 2); ps.setArray("boolean_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setNull("boolean_not_null", Types.ARRAY); @@ -253,10 +252,10 @@ void testInsertEmptyArrayViaQueryPreparedStatement() throws SQLException { DataType.StructType.Field.from("B", DataType.StringType.nullable(), 2)), false)).build(); - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement(statement))) { + try (final var ps = conn.prepareStatement(statement)) { ps.setInt("pk", 1); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder(DataType.BooleanType.notNullable()).build()); ps.setArray("integer_not_null", EmbeddedRelationalArray.newBuilder(DataType.IntegerType.notNullable()).build()); @@ -270,7 +269,7 @@ void testInsertEmptyArrayViaQueryPreparedStatement() throws SQLException { Assertions.assertEquals(1, ps.executeUpdate()); } - try (final var ps = (RelationalPreparedStatement) conn.prepareStatement("SELECT * from T where pk = 1")) { + try (final var ps = conn.prepareStatement("SELECT * from T where pk = 1")) { ResultSetAssert.assertThat(ps.executeQuery()) .hasNextRow() .hasColumn("boolean_not_null", List.of()) @@ -288,10 +287,10 @@ void testInsertEmptyArrayViaQueryPreparedStatement() throws SQLException { @Test void testMoreParametersThanColumns() throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk) VALUES (?pk, ?boolean_null, ?boolean_not_null)"))) { + try (final var ps = conn.prepareStatement("INSERT INTO T (pk) VALUES (?pk, ?boolean_null, ?boolean_not_null)")) { ps.setInt("pk", 1); ps.setArray("boolean_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); @@ -303,10 +302,10 @@ void testMoreParametersThanColumns() throws SQLException { @Test void testNonNullViolation() throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk) VALUES (?pk)"))) { + try (final var ps = conn.prepareStatement("INSERT INTO T (pk) VALUES (?pk)")) { ps.setInt("pk", 1); RelationalAssertions.assertThrowsSqlException(ps::executeUpdate).containsInMessage("violates not-null constraint") .hasErrorCode(ErrorCode.NOT_NULL_VIOLATION); @@ -316,13 +315,13 @@ void testNonNullViolation() throws SQLException { @Test void testNullFieldsGetAutomaticallyFilled() throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + + try (final var ps = conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + "integer_not_null, bigint_not_null, float_not_null, double_not_null, string_not_null, bytes_not_null, " + "struct_not_null, uuid_not_null) VALUES (?pk, ?boolean_not_null, ?integer_not_null, ?bigint_not_null, " + - "?float_not_null, ?double_not_null, ?string_not_null, ?bytes_not_null, ?struct_not_null, ?uuid_not_null)"))) { + "?float_not_null, ?double_not_null, ?string_not_null, ?bytes_not_null, ?struct_not_null, ?uuid_not_null)")) { ps.setInt("pk", 2); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("integer_not_null", EmbeddedRelationalArray.newBuilder().addAll(11, 22).build()); @@ -336,7 +335,7 @@ void testNullFieldsGetAutomaticallyFilled() throws SQLException { assertEquals(1, ps.executeUpdate()); } - try (final var ps = (RelationalPreparedStatement) conn.prepareStatement("SELECT * from T where pk = 2")) { + try (final var ps = conn.prepareStatement("SELECT * from T where pk = 2")) { ResultSetAssert.assertThat(ps.executeQuery()) .hasNextRow() .hasColumn("boolean_null", null) @@ -354,13 +353,13 @@ void testNullFieldsGetAutomaticallyFilled() throws SQLException { @Test void testColumnRequiresValue() throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); conn.setAutoCommit(true); - try (final var ps = ((RelationalPreparedStatement) conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + + try (final var ps = conn.prepareStatement("INSERT INTO T (pk, boolean_not_null, " + "integer_not_null, bigint_not_null, float_not_null, double_not_null, string_not_null, bytes_not_null, " + "struct_not_null) VALUES (?pk, ?boolean_not_null, ?integer_not_null, ?bigint_not_null, ?float_not_null, " + - "?double_not_null, ?string_not_null)"))) { + "?double_not_null, ?string_not_null)")) { ps.setInt("pk", 2); ps.setArray("boolean_not_null", EmbeddedRelationalArray.newBuilder().addAll(true, false).build()); ps.setArray("integer_not_null", EmbeddedRelationalArray.newBuilder().addAll(11, 22).build()); @@ -405,7 +404,7 @@ void testArrays(int nullArrayIdx, int nonNullArrayIdx, List filledArray, private void testArrays(int nullArrayIdx, int nonNullArrayIdx, List nullArrayElements, List nonNullArrayElements, int sqlType, int pk) throws SQLException { - try (final var conn = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { conn.setSchema(database.getSchemaName()); try (final var s = conn.createStatement()) { try (final var rs = s.executeQuery("SELECT * from T where pk = " + pk)) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalConnectionRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalConnectionRule.java index f35d8d2055..6e8a05564a 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalConnectionRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalConnectionRule.java @@ -36,9 +36,9 @@ import javax.annotation.Nonnull; import java.net.URI; import java.sql.Array; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Struct; +import java.util.Objects; import java.util.function.Supplier; public class RelationalConnectionRule implements BeforeEachCallback, AfterEachCallback, RelationalConnection { @@ -54,8 +54,13 @@ public RelationalConnectionRule(SqlSupplier driverSupplier, Su this.dbPathSupplier = dbPathSupplier; } - public RelationalConnectionRule(Supplier dbPathSupplier) { - this(() -> (RelationalDriver) DriverManager.getDriver(dbPathSupplier.get().toString()), + public RelationalConnectionRule(@Nonnull RelationalExtension extension, Supplier dbPathSupplier) { + // Use the extension's driver directly rather than DriverManager. The previous pattern + // (DriverManager.getDriver) raced under parallel JUnit execution: another test class's + // afterEach could deregister the only driver between this rule's beforeEach and the + // getDriver() call, surfacing as `SQLException: No suitable driver`. + this(() -> Objects.requireNonNull(extension.getDriver(), + "RelationalExtension has no active driver — its @BeforeEach must run before this rule's @BeforeEach."), dbPathSupplier); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java index 0945ccbad6..6a96619bac 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/RelationalExtension.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.relational.recordlayer; import com.apple.foundationdb.relational.api.EmbeddedRelationalEngine; +import com.apple.foundationdb.relational.api.RelationalDriver; import org.junit.jupiter.api.extension.Extension; import javax.annotation.Nullable; @@ -30,4 +31,13 @@ public interface RelationalExtension extends Extension { @Nullable EmbeddedRelationalEngine getEngine(); + /** + * Get the driver owned by this extension. + * + * @return the driver owned by this extension. May be {@code null} if the extension's + * {@code beforeEach} has not run yet, or has been cleaned up. + */ + @Nullable + RelationalDriver getDriver(); + } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ResultSetMetaDataTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ResultSetMetaDataTest.java index 2a86090bd8..3e89538477 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ResultSetMetaDataTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ResultSetMetaDataTest.java @@ -50,13 +50,13 @@ public abstract class ResultSetMetaDataTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, ResultSetMetaDataTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java index 089acf0dca..1e8bf1c33f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SimpleDirectAccessInsertionTests.java @@ -38,10 +38,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.Collections; import java.util.List; +import java.net.URI; /** * Simple unit tests around direct-access insertion tests. @@ -53,12 +53,12 @@ public class SimpleDirectAccessInsertionTests { @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(SimpleDirectAccessInsertionTests.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, SimpleDirectAccessInsertionTests.class, TestSchemas.restaurant()); @Test void useScanContinuationInQueryShouldNotWork() throws Exception { insertRows(); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); // scan @@ -91,7 +91,7 @@ void useScanContinuationInQueryShouldNotWork() throws Exception { @Test void useQueryContinuationInDirectAccess() throws Exception { insertRows(); - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -119,7 +119,7 @@ void useQueryContinuationInDirectAccess() throws Exception { @Test void insertNestedFields() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -149,7 +149,7 @@ void insertNestedFields() throws SQLException { @Test void insertWithExplicitNullFields() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -179,7 +179,7 @@ void insertMultipleTablesDontMix() throws SQLException { * tables are logically separated. */ - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -262,7 +262,7 @@ void insertMultipleTablesDontMix() throws SQLException { } private void insertRows() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java index 2334fed6c4..a316fb650e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SqlFunctionsTest.java @@ -29,9 +29,17 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class SqlFunctionsTest { @Nonnull private static final String SCHEMA_TEMPLATE = @@ -53,12 +61,12 @@ create function f4 (in x bigint, in y string) @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(CaseSensitiveDbObjectsTest.class, + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, CaseSensitiveDbObjectsTest.class, SCHEMA_TEMPLATE, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/StructDataMetadataTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/StructDataMetadataTest.java index 898e0313d0..c8152a4843 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/StructDataMetadataTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/StructDataMetadataTest.java @@ -76,11 +76,11 @@ CREATE TABLE t4(id bigint, n1 n1, n2 n2, PRIMARY KEY(id)) */ @RegisterExtension @Order(0) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(StructDataMetadataTest.class, TABLE_STRUCTURE); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, StructDataMetadataTest.class, TABLE_STRUCTURE); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java index 6b217bc715..1b269755aa 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/SystemCatalogQueryTest.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.google.common.collect.ImmutableList; @@ -36,7 +37,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; @@ -71,12 +71,7 @@ public void teardown() throws Exception { } private static void runDdl(@Nonnull final String ddl) throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); - try (final var statement = conn.createStatement()) { - statement.executeUpdate(ddl); - } - } + CatalogOperations.runDdl(relationalExtension.getDriver(), ddl); } private static void createDb(@Nonnull final String dbName) throws Exception { @@ -95,8 +90,8 @@ private static void dropDb(@Nonnull final String dbName) throws Exception { @Test @SuppressWarnings("checkstyle:Indentation") public void selectSchemasWorks() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); //we are selective here to make it easier to check the correctness of the row (otherwise we'd have to put //MetaData objects in for equality) try (RelationalStatement statement = conn.createStatement(); @@ -111,26 +106,26 @@ public void selectSchemasWorks() throws SQLException { new Object[]{"CATALOG", "/__SYS"} )); } - } + }); + } @Test public void selectSchemasWithPredicateAndProjectionWorks() throws SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT schema_name FROM \"SCHEMAS\" WHERE database_id = '/__SYS'")) { shouldBe(rs, Set.of( List.of("CATALOG") )); } - } + }); + } @Disabled // TODO (SystemCatalogQueryTest has fragile tests) @Test public void selectDatabaseInfoWorks() throws SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT * FROM \"DATABASES\"")) { shouldBe(rs, Set.of( List.of("/TEST/DB1"), @@ -139,14 +134,14 @@ public void selectDatabaseInfoWorks() throws SQLException { List.of("/__SYS") )); } - } + }); + } @Disabled // TODO (SystemCatalogQueryTest has fragile tests) @Test public void selectDatabaseInfoWithPredicateAndProjectionWorks() throws RelationalException, SQLException { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relationalExtension.getDriver(), conn -> { try (final var statement = conn.createStatement(); final var rs = statement.executeQuery("SELECT database_id FROM \"DATABASES\" WHERE database_id != '/__SYS'")) { shouldBe(rs, Set.of( List.of("/TEST/DB1"), @@ -154,7 +149,8 @@ public void selectDatabaseInfoWithPredicateAndProjectionWorks() throws Relationa List.of("/TEST/DB3") )); } - } + }); + } private static void shouldBe(@Nonnull final ResultSet resultSet, @Nonnull final Set> expected) throws SQLException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableMetadataVersionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableMetadataVersionTest.java index 28de364e4b..c20cea4948 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableMetadataVersionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableMetadataVersionTest.java @@ -43,11 +43,11 @@ public class TableMetadataVersionTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(TableMetadataVersionTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, TableMetadataVersionTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule dbConn = new RelationalConnectionRule(database::getConnectionUri); + public final RelationalConnectionRule dbConn = new RelationalConnectionRule(relationalExtension, database::getConnectionUri); @Test void missingMetadataVersionFailsToLoadDuringGet() throws Exception { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java index 26478888b2..27a33a9bfd 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableTest.java @@ -38,7 +38,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -55,11 +54,11 @@ public class TableTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(TableTest.class, TestSchemas.restaurantWithCoveringIndex()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, TableTest.class, TestSchemas.restaurantWithCoveringIndex()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); @@ -93,7 +92,7 @@ void wrongSizeOfPrimaryKeyInGet() { @Test void wrongSizeOfPrimaryKeyInGetLongerKey() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate("CREATE TABLE FOO(A bigint, B bigint, C bigint, PRIMARY KEY(C, A))").build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate("CREATE TABLE FOO(A bigint, B bigint, C bigint, PRIMARY KEY(C, A))").build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { RelationalAssertions.assertThrowsSqlException( () -> statement.executeGet("FOO", new KeySet().setKeyColumn("C", 5), Options.NONE)) @@ -324,7 +323,7 @@ void testIndexCreatedUsingLastColumn() throws Exception { final String schema = " CREATE TABLE tbl1 (id bigint, a string, b string, c string, PRIMARY KEY(id))" + " CREATE INDEX c_name_idx as select c from tbl1"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { var result = EmbeddedRelationalStruct.newBuilder().addLong("ID", 42L).addString("A", "valuea1").addString("B", "valueb1").addString("C", "valuec1").build(); @@ -357,7 +356,7 @@ void testIndexCreatedUsingMultipleColumns() throws Exception { final String schema = " CREATE TABLE tbl1 (id bigint, a string, b string, c string, d string, PRIMARY KEY(id))" + " CREATE INDEX c_name_idx as select c, d from tbl1 order by c, d"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { var result = EmbeddedRelationalStruct.newBuilder().addLong("ID", 42L).addString("A", "valuea1").addString("B", "valueb1").addString("C", "valuec1").addString("D", "valued1").build(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java index 87eb4c0ced..a05654a828 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithEnumTest.java @@ -54,11 +54,11 @@ public class TableWithEnumTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(TableWithEnumTest.class, TestSchemas.playingCard()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, TableWithEnumTest.class, TestSchemas.playingCard()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java index 1becdabc95..453c03be4b 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TableWithNoPkTest.java @@ -37,10 +37,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; +import java.net.URI; /** * A table with no primary key (but with a record-type key can contain only one row. @@ -52,12 +52,12 @@ public class TableWithNoPkTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(TableWithNoPkTest.class, + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, TableWithNoPkTest.class, "CREATE TABLE no_pk(a bigint, b bigint, SINGLE ROW ONLY)"); @Test void simpleTest() throws RelationalException, SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -81,7 +81,7 @@ void simpleTest() throws RelationalException, SQLException { @Test void twoInserts() throws RelationalException, SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -114,7 +114,7 @@ void twoInserts() throws RelationalException, SQLException { @Test void testDelete() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -138,7 +138,7 @@ void testDelete() throws Exception { @Test void testScan() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -160,7 +160,7 @@ void testScan() throws Exception { @Test void testQuery() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed://" + db.getDatabasePath().getPath()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed://" + db.getDatabasePath().getPath())).unwrap(RelationalConnection.class)) { conn.setSchema(db.getSchemaName()); try (RelationalStatement s = conn.createStatement()) { @@ -190,7 +190,7 @@ void testQuery() throws Exception { @Test void testCreateTableWithNoSingleRowOnlyClause() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create("jdbc:embed:/__SYS")).unwrap(RelationalConnection.class)) { conn.setSchema("CATALOG"); try (Statement statement = conn.createStatement()) { //create a schema diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseTest.java index b82c51d28b..a992ae11f5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseTest.java @@ -90,11 +90,11 @@ public class TransactionBoundDatabaseTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule dbRule = new SimpleDatabaseRule(TransactionBoundDatabaseTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule dbRule = new SimpleDatabaseRule(relational, TransactionBoundDatabaseTest.class, TestSchemas.restaurant()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connRule = new RelationalConnectionRule(dbRule::getConnectionUri) + public final RelationalConnectionRule connRule = new RelationalConnectionRule(relational, dbRule::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java index 058b81325f..b130b713ef 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionBoundDatabaseWithEnumTest.java @@ -64,11 +64,11 @@ public class TransactionBoundDatabaseWithEnumTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule dbRule = new SimpleDatabaseRule(TransactionBoundDatabaseWithEnumTest.class, TestSchemas.playingCard()); + public final SimpleDatabaseRule dbRule = new SimpleDatabaseRule(relational, TransactionBoundDatabaseWithEnumTest.class, TestSchemas.playingCard()); @RegisterExtension @Order(2) - public final RelationalConnectionRule connRule = new RelationalConnectionRule(dbRule::getConnectionUri) + public final RelationalConnectionRule connRule = new RelationalConnectionRule(relational, dbRule::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java index 07dfe99dcf..6f7c35c0a6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/TransactionConfigTest.java @@ -34,8 +34,8 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; +import java.net.URI; public class TransactionConfigTest { @RegisterExtension @@ -44,11 +44,11 @@ public class TransactionConfigTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(TransactionConfigTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relational, TransactionConfigTest.class, TestSchemas.restaurant()); @Disabled // TODO (Bug: sporadic failure in `testRecordInsertionWithTimeOutInConfig`) void testRecordInsertionWithTimeOutInConfig() throws RelationalException, SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relational.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); conn.setOption(Options.Name.TRANSACTION_TIMEOUT, 1L); try (RelationalStatement s = conn.createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/UniqueIndexTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/UniqueIndexTests.java index 843c57248e..bed95ae023 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/UniqueIndexTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/UniqueIndexTests.java @@ -64,11 +64,11 @@ CREATE TABLE T5(t5_p bigint, t5_a bigint, t5_b bigint, t5_c bigint, t5_d bigint, @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(UniqueIndexTests.class, getTemplate_definition); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, UniqueIndexTests.class, getTemplate_definition); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(db::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, db::getConnectionUri) .withOptions(Options.NONE) .withSchema(db.getSchemaName()); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java index ee289d6e1c..15316d95be 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/VectorDirectAccessTest.java @@ -40,10 +40,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.net.URI; public class VectorDirectAccessTest { @RegisterExtension @@ -52,7 +52,7 @@ public class VectorDirectAccessTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(VectorDirectAccessTest.class, + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, VectorDirectAccessTest.class, "CREATE TABLE V(PK INTEGER, V1 VECTOR(4, FLOAT), V2 VECTOR(3, HALF), V3 VECTOR(2, DOUBLE), PRIMARY KEY(PK))\n" + "CREATE TABLE VHNSW(PK INTEGER, ZONE STRING, VEC VECTOR(3, FLOAT), PRIMARY KEY(PK))\n" + "CREATE VIEW VHNSW_VIEW AS SELECT VEC, ZONE, PK FROM VHNSW\n" + @@ -60,7 +60,7 @@ public class VectorDirectAccessTest { @Test void insertNulls() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).build(); @@ -80,7 +80,7 @@ void insertNulls() throws SQLException { @Test void partialInsert() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).addObject("V1", new FloatRealVector(new float[]{1f, 2f, 3f, 4f})).build(); @@ -100,7 +100,7 @@ void partialInsert() throws SQLException { @Test void fullInsert() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0) @@ -124,7 +124,7 @@ void fullInsert() throws SQLException { @Test void insertWrongDimensionFails() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).addObject("V1", new FloatRealVector(new float[] {1f, 2f, 3f, 4f, 5f})).build(); @@ -137,7 +137,7 @@ void insertWrongDimensionFails() throws SQLException { @Test void insertWrongPrecisionFails() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).addObject("V1", new DoubleRealVector(new double[] {1d, 2d, 3d, 4d})).build(); @@ -150,7 +150,7 @@ void insertWrongPrecisionFails() throws SQLException { @Test void insertWrongTypeFails() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 0).addInt("V1", 42).build(); @@ -163,7 +163,7 @@ void insertWrongTypeFails() throws SQLException { @Test void deleteVectorRecord() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 10) @@ -185,7 +185,7 @@ void deleteVectorRecord() throws SQLException { @Test void deleteNonExistentVectorRecord() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { int deleted = s.executeDelete("V", List.of(new KeySet().setKeyColumn("PK", 999))); @@ -196,7 +196,7 @@ void deleteNonExistentVectorRecord() throws SQLException { @Test void deleteOneOfMultipleVectorRecords() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec1 = EmbeddedRelationalStruct.newBuilder().addInt("PK", 20) @@ -226,7 +226,7 @@ void deleteOneOfMultipleVectorRecords() throws SQLException { @Test void deleteMultipleVectorRecords() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec1 = EmbeddedRelationalStruct.newBuilder().addInt("PK", 30) @@ -264,7 +264,7 @@ void deleteMultipleVectorRecords() throws SQLException { @Test void deleteVectorRecordWithNullVectors() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { RelationalStruct rec = EmbeddedRelationalStruct.newBuilder().addInt("PK", 40).build(); @@ -282,7 +282,7 @@ void deleteVectorRecordWithNullVectors() throws SQLException { @Test void deleteVectorMaintainsHnswIndex() throws SQLException { - try (RelationalConnection conn = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (RelationalConnection conn = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { conn.setSchema("TEST_SCHEMA"); try (RelationalStatement s = conn.createStatement()) { // Insert 3 vectors into VHNSW, all in zone "z1" diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/CatalogMetaDataProviderTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/CatalogMetaDataProviderTest.java index 8d37d56cc4..8797c6856d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/CatalogMetaDataProviderTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/CatalogMetaDataProviderTest.java @@ -36,6 +36,7 @@ import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerColumn; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerTable; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.DescriptorAssert; import com.google.protobuf.Descriptors; @@ -55,38 +56,52 @@ void canLoadMetaDataFromStore() throws RelationalException, Descriptors.Descript //now create a RecordStore in that Catalog FDBDatabaseFactory factory = FDBDatabaseFactory.instance(); FdbConnection fdbConn = new DirectFdbConnection(factory.getDatabase(FDBTestEnvironment.randomClusterFile())); - StoreCatalog storeCatalog; - try (Transaction txn = fdbConn.getTransactionManager().createTransaction(Options.NONE)) { - //create the Catalog RecordStore - storeCatalog = StoreCatalogProvider.getCatalog(txn, RelationalKeyspaceProvider.instance().getKeySpace()); - txn.commit(); - } + // Use a holder so we can populate from inside the runLockedWithRelationalRetry lambda. + final StoreCatalog[] storeCatalogHolder = new StoreCatalog[1]; + // Catalog bootstrap: writes the SYS catalog metadata. Serialise + retry against + // concurrent extension setups (which also bootstrap their own StoreCatalog instances). + CatalogOperations.runLockedWithRelationalRetry(() -> { + try (Transaction txn = fdbConn.getTransactionManager().createTransaction(Options.NONE)) { + //create the Catalog RecordStore + storeCatalogHolder[0] = StoreCatalogProvider.getCatalog(txn, RelationalKeyspaceProvider.instance().getKeySpace()); + txn.commit(); + } + }); + final StoreCatalog storeCatalog = storeCatalogHolder[0]; URI dbUri = URI.create("/testdb"); String schemaName = "TEST_SCHEMA" + System.currentTimeMillis(); RecordLayerSchemaTemplate schemaTemplate = createSchemaTemplate(); - try (Transaction txn = fdbConn.getTransactionManager().createTransaction(Options.NONE)) { - //write template into template catalog - storeCatalog.getSchemaTemplateCatalog().createTemplate(txn, schemaTemplate); - //write schema info to the store - Schema schema = schemaTemplate.generateSchema(dbUri.getPath(), schemaName); - storeCatalog.createDatabase(txn, dbUri); - storeCatalog.saveSchema(txn, schema, false); + // Catalog writes (schema template / database / schema rows) — also under the lock. + CatalogOperations.runLockedWithRelationalRetry(() -> { + try (Transaction txn = fdbConn.getTransactionManager().createTransaction(Options.NONE)) { + //write template into template catalog + storeCatalog.getSchemaTemplateCatalog().createTemplate(txn, schemaTemplate); + //write schema info to the store + Schema schema = schemaTemplate.generateSchema(dbUri.getPath(), schemaName); + storeCatalog.createDatabase(txn, dbUri); + storeCatalog.saveSchema(txn, schema, false); - CatalogMetaDataProvider metaDataProvider = new CatalogMetaDataProvider(storeCatalog, dbUri, schemaName, txn); - final RecordMetaData recordMetaData = metaDataProvider.getRecordMetaData(); - final Descriptors.FileDescriptor descriptor = recordMetaData.getRecordsDescriptor(); + CatalogMetaDataProvider metaDataProvider = new CatalogMetaDataProvider(storeCatalog, dbUri, schemaName, txn); + final RecordMetaData recordMetaData = metaDataProvider.getRecordMetaData(); + final Descriptors.FileDescriptor descriptor = recordMetaData.getRecordsDescriptor(); - Descriptors.FileDescriptor expected = Descriptors.FileDescriptor.buildFrom( - schemaTemplate.toRecordMetadata().getRecordsDescriptor().toProto(), // not sure this is correct - new Descriptors.FileDescriptor[]{RecordMetaDataProto.getDescriptor()}); + final Descriptors.FileDescriptor expected; + try { + expected = Descriptors.FileDescriptor.buildFrom( + schemaTemplate.toRecordMetadata().getRecordsDescriptor().toProto(), + new Descriptors.FileDescriptor[]{RecordMetaDataProto.getDescriptor()}); + } catch (Descriptors.DescriptorValidationException dve) { + throw new RelationalException("buildFrom failed", null, dve); + } - for (Descriptors.Descriptor message : descriptor.getMessageTypes()) { - new DescriptorAssert(message).as("Incorrect descriptor for type %s", message.getName()) - .isContainedIn(expected.getMessageTypes()); + for (Descriptors.Descriptor message : descriptor.getMessageTypes()) { + new DescriptorAssert(message).as("Incorrect descriptor for type %s", message.getName()) + .isContainedIn(expected.getMessageTypes()); + } } - } + }); } private RecordLayerSchemaTemplate createSchemaTemplate() throws RelationalException { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java index 1adbea789b..d63c388381 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogImplTest.java @@ -20,12 +20,8 @@ package com.apple.foundationdb.relational.recordlayer.catalog; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore; -import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; -import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.foundationdb.relational.api.Transaction; +import com.apple.foundationdb.relational.api.catalog.StoreCatalog; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.metadata.Metadata; @@ -33,13 +29,11 @@ import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.api.metadata.View; import com.apple.foundationdb.relational.recordlayer.RecordContextTransaction; -import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import javax.annotation.Nonnull; import java.net.URI; import java.util.stream.Collectors; @@ -47,24 +41,9 @@ public class RecordLayerStoreCatalogImplTest extends RecordLayerStoreCatalogTestBase { - @BeforeEach - void setUpCatalog() throws RelationalException { - fdb = FDBDatabaseFactory.instance().getDatabase(FDBTestEnvironment.randomClusterFile()); - // create a FDBRecordStore - try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); - } - } - - @AfterEach - void deleteAllRecords() throws RelationalException { - try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { - - final KeySpacePath keySpacePath = RelationalKeyspaceProvider.toDatabasePath(URI.create("/__SYS"), keySpace).schemaPath("CATALOG"); - FDBRecordStore.deleteStore(txn.unwrap(FDBRecordContext.class), keySpacePath); - txn.commit(); - } + @Override + protected StoreCatalog createCatalog(@Nonnull Transaction txn) throws RelationalException { + return StoreCatalogProvider.getCatalog(txn, keySpace); } @Test diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogTestBase.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogTestBase.java index 5a883d24ae..7dd4bb57b6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogTestBase.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogTestBase.java @@ -21,7 +21,11 @@ package com.apple.foundationdb.relational.recordlayer.catalog; import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; +import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore; import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpace; +import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.Transaction; @@ -38,7 +42,12 @@ import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerSchemaTemplate; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerTable; import com.apple.foundationdb.relational.recordlayer.metadata.RecordLayerView; +import com.apple.foundationdb.test.FDBTestEnvironment; +import com.apple.test.Tags; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import javax.annotation.Nonnull; @@ -48,6 +57,12 @@ import java.util.HashSet; import java.util.Set; +// Tagged @WipesFDB because @BeforeEach/@AfterEach call FDBRecordStore.deleteStore on the SYS +// catalog (/__SYS/CATALOG) — the very record store every other test in relational depends on. +// The destructiveTest Gradle task runs WipesFDB-tagged tests with maxParallelForks = 1, so no +// sibling JVM (or in-JVM test) can observe the catalog while it's being wiped and rebuilt. +// The tag is inherited by every concrete subclass, so subclasses do not need to redeclare it. +@Tag(Tags.WipesFDB) public abstract class RecordLayerStoreCatalogTestBase { FDBDatabase fdb; @@ -61,6 +76,42 @@ public abstract class RecordLayerStoreCatalogTestBase { keySpace = keyspaceProvider.getKeySpace(); } + @BeforeEach + void setUpCatalog() throws RelationalException { + fdb = FDBDatabaseFactory.instance().getDatabase(FDBTestEnvironment.randomClusterFile()); + // Defensively wipe any state left over from a previous run (e.g. a JVM that crashed + // between @BeforeEach and @AfterEach) so each test starts against an empty catalog. + deleteCatalog(); + try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { + storeCatalog = createCatalog(txn); + txn.commit(); + } + } + + @AfterEach + void deleteAllRecords() throws RelationalException { + deleteCatalog(); + } + + /** + * Build the {@link StoreCatalog} variant this test class exercises. Called from + * {@link #setUpCatalog()} inside a fresh transaction; implementations should not commit — + * the caller commits after this returns. + * + * @param txn the transaction to build the catalog under + * @return the freshly-created catalog + * @throws RelationalException if the catalog cannot be created + */ + protected abstract StoreCatalog createCatalog(@Nonnull Transaction txn) throws RelationalException; + + private void deleteCatalog() throws RelationalException { + try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { + final KeySpacePath keySpacePath = RelationalKeyspaceProvider.toDatabasePath(URI.create("/__SYS"), keySpace).schemaPath("CATALOG"); + FDBRecordStore.deleteStore(txn.unwrap(FDBRecordContext.class), keySpacePath); + txn.commit(); + } + } + @Test void testListSchemasEmptyResult() throws RelationalException, SQLException { // list all schemas diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java index a36f6bbc87..7243eff89f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/catalog/RecordLayerStoreCatalogWithNoTemplateOperationsTest.java @@ -20,45 +20,25 @@ package com.apple.foundationdb.relational.recordlayer.catalog; -import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; -import com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore; -import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpacePath; -import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.foundationdb.relational.api.Transaction; +import com.apple.foundationdb.relational.api.catalog.StoreCatalog; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.metadata.Schema; import com.apple.foundationdb.relational.api.metadata.SchemaTemplate; import com.apple.foundationdb.relational.recordlayer.RecordContextTransaction; -import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import javax.annotation.Nonnull; import java.net.URI; public class RecordLayerStoreCatalogWithNoTemplateOperationsTest extends RecordLayerStoreCatalogTestBase { - @BeforeEach - void setUpCatalog() throws RelationalException { - fdb = FDBDatabaseFactory.instance().getDatabase(FDBTestEnvironment.randomClusterFile()); - // create a FDBRecordStore - try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { - storeCatalog = StoreCatalogProvider.getCatalogWithNoTemplateOperations(txn); - txn.commit(); - } - } - - @AfterEach - void deleteAllRecords() throws RelationalException { - try (Transaction txn = new RecordContextTransaction(fdb.openContext())) { - final KeySpacePath keySpacePath = RelationalKeyspaceProvider.instance().toDatabasePath(URI.create("/__SYS")).schemaPath("CATALOG"); - FDBRecordStore.deleteStore(txn.unwrap(FDBRecordContext.class), keySpacePath); - txn.commit(); - } + @Override + protected StoreCatalog createCatalog(@Nonnull Transaction txn) throws RelationalException { + return StoreCatalogProvider.getCatalogWithNoTemplateOperations(txn); } @Test diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java index 31a87787b7..77eb8d2cb5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlDatabaseTest.java @@ -26,6 +26,7 @@ import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SchemaTemplateRule; import com.apple.foundationdb.relational.utils.TableDefinition; @@ -37,12 +38,15 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.net.URI; /** * End-to-end unit tests for the database ddl language built over RecordLayer. @@ -52,39 +56,52 @@ public class DdlDatabaseTest { public static final EmbeddedRelationalExtension relational = new EmbeddedRelationalExtension(); @RegisterExtension - public final SchemaTemplateRule baseTemplate = new SchemaTemplateRule(DdlDatabaseTest.class.getSimpleName() + "_TEMPLATE", + public final SchemaTemplateRule baseTemplate = new SchemaTemplateRule(relational, DdlDatabaseTest.class.getSimpleName() + "_TEMPLATE", Options.none(), null, Collections.singleton(new TableDefinition("FOO_TBL", List.of("string", "double"), List.of("col1"))), Collections.singleton(new TypeDefinition("FOO_NESTED_TYPE", List.of("string", "bigint")))); @Test public void canCreateDatabase() throws Exception { + // Use a unique-per-run database path so the SHOW DATABASES assertion below can't be + // satisfied by a sibling test's leftover database with the same name. The path is + // lower-cased here because the engine normalises identifiers to upper-case, and we want + // to verify the normalised form in the assertion (see jdbcConnectPath / showName below). + final String uniqueSuffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + final String dbPathLower = "/test/test_db_" + uniqueSuffix; + final String dbPathUpper = "/TEST/TEST_DB_" + uniqueSuffix.toUpperCase(Locale.ROOT); try { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a database - statement.executeUpdate("CREATE DATABASE /test/test_db"); - statement.executeUpdate("CREATE SCHEMA /test/test_db/foo_schem with template \"" + baseTemplate.getSchemaTemplateName() + "\""); + statement.executeUpdate("CREATE DATABASE " + dbPathLower); + statement.executeUpdate("CREATE SCHEMA " + dbPathLower + "/foo_schem with template \"" + baseTemplate.getSchemaTemplateName() + "\""); } - } - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/TEST/TEST_DB").unwrap(RelationalConnection.class)) { + }); + + try (RelationalConnection conn = relational.getDriver().connect(URI.create("jdbc:embed:" + dbPathUpper)).unwrap(RelationalConnection.class)) { conn.setSchema("FOO_SCHEM"); try (RelationalStatement statement = conn.createStatement()) { - //look to see if it's in the list - Set databases = Set.of("/TEST/TEST_DB", "/__SYS"); + // Assert that our database appears in SHOW DATABASES. We can't use a + // meetsForAllRows-style "every row must be in {our DB, __SYS}" check because + // under parallel execution the catalog also holds databases from + // concurrently-running test classes. Just verify ours is in the list. try (RelationalResultSet rs = statement.executeQuery("SHOW DATABASES")) { - ResultSetAssert.assertThat(rs) - .meetsForAllRows(ResultSetAssert.perRowCondition(resultSet -> databases.contains(resultSet.getString(1)), "Should be a valid database")); + Set seen = new HashSet<>(); + while (rs.next()) { + seen.add(rs.getString(1)); + } + Assertions.assertThat(seen).contains(dbPathUpper); } } } } finally { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS?schema=CATALOG")) { + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { - statement.execute("DROP DATABASE /test/test_db"); + statement.execute("DROP DATABASE " + dbPathLower); } - } + }); + } } @@ -92,8 +109,8 @@ public void canCreateDatabase() throws Exception { @Disabled("TODO") public void dropDatabaseRemovesFromList() throws Exception { final String listCommand = "SHOW DATABASES WITH PREFIX /test_db"; - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try (RelationalStatement statement = conn.createStatement()) { //create a database statement.executeUpdate("CREATE DATABASE /test_db"); @@ -111,7 +128,8 @@ public void dropDatabaseRemovesFromList() throws Exception { ResultSetAssert.assertThat(rs).isEmpty(); } } - } + }); + } @Test @@ -122,8 +140,7 @@ public void cannotCreateSchemaFromDroppedDatabase() throws Exception { * * This is a drop database test that doesn't require listing the database */ - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a database statement.executeUpdate("CREATE DATABASE /test/test_db"); @@ -140,13 +157,14 @@ public void cannotCreateSchemaFromDroppedDatabase() throws Exception { .extracting("SQLState") .isEqualTo(ErrorCode.UNDEFINED_DATABASE.getErrorCode()); } - } + }); + } @Test public void cannotCreateDatabaseIfExists() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); // assure that there is not a database with the same name from before try (Statement statement = conn.createStatement()) { statement.executeUpdate("DROP DATABASE if exists /test/test_db"); @@ -163,19 +181,21 @@ public void cannotCreateDatabaseIfExists() throws Exception { statement.executeUpdate("DROP DATABASE /test/test_db"); } } - } + }); + } @Test public void cannotCreateSchemaWithoutDatabase() throws Exception { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); try (Statement statement = conn.createStatement()) { //create a database RelationalAssertions.assertThrowsSqlException(() -> statement.executeUpdate("CREATE SCHEMA /database_that_does_not_exist/schema_that_cannot_be_created with template " + baseTemplate.getSchemaTemplateName())) .hasErrorCode(ErrorCode.UNDEFINED_DATABASE); } - } + }); + } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java index 78f3263ef0..4618eed9d8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTemplateTest.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; import com.apple.foundationdb.relational.util.Assert; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.DdlPermutationGenerator; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.RelationalAssertions; @@ -41,7 +42,6 @@ import org.junit.jupiter.params.provider.MethodSource; import java.sql.Array; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashSet; @@ -60,27 +60,45 @@ public class DdlRecordLayerSchemaTemplateTest { @RegisterExtension public static final EmbeddedRelationalExtension relational = new EmbeddedRelationalExtension(); + // Suffix added to every schema-template name this class creates. The catalog is + // physically shared across every test run against this FDB cluster, and the tests + // in this class historically never dropped the templates they created — so a second + // run against the same cluster would fail with "Schema template already exists". + // Randomising the suffix per JVM makes each test's template name unique, so a + // previous run's leftovers don't collide with this one, and two concurrent JVMs + // running this class don't collide either. The suffix is uppercased because SQL + // uppercases unquoted identifiers, and describeSchemaTemplate checks the returned + // template name for exact equality. + private static final String NAME_SUFFIX = "_" + Long.toHexString(ThreadLocalRandom.current().nextLong()).toUpperCase(); + public static Stream columnTypePermutations() { - return DdlPermutationGenerator.generateTables("SCHEMA_TEMPLATE_TEST", 2) + return DdlPermutationGenerator.generateTables("SCHEMA_TEMPLATE_TEST" + NAME_SUFFIX + "_", 2) .map(Arguments::of); } private void run(ThrowingConsumer operation) throws RelationalException, SQLException { - try (RelationalConnection conn = DriverManager.getConnection("jdbc:embed:/__SYS").unwrap(RelationalConnection.class)) { - conn.setSchema("CATALOG"); - try (RelationalStatement statement = conn.createStatement()) { - operation.accept(statement); - } catch (RelationalException | SQLException | RuntimeException err) { - throw err; - } catch (Throwable throwable) { - Assertions.fail("unexpected error type", throwable); - } - } + // Serialise via the JVM-wide catalog lock — `operation` typically issues catalog DDL + // (CREATE/DROP SCHEMA TEMPLATE etc.) against /__SYS/CATALOG. The lock prevents racing + // commits with other test classes; the retry handles residual SQLSTATE 40001 conflicts. + CatalogOperations.runLockedWithRetry(() -> { + CatalogOperations.runOnCatalog(relational.getDriver(), connection -> { + RelationalConnection conn = connection.unwrap(RelationalConnection.class); + try (RelationalStatement statement = conn.createStatement()) { + operation.accept(statement); + } catch (SQLException | RuntimeException err) { + throw err; + } catch (Throwable throwable) { + Assertions.fail("unexpected error type", throwable); + } + }); + + }); } @Test void canDropSchemaTemplates() throws Exception { - String createColumnStatement = "CREATE SCHEMA TEMPLATE drop_template " + + final String templateName = "drop_template" + NAME_SUFFIX; + String createColumnStatement = "CREATE SCHEMA TEMPLATE " + templateName + " " + "CREATE TYPE AS STRUCT FOO_TYPE (a bigint)" + " CREATE TABLE FOO_TBL (b double, PRIMARY KEY(b))"; @@ -88,16 +106,16 @@ void canDropSchemaTemplates() throws Exception { statement.executeUpdate(createColumnStatement); //verify that it's there - try (final var rs = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE drop_template")) { + try (final var rs = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE " + templateName)) { Assertions.assertTrue(rs.next(), "Didn't find created template!"); - Assertions.assertEquals("DROP_TEMPLATE", rs.getString(1)); + Assertions.assertEquals(templateName.toUpperCase(), rs.getString(1)); Assertions.assertFalse(rs.next(), "too many schema templates!"); } //now drop it - statement.executeUpdate("DROP SCHEMA TEMPLATE drop_template"); + statement.executeUpdate("DROP SCHEMA TEMPLATE " + templateName); //verify it's not there, and that means that there is an error trying to describe it - SQLException ve = Assertions.assertThrows(SQLException.class, () -> statement.executeQuery("DESCRIBE SCHEMA TEMPLATE drop_template")); + SQLException ve = Assertions.assertThrows(SQLException.class, () -> statement.executeQuery("DESCRIBE SCHEMA TEMPLATE " + templateName)); Assertions.assertEquals(ErrorCode.UNKNOWN_SCHEMA_TEMPLATE.getErrorCode(), ve.getSQLState(), "Incorrect error code"); }); } @@ -163,7 +181,7 @@ void listSchemaTemplatesWorks(DdlPermutationGenerator.NamedPermutation table) th @Test void createSchemaTemplateWithNoTable() throws SQLException, RelationalException { - String createColumnStatement = "CREATE SCHEMA TEMPLATE no_table " + + String createColumnStatement = "CREATE SCHEMA TEMPLATE no_table" + NAME_SUFFIX + " " + "CREATE TYPE AS STRUCT not_a_table(a bigint)"; run(statement -> RelationalAssertions.assertThrowsSqlException(() -> statement.executeUpdate(createColumnStatement)) @@ -172,7 +190,7 @@ void createSchemaTemplateWithNoTable() throws SQLException, RelationalException @Test void cyclicDependencyTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE cyclic " + + String template = "CREATE SCHEMA TEMPLATE cyclic" + NAME_SUFFIX + " " + "CREATE TYPE AS STRUCT s1 (a s2) " + "CREATE TYPE AS STRUCT s2 (a s1) " + "CREATE TABLE t1 (id bigint, a s1, b s2, PRIMARY KEY(id))"; @@ -184,7 +202,8 @@ void cyclicDependencyTest() throws RelationalException, SQLException { @Test void manyStructsThatDoNotDependOnEachOther() throws RelationalException, SQLException { - StringBuilder template = new StringBuilder("CREATE SCHEMA TEMPLATE many_structs "); + final String templateName = "many_structs" + NAME_SUFFIX; + StringBuilder template = new StringBuilder("CREATE SCHEMA TEMPLATE ").append(templateName).append(' '); for (int i = 0; i < 100; i++) { template.append("CREATE TYPE AS STRUCT s").append(i).append("(a bigint) "); } @@ -196,7 +215,7 @@ void manyStructsThatDoNotDependOnEachOther() throws RelationalException, SQLExce run(statement -> { statement.executeUpdate(template.toString()); - try (RelationalResultSet resultSet = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE many_structs")) { + try (RelationalResultSet resultSet = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE " + templateName)) { ResultSetAssert.assertThat(resultSet).hasNextRow(); final var type = resultSet.getArray("TABLES").getResultSet(); Assert.that(type.next()); @@ -207,7 +226,7 @@ void manyStructsThatDoNotDependOnEachOther() throws RelationalException, SQLExce @Test void missingTypeTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE missing_type " + + String template = "CREATE SCHEMA TEMPLATE missing_type" + NAME_SUFFIX + " " + "CREATE TABLE t1 (id bigint, val unknown_type, PRIMARY KEY(id))"; run(statement -> RelationalAssertions.assertThrowsSqlException(() -> statement.executeUpdate(template)) @@ -217,7 +236,8 @@ void missingTypeTest() throws RelationalException, SQLException { @Test void basicEnumTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE basic_enum_template " + + final String templateName = "basic_enum_template" + NAME_SUFFIX; + String template = "CREATE SCHEMA TEMPLATE " + templateName + " " + "CREATE TYPE AS ENUM basic_enum ('FOO', 'BAR', 'BAZ') " + "CREATE TABLE t1 (id bigint, val basic_enum, PRIMARY KEY(id))"; @@ -225,7 +245,7 @@ void basicEnumTest() throws RelationalException, SQLException { statement.executeUpdate(template); //verify that it's there - try (ResultSet rs = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE basic_enum_template")) { + try (ResultSet rs = statement.executeQuery("DESCRIBE SCHEMA TEMPLATE " + templateName)) { Assertions.assertTrue(rs.next(), "Didn't find created template!"); } }); @@ -233,7 +253,7 @@ void basicEnumTest() throws RelationalException, SQLException { @Test void twoTypesSameNameTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE same_name " + + String template = "CREATE SCHEMA TEMPLATE same_name" + NAME_SUFFIX + " " + "CREATE TABLE t1 (id bigint, foo string, PRIMARY KEY(id)) " + "CREATE TABLE t1 (id bigint, bar string, PRIMARY KEY(id))"; @@ -244,7 +264,7 @@ void twoTypesSameNameTest() throws RelationalException, SQLException { @Test void twoTypesSameNameMixedCase() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE same_name_mixed_case " + + String template = "CREATE SCHEMA TEMPLATE same_name_mixed_case" + NAME_SUFFIX + " " + "CREATE TABLE aTypeName (id bigint, foo string, PRIMARY KEY(id)) " + "CREATE TABLE AtYPEnAME (id bigint, bar string, PRIMARY KEY(id))"; @@ -255,7 +275,7 @@ void twoTypesSameNameMixedCase() throws RelationalException, SQLException { @Test void typeAndEnumSameNameTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE same_name " + + String template = "CREATE SCHEMA TEMPLATE type_and_enum_same_name" + NAME_SUFFIX + " " + "CREATE TABLE foo (id bigint, foo string, PRIMARY KEY(id)) " + "CREATE TYPE AS ENUM foo ('A', 'B', 'C')"; @@ -266,7 +286,7 @@ void typeAndEnumSameNameTest() throws RelationalException, SQLException { @Test void notNullNonArrayTypeNotAllowedTest() throws RelationalException, SQLException { - String template = "CREATE SCHEMA TEMPLATE not_null_non_array_column_type " + + String template = "CREATE SCHEMA TEMPLATE not_null_non_array_column_type" + NAME_SUFFIX + " " + "CREATE TABLE foo (id bigint, foo string not null, PRIMARY KEY(id))"; run(statement -> RelationalAssertions.assertThrowsSqlException(() -> statement.executeUpdate(template)) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java index 76111c6200..c6d701fbe3 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/ddl/DdlRecordLayerSchemaTest.java @@ -24,6 +24,7 @@ import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalExtension; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.DatabaseRule; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.SchemaTemplateRule; @@ -38,7 +39,6 @@ import java.net.URI; import java.sql.Array; -import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Collections; @@ -52,19 +52,18 @@ public class DdlRecordLayerSchemaTest { @RegisterExtension @Order(1) - public final SchemaTemplateRule baseTemplate = new SchemaTemplateRule( + public final SchemaTemplateRule baseTemplate = new SchemaTemplateRule(relational, DdlRecordLayerSchemaTest.class.getSimpleName().toUpperCase(Locale.ROOT) + "_TEMPLATE", Options.none(), null, Collections.singleton(new TableDefinition("FOO_TBL", List.of("string", "double"), List.of("col0"))), Collections.singleton(new TypeDefinition("FOO_NESTED_TYPE", List.of("string", "bigint")))); @RegisterExtension @Order(2) - public final DatabaseRule db = new DatabaseRule(URI.create("/TEST/" + DdlRecordLayerSchemaTest.class.getSimpleName().toUpperCase(Locale.ROOT)), Options.none()); + public final DatabaseRule db = new DatabaseRule(relational, URI.create("/TEST/" + DdlRecordLayerSchemaTest.class.getSimpleName().toUpperCase(Locale.ROOT)), Options.none()); @Test void canCreateSchema() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a schema final String createStatement = "CREATE SCHEMA " + db.getDbUri() + "/TEST_SCHEMA WITH TEMPLATE " + baseTemplate.getSchemaTemplateName(); @@ -84,26 +83,30 @@ void canCreateSchema() throws Exception { } } } - } + }); + } @Test void canCreateSchemaTemplateWhenConnectedToNonCatalogSchema() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (Statement statement = conn.createStatement()) { //create a schema final String createStatement = "CREATE SCHEMA " + db.getDbUri() + "/TEST_SCHEMA WITH TEMPLATE " + baseTemplate.getSchemaTemplateName(); statement.executeUpdate(createStatement); } - } - //now create a new schema in the same db but using a different connection - try (final var conn = DriverManager.getConnection("jdbc:embed:" + db.getDbUri())) { + }); + + //now create a new schema in the same db but using a different connection. + // Use a random-per-invocation template name so this test doesn't clash with a prior + // run's leftover FOO template in the shared catalog — the test never drops it. + final String templateName = "FOO_" + Long.toHexString(java.util.concurrent.ThreadLocalRandom.current().nextLong()).toUpperCase(); + try (final var conn = relational.getDriver().connect(URI.create("jdbc:embed:" + db.getDbUri()))) { conn.setSchema("TEST_SCHEMA"); try (Statement statement = conn.createStatement()) { //create a schema - final String createStatement = "CREATE SCHEMA TEMPLATE FOO CREATE TABLE T(A string, B string, PRIMARY KEY (A))"; + final String createStatement = "CREATE SCHEMA TEMPLATE " + templateName + " CREATE TABLE T(A string, B string, PRIMARY KEY (A))"; statement.executeUpdate(createStatement); } } @@ -111,8 +114,7 @@ void canCreateSchemaTemplateWhenConnectedToNonCatalogSchema() throws Exception { @Test void cannotCreateSchemaTwice() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (Statement statement = conn.createStatement()) { //create a schema @@ -122,13 +124,13 @@ void cannotCreateSchemaTwice() throws Exception { .hasErrorCode(ErrorCode.SCHEMA_ALREADY_EXISTS); } - } + }); + } @Test void dropSchema() throws Exception { - try (final var conn = DriverManager.getConnection("jdbc:embed:/__SYS")) { - conn.setSchema("CATALOG"); + CatalogOperations.runOnCatalog(relational.getDriver(), conn -> { try (final var statement = conn.createStatement()) { //create a schema @@ -159,6 +161,7 @@ void dropSchema() throws Exception { RelationalAssertions.assertThrowsSqlException(() -> statement.executeQuery("DESCRIBE SCHEMA " + db.getDbUri() + "/TEST_SCHEMA")) .hasErrorCode(ErrorCode.UNDEFINED_SCHEMA); } - } + }); + } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/MetricsCollectionTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/MetricsCollectionTest.java index 52f970689b..b6034380b1 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/MetricsCollectionTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/MetricsCollectionTest.java @@ -37,7 +37,7 @@ public class MetricsCollectionTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(MetricsCollectionTest.class, TestSchemas.restaurant()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relational, MetricsCollectionTest.class, TestSchemas.restaurant()); @Test void canRecoverFDBLevelMetrics() { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/StoreTimerMetricCollectorFromFDBRecordContextTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/StoreTimerMetricCollectorFromFDBRecordContextTest.java index 09ddc0cc38..e22347e249 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/StoreTimerMetricCollectorFromFDBRecordContextTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/metric/StoreTimerMetricCollectorFromFDBRecordContextTest.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.net.URI; import java.sql.SQLException; import java.util.function.Consumer; @@ -108,7 +107,7 @@ private Continuation executeSimpleSelectAndReturnContinuation(EmbeddedRelational } private void setupAndExecuteWithConnection(Consumer execute) throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/METRIC_COLLECTOR_TESTS")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection().unwrap(EmbeddedRelationalConnection.class); try (var statement = connection.createStatement()) { for (int i = 0; i < 10; i++) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java index 4dd9c73f72..0ed9e763ff 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CaseSensitivityQueryTests.java @@ -33,7 +33,6 @@ import org.junit.jupiter.params.provider.ValueSource; import javax.annotation.Nonnull; -import java.net.URI; import java.sql.SQLException; public class CaseSensitivityQueryTests { @@ -47,7 +46,7 @@ void caseSensitiveConnectionTest() throws Exception { final String schemaTemplate = "create type as struct LoCaTiOn (address string, latitude string, longitude string)" + " create table ReStAuRaNt(rest_no bigint, name string, location LoCaTiOn, primary key(rest_no))"; try (var extensionResource = EmbeddedRelationalExtension.newAsResource(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true).build()); - var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(extensionResource.getUnderlyingExtension()) + var ddl = Ddl.builder().database().relationalExtension(extensionResource.getUnderlyingExtension()) .withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true) .schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { @@ -80,7 +79,7 @@ void caseSensitiveConnectionTestCase2() throws Exception { final String schemaTemplate = "create type as struct LoCaTiOn (address string, latitude string, longitude string)" + " create table ReStAuRaNt(rest_no bigint, name string, location LoCaTiOn, primary key(rest_no))"; try (var extensionResource = EmbeddedRelationalExtension.newAsResource(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, false).build()); - var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(extensionResource.getUnderlyingExtension()) + var ddl = Ddl.builder().database().relationalExtension(extensionResource.getUnderlyingExtension()) .withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, false) .schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { @@ -114,7 +113,7 @@ void caseSensitiveConnectionTestCase3(boolean isCaseSensitive) throws Exception final String schemaTemplate = "create type as struct \"Location\" (address string, latitude string, longitude string)" + " create table \"Restaurant\"(rest_no bigint, name string, location \"Location\", primary key(rest_no))"; try (var extensionResource = EmbeddedRelationalExtension.newAsResource(Options.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, isCaseSensitive).build()); - var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(extensionResource.getUnderlyingExtension()) + var ddl = Ddl.builder().database().relationalExtension(extensionResource.getUnderlyingExtension()) .withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, isCaseSensitive) .schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CountQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CountQueryTest.java index 06960d591e..367f83a1f0 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CountQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/CountQueryTest.java @@ -56,11 +56,11 @@ CREATE INDEX i3 AS SELECT count(a) FROM t1 GROUP BY b @RegisterExtension @Order(1) - final SimpleDatabaseRule database = new SimpleDatabaseRule(CountQueryTest.class, SCHEMA_TEMPLATE); + final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, CountQueryTest.class, SCHEMA_TEMPLATE); @RegisterExtension @Order(2) - final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri).withSchema(database.getSchemaName()); + final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri).withSchema(database.getSchemaName()); @RegisterExtension @Order(3) diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java index 195993ef93..b056adcdc9 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExecutePropertyTests.java @@ -22,7 +22,6 @@ import com.apple.foundationdb.relational.api.Continuation; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalResultSet; import com.apple.foundationdb.relational.api.exceptions.ContextualSQLException; import com.apple.foundationdb.relational.recordlayer.ContinuationImpl; @@ -40,7 +39,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.sql.DriverManager; import java.util.List; public class ExecutePropertyTests { @@ -66,13 +64,13 @@ public class ExecutePropertyTests { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, ExecutePropertyTests.class, schemaTemplate); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withOptions(Options.NONE) .withSchema("TEST_SCHEMA"); @@ -99,7 +97,7 @@ void hitLimitEveryRow(Options.Name optionName, Object optionValue, int expectedR statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11'), (12, '12'), (13, '13'), (14, '14'), (15, '15'), (16, '16')"); Continuation continuation = ContinuationImpl.BEGIN; long nextCorrectResult = 10L; - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(optionName, optionValue).build())) { conn.setSchema("TEST_SCHEMA"); while (!continuation.atEnd()) { @@ -137,7 +135,7 @@ void hitLimitEveryRow(Options.Name optionName, Object optionValue, int expectedR @Test public void multipleConnectionsDoNotAffectEachOthersLimit() throws Exception { statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11'), (12, '12'), (13, '13'), (14, '14'), (15, '15'), (16, '16')"); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn1 = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 2).build()); var conn2 = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 3).build())) { conn1.setSchema("TEST_SCHEMA"); @@ -165,7 +163,7 @@ public void multipleConnectionsDoNotAffectEachOthersLimit() throws Exception { @Test public void limitIsKeptAcrossMultipleQueriesWithinTheSameTransaction() throws Exception { statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11'), (12, '12')"); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 5).build())) { conn.setSchema("TEST_SCHEMA"); conn.setAutoCommit(false); @@ -192,7 +190,7 @@ public void limitIsKeptAcrossMultipleQueriesWithinTheSameTransaction() throws Ex @Test public void limitIsKeptAcrossMultipleQueriesWithinTheSameTransactionSecondQueryFailsRightAway() throws Exception { statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11'), (12, '12')"); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 1).build())) { conn.setSchema("TEST_SCHEMA"); conn.setAutoCommit(false); @@ -214,7 +212,7 @@ public void limitIsKeptAcrossMultipleQueriesWithinTheSameTransactionSecondQueryF @Test public void limitIsResetWithNewTransaction() throws Exception { statement.executeUpdate("INSERT INTO FOO VALUES (10, '10'), (11, '11')"); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (var conn = driver.connect(database.getConnectionUri(), Options.builder().withOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 5).build())) { conn.setSchema("TEST_SCHEMA"); conn.setAutoCommit(false); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java index 1e34a66ed0..34c5bb03a5 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ExplainTests.java @@ -38,7 +38,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import java.sql.SQLException; import java.sql.Types; import java.util.Collections; @@ -96,7 +95,7 @@ void explainResultSetMetadataTest() throws Exception { "PLANNING_PHASE_TASKS_TOTAL_TIME_NS" ); final var expectedPlannerMetricsTypes = Collections.nCopies(expectedPlannerMetricsLabels.size(), Types.BIGINT); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { try (final RelationalResultSet resultSet = ps.executeQuery()) { @@ -125,7 +124,7 @@ void explainResultSetMetadataTest() throws Exception { @Test void explainWithNoContinuationTest() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { ps.setMaxRows(2); @@ -143,7 +142,7 @@ void explainWithNoContinuationTest() throws Exception { @Test void explainContainsEventStatsWithADebuggerInstalled() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { ps.setMaxRows(2); @@ -171,7 +170,7 @@ void explainContainsEventStats() throws Exception { Debugger.setDebugger(null); org.junit.jupiter.api.Assertions.assertNull(Debugger.getDebugger()); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); try (RelationalPreparedStatement ps = ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM RestaurantComplexRecord")) { ps.setMaxRows(2); @@ -222,7 +221,7 @@ public java.util.Optional ddl.setSchemaAndGetConnection().prepareStatement("EXPLAIN SELECT * FROM bla").execute()); @@ -314,7 +313,7 @@ void failExplain() throws Exception { @Test void explainWithContinuationSerializedPlanTest() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); Continuation continuation; try (final var connection = ddl.setSchemaAndGetConnection()) { @@ -346,7 +345,7 @@ void explainWithContinuationSerializedPlanTest() throws Exception { @Test void explainExecuteStatementTest() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { executeInsert(ddl); Continuation continuation; try (final var connection = ddl.setSchemaAndGetConnection()) { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ForceContinuationQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ForceContinuationQueryTests.java index 849c1cd32b..d2d04cfc75 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ForceContinuationQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/ForceContinuationQueryTests.java @@ -67,11 +67,11 @@ create index t3_i6 as select sum(col1) from t3 group by col2 @RegisterExtension @Order(1) - public final SimpleDatabaseRule db = new SimpleDatabaseRule(UniqueIndexTests.class, getTemplate_definition); + public final SimpleDatabaseRule db = new SimpleDatabaseRule(relationalExtension, UniqueIndexTests.class, getTemplate_definition); @RegisterExtension @Order(2) - public final RelationalConnectionRule connection = new RelationalConnectionRule(db::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, db::getConnectionUri) .withSchema(db.getSchemaName()); @RegisterExtension diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java index 7b0cb37d94..552a498c8c 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/GroupByQueryTests.java @@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; import static com.apple.foundationdb.relational.recordlayer.query.QueryTestUtils.insertT1Record; import static com.apple.foundationdb.relational.recordlayer.query.QueryTestUtils.insertT1RecordColAIsNull; @@ -54,7 +53,7 @@ void groupByWithScanLimit() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var conn = ddl.setSchemaAndGetConnection()) { Continuation continuation = null; conn.setOption(Options.Name.EXECUTION_SCANNED_ROWS_LIMIT, 2); @@ -120,7 +119,7 @@ void groupByWithRowLimit() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, b bigint, c bigint, primary key(pk))\n" + "create index i1 ON t1(a)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var conn = ddl.setSchemaAndGetConnection()) { conn.setOption(Options.Name.MAX_ROWS, 1); try (var statement = conn.createStatement()) { @@ -169,7 +168,7 @@ void isNullPredicateUsesGroupIndex() throws Exception { "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))\n" + "CREATE INDEX idx1 as select a, b, c from t1 order by a, b, c\n" + "CREATE INDEX sum_idx as select sum(c) from t1 group by a\n"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -200,7 +199,7 @@ void groupByClauseWithPredicateWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -225,7 +224,7 @@ void groupByClauseWithPredicateMultipleAggregationsWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 as select a, b, c from T1 order by a, b, c"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -250,7 +249,7 @@ void groupByClauseWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON T1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -279,7 +278,7 @@ void groupByClauseWorksWithSubquery() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON T1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -308,7 +307,7 @@ void groupByClauseWorksWithSubqueryAliases() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON T1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -337,7 +336,7 @@ void groupByClauseWorksWithSubqueryAliasesComplex() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); @@ -366,7 +365,7 @@ void groupByClauseWorksDifferentAggregations() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -395,7 +394,7 @@ void groupByClauseWorksComplexGrouping() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -424,7 +423,7 @@ void groupByClauseSingleGroup() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -445,7 +444,7 @@ void groupByClauseWithoutGroupingColumnsInProjectionList() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -466,7 +465,7 @@ void groupByClauseWithoutAggregationsInProjectionList() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -487,7 +486,7 @@ void groupByInSubSelectWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -508,7 +507,7 @@ void groupByConstantColumn() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 2, 10); @@ -529,7 +528,7 @@ void aggregationWithoutGroupBy() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -550,7 +549,7 @@ void nestedGroupByStatements() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 2000); insertT1Record(statement, 3, 1, 1, 10); @@ -571,7 +570,7 @@ void groupByClauseWorksComplexAggregations() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -600,7 +599,7 @@ void groupByClauseWithNestedAggregationsIsSupported() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -629,7 +628,7 @@ void expansionOfStarWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -656,7 +655,7 @@ void groupByClauseWithNamedGroupingColumns() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -686,7 +685,7 @@ void groupByOverJoinWorks() throws Exception { "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, x bigint, y bigint, z bigint, primary key(pk))" + "CREATE INDEX idx1 ON T1(B)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 10); @@ -713,7 +712,7 @@ void groupByWithNamedGroups() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE INDEX idx1 ON t1(a, b, c)"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertT1Record(statement, 2, 1, 1, 20); insertT1Record(statement, 3, 1, 2, 5); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java index b936b62845..62dc390c8d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/LargeRecordLayerSchemaTest.java @@ -36,7 +36,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.net.URI; import java.sql.ResultSet; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -56,7 +55,7 @@ void canCreateColumns(int colCount) throws Exception { StringBuilder template = new StringBuilder("CREATE TABLE T1("); template.append(IntStream.range(0, colCount).mapToObj(LargeRecordLayerSchemaTest::column).map(c -> c + " bigint").collect(Collectors.joining(","))); template.append(", PRIMARY KEY(").append(column(0)).append("))"); - try (var ddl = Ddl.builder().database(URI.create("/TEST/" + LargeRecordLayerSchemaTest.class.getSimpleName())).relationalExtension(relationalExtension).schemaTemplate(template.toString()).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(template.toString()).build()) { try (RelationalConnection conn = ddl.setSchemaAndGetConnection()) { try (var statement = conn.createStatement()) { RelationalStructBuilder builder = EmbeddedRelationalStruct.newBuilder(); @@ -84,7 +83,7 @@ void tooManyColumns() { template.append(IntStream.range(0, colCount).mapToObj(LargeRecordLayerSchemaTest::column).map(c -> c + " bigint").collect(Collectors.joining(","))); template.append(", PRIMARY KEY(").append(column(0)).append("))"); RelationalAssertions.assertThrowsSqlException(() -> - Ddl.builder().database(URI.create("/TEST/" + LargeRecordLayerSchemaTest.class.getSimpleName())).relationalExtension(relationalExtension).schemaTemplate(template.toString()).build()) + Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(template.toString()).build()) .hasErrorCode(ErrorCode.TOO_MANY_COLUMNS); } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java index 6c0560e056..1904ee4568 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/PreparedStatementTests.java @@ -42,11 +42,11 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.sql.Array; import java.sql.Connection; @@ -61,6 +61,13 @@ import java.util.function.BiConsumer; import java.util.stream.Stream; +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class PreparedStatementTests { private static final String schemaTemplate = @@ -91,7 +98,7 @@ public PreparedStatementTests() { @Test void failsToQueryWithoutASchema() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (Connection conn = ddl.getConnection()) { conn.setSchema(null); @@ -105,7 +112,7 @@ void failsToQueryWithoutASchema() throws Exception { @Test void simpleSelect() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -119,7 +126,7 @@ void simpleSelect() throws Exception { @Test void basicParameterizedQuery() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -138,7 +145,7 @@ void basicParameterizedQuery() throws Exception { @Test void parameterizedQueryMultipleParameters() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, 'testName')"); } @@ -164,7 +171,7 @@ void parameterizedQueryMultipleParameters() throws Exception { @Test void parameterizedQueryNamedParameters() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, 'testName')"); } @@ -190,7 +197,7 @@ void parameterizedQueryNamedParameters() throws Exception { @Test void parameterizedQueryNamedAndUnnamedParameters() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, 'testName')"); } @@ -216,7 +223,7 @@ void parameterizedQueryNamedAndUnnamedParameters() throws Exception { @Test void parameterizedQueryQuestionAndDollarParameter() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, 'testName')"); } @@ -242,7 +249,7 @@ void parameterizedQueryQuestionAndDollarParameter() throws Exception { @Test void parameterizedQueryMissingNamedParameters() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -256,7 +263,7 @@ void parameterizedQueryMissingNamedParameters() throws Exception { @Test void executeWithoutNeededParameterShouldThrow() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var ps = ddl.setSchemaAndGetConnection().prepareStatement("SELECT * FROM RestaurantComplexRecord WHERE REST_NO = ?")) { RelationalAssertions.assertThrowsSqlException(ps::executeQuery) .hasErrorCode(ErrorCode.UNDEFINED_PARAMETER); @@ -272,7 +279,7 @@ void executeWithoutNeededParameterShouldThrow() throws Exception { @Test void limit() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14), (15)"); } @@ -315,7 +322,7 @@ void limit() throws Exception { @Test void setMaxRowsExtremeValues() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14), (15)"); } @@ -340,7 +347,7 @@ void setMaxRowsExtremeValues() throws Exception { @Test void continuation() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -411,7 +418,7 @@ void continuation() throws Exception { @Test void setArrayTypeOfByte() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var count = statement.executeUpdate("INSERT INTO RestaurantReviewer(id) VALUES (1)"); Assertions.assertThat(count).isEqualTo(1); @@ -437,7 +444,7 @@ void setArrayTypeOfByte() throws Exception { @Test void setByteType() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var count = statement.executeUpdate("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (1)"); Assertions.assertThat(count).isEqualTo(1); @@ -456,7 +463,7 @@ void setByteType() throws Exception { @Test void prepareInList() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -517,7 +524,7 @@ void prepareInList() throws Exception { @Test void prepareInListWithMixedTypes() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (5), (6), (7), (8)"); } @@ -535,7 +542,7 @@ void prepareInListWithMixedTypes() throws Exception { @Disabled("equals does work with structs") // TODO ([SQL] Equals comparison does not support tuples) @Test void prepareSelectWithStruct() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantReviewer(id, name, email) VALUES " + "(1, 'alpha', 'alpha@example.com'), " + @@ -598,7 +605,7 @@ void prepareSelectWithStruct() throws Exception { @Test void prepareSelectWithStructList() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantReviewer(id, name, email) VALUES " + "(1, 'alpha', 'alpha@example.com'), " + @@ -664,7 +671,7 @@ void prepareSelectWithStructList() throws Exception { @Test void prepareUpdateWithStruct() throws Exception { final var statsAttributes = new Object[]{3L, "c", "d"}; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantReviewer(id, stats) VALUES (1, (2, 'a', 'b')), (2, (3, 'b', 'c')), (3, (4, 'c', 'd')), (4, (5, 'd', 'e')), (5, (6, 'e', 'f'))"); } @@ -701,7 +708,7 @@ static Stream prepareUpdateWithNestedStructMethodSource() { @ParameterizedTest @MethodSource("prepareUpdateWithNestedStructMethodSource") void prepareUpdateWithNestedStruct(Object[] attributes, boolean succeed) throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (1, 'mango & miso'), (2, 'basil & brawn'), (3, 'peach & pepper'), (4, 'smoky skillet'), (5, 'the tin pot')"); } @@ -736,7 +743,7 @@ void prepareUpdateWithNestedStruct(Object[] attributes, boolean succeed) throws @Test void prepareUpdateWithArrayOfPrimitives() throws Exception { final var customerAttributes = new String[]{"george", "adam", "billy"}; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (1, 'mango & miso'), (2, 'basil & brawn'), (3, 'peach & pepper'), (4, 'smoky skillet'), (5, 'the tin pot')"); } @@ -763,7 +770,7 @@ void prepareUpdateWithArrayOfPrimitives() throws Exception { @Test void prepareUpdateWithArrayOfStructs() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (1, 'mango & miso'), (2, 'basil & brawn'), (3, 'peach & pepper'), (4, 'smoky skillet'), (5, 'the tin pot')"); } @@ -835,7 +842,7 @@ private void assertTags(RelationalResultSet resultSet, Object[][] restaurantTagA @Test void prepareInListWrongTypeInArray() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { // IN list parameter is an array of Long, but has some non-Long elements. try (var ps = ddl.setSchemaAndGetConnection().prepareStatement("SELECT * FROM RestaurantComplexRecord WHERE rest_no in ?")) { @@ -879,7 +886,7 @@ void prepareInListWrongTypeInArray() throws Exception { @Test void prepareInListOfTuple() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (1, 'mango & miso'), (2, 'basil & brawn'), (3, 'peach & pepper'), (4, 'smoky skillet'), (5, 'the tin pot')"); } @@ -902,7 +909,7 @@ void prepareInListOfTuple() throws Exception { @Test void prepareInListWrongTypeShouldThrow() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -916,7 +923,7 @@ void prepareInListWrongTypeShouldThrow() throws Exception { @Test void prepareEmptyInList() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -933,7 +940,7 @@ void prepareEmptyInList() throws Exception { @Test void withPlanCache() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10), (11), (12), (13), (14)"); } @@ -964,7 +971,7 @@ void withPlanCache() throws Exception { @Test // TODO (Prepared Statement does not cast fields if set with the wrong types) void setWrongTypeForQuestionMarkParameter() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -1001,7 +1008,7 @@ void setWrongTypeForQuestionMarkParameter() throws Exception { @Test // TODO (Prepared Statement does not cast fields if set with the wrong types) void setWrongTypeForQuestionMarkNamedParameter() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.execute("INSERT INTO RestaurantComplexRecord(rest_no) VALUES (10)"); } @@ -1036,7 +1043,7 @@ void setWrongTypeForQuestionMarkNamedParameter() throws Exception { @Test void setNull() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().prepareStatement("INSERT INTO RestaurantComplexRecord(rest_no, name) VALUES (10, ?), (11, ?named), (12, ?)")) { statement.setNull(1, Types.NULL); statement.setNull("named", Types.NULL); @@ -1142,7 +1149,7 @@ static Stream listParameterProvider() { void emptyParametersInTheInList(String ignored, String column, BiConsumer consumer) throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT nested (a bigint)" + " CREATE TABLE T1(pk bigint, a1 bigint, a2 double, a3 string, a4 nested, a5 bytes, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().prepareStatement("select * from t1 where " + column + " in ?")) { consumer.accept(statement, ddl.getConnection()); statement.execute(); @@ -1155,7 +1162,7 @@ void emptyParametersInTheInList(String ignored, String column, BiConsumer consumer) throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT nested (a bigint)" + " CREATE TABLE T1(pk bigint, a1 bigint, a2 double, a3 string, a4 nested, a5 bytes, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().prepareStatement("select * from t1 where " + column + " in ? OPTIONS(LOG QUERY)")) { consumer.accept(statement, ddl.getConnection()); statement.execute(); @@ -1180,7 +1187,7 @@ void cachingQueryWithEmptyList(String ignored, String column, BiConsumer 10")) { resultSet.next(); @@ -248,7 +247,7 @@ void explainTableScan() throws Exception { @Test void explainHintedIndexScan() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { try (final RelationalResultSet resultSet = statement.executeQuery("EXPLAIN SELECT * FROM RestaurantComplexRecord USE INDEX (record_name_idx) WHERE rest_no > 10")) { resultSet.next(); @@ -261,7 +260,7 @@ void explainHintedIndexScan() throws Exception { @Test void explainUnhintedIndexScan() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { try (final RelationalResultSet resultSet = statement.executeQuery("EXPLAIN SELECT * FROM RestaurantComplexRecord AS R WHERE EXISTS (SELECT * FROM R.reviews AS RE WHERE RE.rating >= 9)")) { resultSet.next(); @@ -274,7 +273,7 @@ void explainUnhintedIndexScan() throws Exception { @Test void selectWithPredicateCompositionVariants() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); final var l42 = insertRestaurantComplexRecord(statement, 42L, "rest1"); @@ -314,7 +313,7 @@ void selectWithPredicateCompositionVariants() throws Exception { @Test @Disabled("(yhatem) until https://github.com/FoundationDB/fdb-record-layer/issues/1945 is fixed") void selectWithNullInComparisonOperator() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { var insertedRecord = insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT * FROM RestaurantComplexRecord WHERE 1 is null")) { @@ -350,7 +349,7 @@ void selectWithNullInComparisonOperator() throws Exception { @Test void selectWithFalsePredicate() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 11L); @@ -363,7 +362,7 @@ void selectWithFalsePredicate() throws Exception { @Test void selectWithFalsePredicate2() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 11L); @@ -376,7 +375,7 @@ void selectWithFalsePredicate2() throws Exception { @Test void selectWithContinuation() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.setMaxRows(1); insertRestaurantComplexRecord(statement); @@ -413,7 +412,7 @@ void selectWithContinuation() throws Exception { @Test void selectWithContinuationBeginEndShouldFail() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 42L, "rest1"); @@ -429,7 +428,7 @@ void selectWithContinuationBeginEndShouldFail() throws Exception { @Test void testSelectWithIndexHint() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); // successfully execute a query with hinted index @@ -459,7 +458,7 @@ void testSelectWithIndexHint() throws Exception { void testSelectWithCoveringIndexHint() throws Exception { final String schema = "CREATE TABLE T1(COL1 bigint, COL2 bigint, COL3 bigint, PRIMARY KEY(COL1, COL3))" + " CREATE INDEX T1_IDX as select col1, col3, col2 from t1 order by col1, col3"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var row1 = EmbeddedRelationalStruct.newBuilder().addLong("COL1", 42L).addLong("COL2", 100L).addLong("COL3", 200L).build(); int cnt = statement.executeInsert("T1", row1); @@ -482,7 +481,7 @@ void testSelectWithCoveringIndexHint() throws Exception { @Test void projectIndividualColumns() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT name FROM RestaurantComplexRecord WHERE 11 <= rest_no")) { @@ -495,7 +494,7 @@ void projectIndividualColumns() throws Exception { @Test void projectIndividualQualifiedColumns() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT RestaurantComplexRecord.name FROM RestaurantComplexRecord WHERE 11 <= rest_no")) { @@ -508,7 +507,7 @@ void projectIndividualQualifiedColumns() throws Exception { @Test void projectIndividualQualifiedColumnsOverAlias() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT name FROM RestaurantComplexRecord AS X WHERE 11 <= rest_no")) { @@ -521,7 +520,7 @@ void projectIndividualQualifiedColumnsOverAlias() throws Exception { @Test void projectIndividualQualifiedColumnsOverAlias2() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT X.name FROM RestaurantComplexRecord AS X WHERE 11 <= rest_no")) { @@ -534,7 +533,7 @@ void projectIndividualQualifiedColumnsOverAlias2() throws Exception { @Test void getBytes() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 1, "getBytes", "blob1".getBytes(StandardCharsets.UTF_8)); @@ -566,7 +565,7 @@ void partiqlNestingWorks() throws Exception { " CREATE TYPE AS STRUCT D ( e E )" + " CREATE TYPE AS STRUCT E ( f bigint )" + " CREATE TABLE tbl1 (id bigint, c C, a A, PRIMARY KEY(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var result = EmbeddedRelationalStruct.newBuilder() .addLong("ID", 42L) @@ -618,7 +617,7 @@ void partiqlNestingWorksWithRepeatedLeaf() throws Exception { " CREATE TYPE AS STRUCT D ( e E )" + " CREATE TYPE AS STRUCT E ( f bigint array )" + " CREATE TABLE tbl1 (id bigint, c C, a A, PRIMARY KEY(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var result = EmbeddedRelationalStruct.newBuilder() .addLong("ID", 42L) @@ -663,7 +662,7 @@ void partiqlAccessingNestedFieldWithInnerRepeatedFieldsFails() throws Exception " CREATE TYPE AS STRUCT D ( e E array )" + " CREATE TYPE AS STRUCT E ( f bigint array )" + " CREATE TABLE tbl1 (id bigint, c C, a A, PRIMARY KEY(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { try { statement.execute("SELECT id, c.d.e.f, a.b.c.d.e.f FROM tbl1"); @@ -678,7 +677,7 @@ void partiqlAccessingNestedFieldWithInnerRepeatedFieldsFails() throws Exception @Disabled // until we fix the implicit fetch operator in record layer. void projectIndividualPredicateColumns() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT rest_no FROM RestaurantComplexRecord WHERE 11 <= rest_no")) { @@ -691,7 +690,7 @@ void projectIndividualPredicateColumns() throws Exception { @Disabled // until we implement1 operators for type promotion and casts in record layer. void predicateWithImplicitCast() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); RelationalStruct l42 = insertRestaurantComplexRecord(statement, 42L, "rest1"); @@ -707,7 +706,7 @@ void predicateWithImplicitCast() throws Exception { @Test void existsPredicateWorks() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); insertRestaurantComplexRecord(statement, 42L, "rest1", List.of(Triple.of(1L, 4L, List.of()), Triple.of(2L, 5L, List.of()))); @@ -722,7 +721,7 @@ void existsPredicateWorks() throws Exception { @Test void existsPredicateWorksWithNonNullableArray() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateWithNonNullableArrays).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateWithNonNullableArrays).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement, 42L, "rest1", List.of(Triple.of(1L, 4L, List.of()), Triple.of(2L, 5L, List.of()))); RelationalStruct l43 = insertRestaurantComplexRecord(statement, 43L, "rest2", List.of(Triple.of(3L, 9L, List.of()), Triple.of(4L, 8L, List.of()))); @@ -736,7 +735,7 @@ void existsPredicateWorksWithNonNullableArray() throws Exception { @Test void existsPredicateNestedWorks() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); RelationalStruct l42 = insertRestaurantComplexRecord(statement, 42L, "rest1", @@ -758,7 +757,7 @@ void testSubquery() throws Exception { final String schema = "CREATE TYPE AS STRUCT contact_detail(name string, phone_number string, address string) " + "CREATE TYPE AS STRUCT messages(\"TEXT\" string, timestamp bigint,sent boolean) " + "CREATE TABLE conversations(id bigint, other_party CONTACT_DETAIL, messages MESSAGES ARRAY,primary key(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var row1 = EmbeddedRelationalStruct.newBuilder() .addLong("ID", 0L) @@ -848,7 +847,7 @@ void testSubquery() throws Exception { @Test void aliasingColumnsWorks() throws Exception { - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { insertRestaurantComplexRecord(statement); try (final RelationalResultSet resultSet = statement.executeQuery("SELECT Y.M FROM (SELECT X.N AS M FROM (SELECT name AS N FROM RestaurantComplexRecord WHERE 11 <= rest_no) X) Y")) { @@ -862,7 +861,7 @@ void aliasingColumnsWorks() throws Exception { @Test void aliasingTableToResolveAmbiguityWorks() throws Exception { final String schema = "CREATE TABLE FOO(FOO bigint, PRIMARY KEY(FOO))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schema).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schema).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var row1 = EmbeddedRelationalStruct.newBuilder().addLong("FOO", 42L).build(); int cnt = statement.executeInsert("FOO", row1); @@ -897,14 +896,14 @@ void testIncorrectUserDefinedFunction() throws Exception { "CREATE FUNCTION lat(IN x TYPE Location) RETURNS string AS x.coor.latitude\n"; // "coor" is wrong // fail to build the MacroFunctionValue in the DDL step - Assertions.assertThrows(SQLException.class, () -> Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate1).build()).getMessage(); + Assertions.assertThrows(SQLException.class, () -> Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate1).build()).getMessage(); final String schemaTemplate3 = "CREATE TYPE AS STRUCT LATLON (latitude string, longitude string)\n" + "CREATE TYPE AS STRUCT Location (name string, coord LATLON)" + "CREATE TABLE T1(uid bigint, loc Location, PRIMARY KEY(uid))\n" + "CREATE FUNCTION name(IN x TYPE Location) RETURNS string AS x.name\n"; // name is a reserved word - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate3).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate3).build()) { try (var ps = ddl.setSchemaAndGetConnection().prepareStatement("SELECT * FROM T1 WHERE name(loc) = ?name")) { ps.setString("name", "Apple Park Visitor Center"); final var errorMsg3 = Assertions.assertThrows(SQLException.class, ps::executeQuery).getMessage(); @@ -957,7 +956,7 @@ void testBitmapNoBitmapIndex() throws Exception { private void testBitmapResult(String schemaTemplate, String query) throws Exception { int expectedByteArrayLength = 1250; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'world')"); statement.executeUpdate("insert into t1 values (2, 'world')"); @@ -1021,7 +1020,7 @@ private static List collectOnBits(@Nullable byte[] bitmap, int expectedArr private void testBitmapResultWithEmptyGroup(String schemaTemplate) throws Exception { int expectedByteArrayLength = 1250; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'world')"); statement.executeUpdate("insert into t1 values (2, 'world')"); @@ -1058,7 +1057,7 @@ private void testBitmapResultWithEmptyGroup(String schemaTemplate) throws Except @Test void queryJavaCallFunctionLocallyCreatedUdf() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'world')"); Assertions.assertTrue(statement.execute("SELECT java_call('com.apple.foundationdb.relational.recordlayer.query.udf.SumUdf', pk, 42) + 100 FROM T1"), "Did not return a result set from a select statement!"); @@ -1075,7 +1074,7 @@ void queryJavaCallFunctionLocallyCreatedUdf() throws Exception { void queryJavaCallSimulatecustomerFunction() throws Exception { final var expected = EmbeddedRelationalArray.newBuilder().addBytes(new byte[]{0xA, 0xB}).build(); final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bytes, b bytes array, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, X'0A', [ X'0B' ])"); Assertions.assertTrue(statement.execute("SELECT java_call('com.apple.foundationdb.relational.recordlayer.query.udf.ByteOperationsUdf', a, b) FROM T1"), "Did not return a result set from a select statement!"); @@ -1091,7 +1090,7 @@ void queryJavaCallSimulatecustomerFunction() throws Exception { @Test void selectStarStatement() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 101)"); Assertions.assertTrue(statement.execute("select * from t1")); @@ -1107,7 +1106,7 @@ void selectStarStatement() throws Exception { @Test void selectWithEmptyListAsPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'bla')"); } @@ -1122,7 +1121,7 @@ void selectWithEmptyListAsPredicate() throws Exception { @Test void deleteLimit() throws Exception { final String schemaTemplate = "CREATE TABLE simple (rest_no bigint, name string, primary key(rest_no))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into simple values (1,'testRecord1'), (2, 'testRecord2')"); Assertions.assertTrue(statement.execute("select * from simple")); @@ -1146,7 +1145,7 @@ void deleteLimit() throws Exception { @Test void selectNestedStarWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 101)"); Assertions.assertTrue(statement.execute("select (*) from t1")); @@ -1175,7 +1174,7 @@ void selectNestedStarWorks() throws Exception { @Test void testNamingStruct() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); Assertions.assertTrue(statement.execute("select struct asd (a, 42, struct def (b, c)) as X from t1")); @@ -1193,7 +1192,7 @@ void testNamingStruct() throws Exception { @Test void testNamingStructsSameType() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); Assertions.assertTrue(statement.execute("select struct asd (a, 42, struct def (b, c), struct def(b, c)) as X from t1")); @@ -1214,7 +1213,7 @@ void testNamingStructsSameType() throws Exception { @Test void testNamingStructsDifferentTypesThrows() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); final var message = Assertions.assertThrows(SQLException.class, () -> statement.execute("select struct asd (a, 42, struct def (b, c), struct def(b, c, a)) as X from t1")).getMessage(); @@ -1226,7 +1225,7 @@ void testNamingStructsDifferentTypesThrows() throws Exception { @Test void testNamingStructsSameTypeDifferentNestingLevels() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); Assertions.assertTrue(statement.execute("select a, 42, struct def (b, c), (a, b, c, struct def(b, c)) as X from t1")); @@ -1246,7 +1245,7 @@ void testNamingStructsSameTypeDifferentNestingLevels() throws Exception { @Test void testNamingStructWithNameOfTableIsNotPermitted() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101)"); ContextualSQLException err = Assertions.assertThrows(ContextualSQLException.class, () -> statement.execute("select a, 42, struct T1 (b, c) as X from t1")); @@ -1264,7 +1263,7 @@ void tupleInListAsPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, PRIMARY KEY(pk))" + " CREATE INDEX a_index as select pk, a from T1 order by pk, a"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'bla')"); statement.executeUpdate("insert into t1 values (40, 'foo')"); @@ -1307,7 +1306,7 @@ void tupleInListAsPredicate2() throws Exception { " CREATE INDEX pk_a as select pk, a from T1 order by pk, a" + " CREATE INDEX b_a as select b, a from T1 order by b, a"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'bla', 1)"); statement.executeUpdate("insert into t1 values (40, 'foo', 2)"); @@ -1344,7 +1343,7 @@ void tupleThreeInListAsPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, b bigint, PRIMARY KEY(pk))" + " CREATE INDEX pk_a_b as select pk, a, b from T1 order by pk, a, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 'bla', 1)"); statement.executeUpdate("insert into t1 values (40, 'foo', 2)"); @@ -1381,7 +1380,7 @@ void tupleThreeInListAsPredicate() throws Exception { void unionIsNotSupported() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { RelationalAssertions.assertThrows(() -> ((EmbeddedRelationalStatement) statement) @@ -1395,7 +1394,7 @@ void unionIsNotSupported() throws Exception { void structArrayContains() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT A(col2 string, col3 bigint, col4 bigint) " + "CREATE TABLE T1(col1 bigint, a A Array, col5 bigint, primary key(col1))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, [('Apple', 1, 100), ('Orange', 2, 200)], 142), (44, [('Grape', 3, 300), ('Pear', 4, 400)], 144)"); Assertions.assertTrue(statement.execute("SELECT T1.col5 FROM T1 where exists (SELECT 1 FROM T1.A r where r.col2 = 'Grape') ")); @@ -1426,7 +1425,7 @@ void structArrayContains() throws Exception { @Test void primitiveArrayContains() throws Exception { final String schemaTemplate = "CREATE TABLE T1(col1 bigint, a string Array, primary key(col1))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, ['Apple', 'Orange']), (44, ['Grape', 'Pear'])"); Assertions.assertTrue(statement.execute("SELECT * FROM T1 where exists (SELECT 1 FROM T1.A r where r = 'Grape')")); @@ -1457,7 +1456,7 @@ void primitiveArrayContains() throws Exception { @Test void cteWorksCorrectly() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101), (44, 101, 501, 102)"); //Assertions.assertTrue(statement.execute("with C1 (X, Y, Z) as (SELECT a, b, c from T1) select Y, Z from C1")); @@ -1477,7 +1476,7 @@ void cteWorksCorrectly() throws Exception { @Test void cteWorksCorrectly2() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101), (44, 101, 501, 102)"); //Assertions.assertTrue(statement.execute("with C1 (X, Y, Z) as (SELECT a, b, c from T1) select Y, Z from C1")); @@ -1497,7 +1496,7 @@ void cteWorksCorrectly2() throws Exception { @Test void cteWithColumnAliasesWorksCorrectly() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (42, 100, 500, 101), (44, 101, 501, 102)"); Assertions.assertTrue(statement.execute("with C1 (X, Y, Z) as (SELECT a, b, c from T1) select Y, Z from C1")); @@ -1516,7 +1515,7 @@ void cteWithColumnAliasesWorksCorrectly() throws Exception { void unionParenthesisIsNotSupported() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a string, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { RelationalAssertions.assertThrows(() -> ((EmbeddedRelationalStatement) statement) @@ -1529,7 +1528,7 @@ void unionParenthesisIsNotSupported() throws Exception { @Test void selfJoinTest() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20)"); Assertions.assertTrue(statement.execute("select * from t1 as x, t1 as y")); @@ -1552,7 +1551,7 @@ void selfJoinTest() throws Exception { void testInsertUuidTest() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a UUID, PRIMARY KEY(pk))"; final var actualUuidValue = UUID.fromString("14b387cd-79ad-4860-9588-9c4e81588af0"); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (1, '14b387cd-79ad-4860-9588-9c4e81588af0')")) { final var numActualInserted = insert.executeUpdate(); Assertions.assertEquals(1, numActualInserted); @@ -1567,7 +1566,7 @@ void testInsertUuidTest() throws Exception { } } } - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (?pk, ?a)")) { insert.setLong("pk", 1L); insert.setUUID("a", actualUuidValue); @@ -1596,7 +1595,7 @@ void testInsertStructWithUuidTest() throws Exception { .addLong("A", 2L) .addUuid("B", actualUuidValue2) .build(); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (?pk, ?a, ?b)")) { insert.setLong("pk", 1L); insert.setUUID("a", actualUuidValue1); @@ -1632,7 +1631,7 @@ private static Stream vectorTypeProvider() { void vectorInsertSelectPrepared(String vectorType, RealVector vector) throws Exception { final String schemaTemplate = String.format("CREATE TABLE T1(pk bigint, v %s, PRIMARY KEY(pk))", vectorType); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var insert = ddl.setSchemaAndGetConnection().prepareStatement("insert into t1 values (?pk, ?v)")) { insert.setLong("pk", 1L); insert.setObject("v", vector); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java index 8ddfbbdede..50718ff7f3 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TemporaryFunctionTests.java @@ -57,6 +57,7 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; @@ -72,6 +73,13 @@ * This is for testing different aspects of temporary SQL functions. This test suite can migrate to YAML once we have * support for multi-statement transactions in YAML. */ +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class TemporaryFunctionTests { @RegisterExtension @@ -89,7 +97,7 @@ public TemporaryFunctionTests() { @Test void createTemporaryFunctionWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -106,7 +114,7 @@ void createTemporaryFunctionWorks() throws Exception { @Test void createTemporaryFunctionAcrossTransactionsWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -131,7 +139,7 @@ void createTemporaryFunctionAcrossTransactionsWorks() throws Exception { void createTemporaryFunctionWithNameCollisionsThrows() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) " + "create function foo() as select * from t1 where a < 43"; // add non-temporary function called foo - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); connection.setAutoCommit(false); try (var statement = connection.createStatement()) { @@ -152,7 +160,7 @@ void createTemporaryFunctionWithNameCollisionsThrows() throws Exception { @Test void temporaryFunctionVisibilityAcrossTransactionAfterCommit() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")) + try (var ddl = Ddl.builder().database() .relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); @@ -177,7 +185,7 @@ void temporaryFunctionVisibilityAcrossTransactionAfterCommit() throws Exception @Test void temporaryFunctionVisibilityAcrossTransactionsAfterRollback() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")) + try (var ddl = Ddl.builder().database() .relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); @@ -202,7 +210,7 @@ void temporaryFunctionVisibilityAcrossTransactionsAfterRollback() throws Excepti @Test void createOrReplaceTemporaryFunctionWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -220,7 +228,7 @@ void createOrReplaceTemporaryFunctionWorks() throws Exception { @Test void createOrReplaceTemporaryFunctionAndInvokeMultipleTimesWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -242,7 +250,7 @@ void createOrReplaceTemporaryFunctionAndInvokeMultipleTimesWorks() throws Except @Test void temporaryFunctionIsMemoizedAcrossInvocations() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -277,7 +285,7 @@ private UserDefinedFunction getUserDefinedFunction(@Nonnull RelationalConnection @Test void dropTemporaryFunctionWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -307,7 +315,7 @@ void dropTemporaryFunctionWorks() throws Exception { void dropNonTemporaryFunctionFails() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) " + "create function sq0(in x bigint) as select * from t1 where a > x"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -329,7 +337,7 @@ void dropNonTemporaryFunctionFails() throws Exception { @Test void dropTemporaryFunctionIfExistsWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -356,7 +364,7 @@ void dropTemporaryFunctionIfExistsWorks() throws Exception { @Test void dropTemporaryFunctionMultipleCallsWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -391,7 +399,7 @@ void dropTemporaryFunctionMultipleCallsWorks() throws Exception { @Test void dropNestedTemporaryFunctionCallsWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -436,7 +444,7 @@ void dropNestedTemporaryFunctionCallsWorks() throws Exception { @Test void createTemporaryFunctionSameNameThrows() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -456,7 +464,7 @@ void createTemporaryFunctionSameNameThrows() throws Exception { @Test void createTemporaryFunctionWithPreparedParameters() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -487,7 +495,7 @@ void createTemporaryFunctionWithPreparedParameters() throws Exception { @Test void createNestedTemporaryFunctionWithPreparedParameters() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -522,7 +530,7 @@ void createNestedTemporaryFunctionWithPreparedParameters() throws Exception { @Test void createTemporaryFunctionWithPreparedParametersWorks() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -545,7 +553,7 @@ void createTemporaryFunctionWithPreparedParametersWorks() throws Exception { @Test void createNestedTemporaryFunctionsWithPreparedParameters() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -602,7 +610,7 @@ void createNestedTemporaryFunctionsWithPreparedParameters() throws Exception { @Test void createNestedTemporaryFunctionsWithVariousLiterals() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -653,7 +661,7 @@ void createNestedTemporaryFunctionsWithVariousLiterals() throws Exception { @Test void createNestedTemporaryFunctionsWithPreparedParametersOptimizationConstraintPertainingNestedFunction() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -721,7 +729,7 @@ void createNestedTemporaryFunctionsWithPreparedParametersOptimizationConstraintP @Test void createNestedTemporaryFunctionsWithLiteralsOptimizationConstraintPertainingNestedFunction() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -784,7 +792,7 @@ void createNestedTemporaryFunctionsWithLiteralsOptimizationConstraintPertainingN @Test void useMultipleReferencesOfTemporaryFunctionsWithPreparedParameters() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -870,7 +878,7 @@ void useMultipleReferencesOfTemporaryFunctionsWithPreparedParameters() throws Ex @Test void useMultipleReferencesOfTemporaryFunctionsWithPreparedParametersAcrossContinuations() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 15)"); } @@ -944,7 +952,7 @@ void useMultipleReferencesOfTemporaryFunctionsWithPreparedParametersAcrossContin @Test void useMultipleReferencesOfTemporaryFunctionsWithPreparedParametersContinuationsAcrossTransactions() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk)) create index indexOnA as select a from t1 where a < 35"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1(pk, a) values (1, 10), (2, 15)"); } @@ -1048,7 +1056,7 @@ void useMultipleReferencesOfTemporaryFunctionsWithPreparedParametersContinuation void createTemporaryFunctionCaseSensitivityOption(boolean isCaseSensitive) throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; try (var ddl = Ddl.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, isCaseSensitive) - .database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + .database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -1068,7 +1076,7 @@ void createTemporaryFunctionCaseSensitivityOption(boolean isCaseSensitive) throw void attemptToCreateTemporaryFunctionWithDifferentCaseSensitivityOptionCase1() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; try (var ddl = Ddl.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true) - .database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + .database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -1092,7 +1100,7 @@ void attemptToCreateTemporaryFunctionWithDifferentCaseSensitivityOptionCase1() t void attemptToCreateTemporaryFunctionWithDifferentCaseSensitivityOptionCase2() throws Exception { final String schemaTemplate = "create table t1(pk bigint, a bigint, primary key(pk))"; try (var ddl = Ddl.builder().withOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, false) - .database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + .database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into t1 values (1, 10), (2, 20), (3, 30), (4, 40), (5, 50)"); } @@ -1161,7 +1169,7 @@ public LogicalOperator visitStatementBody(final RelationalParser.StatementBodyCo void unpivotRepeatedFieldInSqlFunctionWorksCorrectly() throws Exception { final String schemaTemplate = "create type as struct city(name string, population bigint) " + "create table country(id bigint, name string, continent string, cities city array, primary key(id))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { statement.executeUpdate("insert into country values " + "(1, 'USA', 'North America', [('New York' ,8419600), ('Los Angeles', 3980400)]), " + diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TransactionBoundQueryTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TransactionBoundQueryTest.java index d430de7b52..e52621c666 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TransactionBoundQueryTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/TransactionBoundQueryTest.java @@ -64,7 +64,6 @@ import javax.annotation.Nonnull; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.Map; import java.util.function.Consumer; @@ -85,7 +84,7 @@ CREATE TABLE t3(id bigint, a bigint, e bigint, f bigint, PRIMARY KEY(id)) final EmbeddedRelationalExtension embeddedExtension = new EmbeddedRelationalExtension(); @RegisterExtension @Order(1) - final SimpleDatabaseRule databaseRule = new SimpleDatabaseRule(TransactionBoundQueryTest.class, SCHEMA_TEMPLATE); + final SimpleDatabaseRule databaseRule = new SimpleDatabaseRule(embeddedExtension, TransactionBoundQueryTest.class, SCHEMA_TEMPLATE); @Nonnull private static Options engineOptions() throws SQLException { @@ -98,7 +97,8 @@ private static Options engineOptions() throws SQLException { @Nonnull private EmbeddedRelationalConnection connectEmbedded() throws SQLException { - Connection connection = DriverManager.getConnection(databaseRule.getConnectionUri().toString()); + Connection connection = embeddedExtension.getDriver() + .connect(databaseRule.getConnectionUri(), Options.NONE); connection.setSchema(databaseRule.getSchemaName()); return connection.unwrap(EmbeddedRelationalConnection.class); } @@ -128,23 +128,20 @@ private EmbeddedRelationalConnection connectTransactionBound(@Nonnull FDBRecordC .setContext(context) .open(); - final var originalDriver = embeddedExtension.getDriver(); - DriverManager.deregisterDriver(originalDriver); - var newDriver = new EmbeddedRelationalDriver(new TransactionBoundEmbeddedRelationalEngine(engineOptions())); - DriverManager.registerDriver(newDriver); - final var driver = (EmbeddedRelationalDriver) DriverManager.getDriver(databaseRule.getConnectionUri().toString()); + // Use the bound driver directly instead of going through DriverManager. + // Previously this test deregistered the extension's driver, registered its own, did + // a getDriver() lookup, then restored the original. Under parallel JUnit execution + // that dance both raced with other tests and depended on whatever DriverManager + // happened to hold — neither is necessary because the test already has both driver + // objects in scope. + final var newDriver = new EmbeddedRelationalDriver(new TransactionBoundEmbeddedRelationalEngine(engineOptions())); final var connectionOptions = Options.none(); - try { - final var schemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaData, databaseRule.getSchemaTemplateName(), - metaData.getVersion()); - final var transactionBoundConnection = driver.connect(databaseRule.getConnectionUri(), - new RecordStoreAndRecordContextTransaction(newStore, context, schemaTemplate), connectionOptions); - transactionBoundConnection.setSchema(databaseRule.getSchemaName()); - return transactionBoundConnection.unwrap(EmbeddedRelationalConnection.class); - } finally { - DriverManager.deregisterDriver(newDriver); - DriverManager.registerDriver(originalDriver); - } + final var schemaTemplate = RecordLayerSchemaTemplate.fromRecordMetadata(metaData, databaseRule.getSchemaTemplateName(), + metaData.getVersion()); + final var transactionBoundConnection = newDriver.connect(databaseRule.getConnectionUri(), + new RecordStoreAndRecordContextTransaction(newStore, context, schemaTemplate), connectionOptions); + transactionBoundConnection.setSchema(databaseRule.getSchemaName()); + return transactionBoundConnection.unwrap(EmbeddedRelationalConnection.class); } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java index cb37157b60..bafe8ddf6d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/UpdateTest.java @@ -25,7 +25,6 @@ import com.apple.foundationdb.relational.api.EmbeddedRelationalStruct; import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; -import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.api.RelationalPreparedStatement; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.recordlayer.ContinuationImpl; @@ -43,14 +42,22 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import org.opentest4j.AssertionFailedError; -import java.sql.DriverManager; import java.sql.SQLException; import java.util.Arrays; import java.util.List; import java.util.function.Function; +import java.net.URI; +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class UpdateTest { private static final String schemaTemplate = @@ -66,7 +73,7 @@ CREATE TABLE RestaurantReviewer (id bigint, name string, email string, stats Rev @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(UpdateTest.class, schemaTemplate, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, UpdateTest.class, schemaTemplate, new SchemaTemplateRule.SchemaTemplateOptions(true, true)); @RegisterExtension @Order(4) @@ -167,7 +174,7 @@ void updateArrayFieldVerifyCacheTest() throws Exception { } public void insertRecords(int numRecords) throws RelationalException, SQLException { - try (final var con = DriverManager.getConnection(database.getConnectionUri().toString())) { + try (final var con = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString()))) { con.setSchema(database.getSchemaName()); final var builder = new StringBuilder("INSERT INTO RestaurantReviewer(id) VALUES"); for (int i = 0; i < numRecords; i++) { @@ -193,7 +200,7 @@ private static RelationalPreparedStatement prepareUpdate(RelationalConnection co } private void verifyUpdates(String updatedField, Object expectedValue, int updatedUpTill) throws SQLException { - try (final var con = DriverManager.getConnection(database.getConnectionUri().toString()).unwrap(RelationalConnection.class)) { + try (final var con = relationalExtension.getDriver().connect(URI.create(database.getConnectionUri().toString())).unwrap(RelationalConnection.class)) { con.setSchema(database.getSchemaName()); final var statement = con.prepareStatement("SELECT id, " + updatedField + " from RestaurantReviewer WHERE id >= 0"); try (final var resultSet = statement.executeQuery()) { @@ -224,7 +231,7 @@ private Pair updateWithScanRowLimit(final String fieldToU Options options) throws SQLException, RelationalException { var continuation = continuationAndNumUpdated.getLeft(); var updatedUpTill = continuationAndNumUpdated.getRight(); - final var driver = (RelationalDriver) DriverManager.getDriver(database.getConnectionUri().toString()); + final var driver = relationalExtension.getDriver(); try (final var con = (EmbeddedRelationalConnection) driver.connect(database.getConnectionUri(), options)) { con.setSchema(database.getSchemaName()); final var statement = prepareUpdate(con, fieldToUpdate, updateValue.apply(con), continuation); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java index 97b7b17c4b..8f635909c4 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/V2PlanGeneratorTests.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import java.net.URI; public class V2PlanGeneratorTests { @RegisterExtension @@ -49,7 +48,7 @@ public V2PlanGeneratorTests() { @Test void simpleQueryWorks() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -68,7 +67,7 @@ void simpleQueryWorks() throws Exception { @Test void simpleQuery2Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -91,7 +90,7 @@ void simpleQuery2Works() throws Exception { @Test void simpleQuery3Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -114,7 +113,7 @@ void simpleQuery3Works() throws Exception { @Test void simpleSubquery4Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -133,7 +132,7 @@ void simpleSubquery4Works() throws Exception { @Test void simpleSubquery5Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -152,7 +151,7 @@ void simpleSubquery5Works() throws Exception { @Test void simpleSubquery6Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -171,7 +170,7 @@ void simpleSubquery6Works() throws Exception { @Test void simpleSubquery7Works() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 100, 500, 101), (43, 101, 501, 102)"); @@ -194,7 +193,7 @@ void simpleCorrelatedJoinWorks() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT Location (longitude bigint, latitude bigint) " + "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info array, b bigint array, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500)), (34, (100, 200))], [500, 501], 101), (43, [(2, (2000, 2200)), (9, (2000,2400))], [700, 701], 102)"); @@ -224,7 +223,7 @@ void starExpansionTest1() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -251,7 +250,7 @@ void starExpansionTest2() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -278,7 +277,7 @@ void starExpansionTest3() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -305,7 +304,7 @@ void starExpansionTest4() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -330,7 +329,7 @@ void starExpansionTest5() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -357,7 +356,7 @@ void starExpansionTest6() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -382,7 +381,7 @@ void starExpansionTest7() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -407,7 +406,7 @@ void starExpansionTest8() throws Exception { "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info array, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500))], 500, 101), (43, [(34, (5001, 5501))], 501, 102)"); @@ -432,7 +431,7 @@ void starExpansionTestDisabled() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT Info (ratings bigint, nested1 bigint, nested2 bigint) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, 5000, 5500), 500, 101), (43, (34, 5001, 5501), 501, 102)"); @@ -460,7 +459,7 @@ void orderByTest1() throws Exception { "CREATE TABLE T1(pk bigint, i Info array, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select pk, i from t2 order by pk, i"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500))], 500, 101), (43, [(34, (5001, 5501))], 501, 102)"); @@ -486,7 +485,7 @@ void orderByTest2() throws Exception { "CREATE TABLE T1(pk bigint, i Info array, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select pk, i from t2 order by pk, i"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500))], 500, 101), (43, [(34, (5001, 5501))], 501, 102)"); @@ -512,7 +511,7 @@ void orderByTest3() throws Exception { "CREATE TABLE T1(pk bigint, i Info array, b bigint, c bigint, PRIMARY KEY(pk))" + "CREATE TABLE T2(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select pk, i from t2 order by pk, i"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, [(30, (5000, 5500))], 500, 101), (43, [(34, (5001, 5501))], 501, 102)"); @@ -527,7 +526,7 @@ void aggregateFunctionTest1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -552,7 +551,7 @@ void aggregateFunctionTest2() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -575,7 +574,7 @@ void aggregateFunctionTest3SelectStar() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension) + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension) .schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; @@ -593,7 +592,7 @@ void simpleArithmeticExpression1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -616,7 +615,7 @@ void simplePredicate1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -635,7 +634,7 @@ void simplePredicate2InPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -654,7 +653,7 @@ void simplePredicate3InPredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b bigint, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 18), (43, 15, 20)"); @@ -673,7 +672,7 @@ void simplePredicate4LikePredicate() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, 'hello'), (43, 15, 'world')"); @@ -692,7 +691,7 @@ void simplePredicate5LikePredicateWithEscape() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, '___abcdef'), (43, 15, 'world')"); @@ -711,7 +710,7 @@ void simplePredicate6IsNull() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -730,7 +729,7 @@ void simplePredicate6IsNotNull() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -754,7 +753,7 @@ void simplePredicate7ExistsPredicate() throws Exception { " create table b(idb integer, q integer, r integer, primary key(idb))\n" + " create index ib as select q from b\n" + " create index ir as select sq.f from r, (select f from r.nr) sq;"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into A values (1, 1), (2, 2), (3, 3)"); @@ -843,7 +842,7 @@ void simpleInsert1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -869,7 +868,7 @@ void visitSelectWithLimit() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -897,7 +896,7 @@ void updateStatement1() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -920,7 +919,7 @@ void updateStatement3SetQualifiedField() throws Exception { final String schemaTemplate = "CREATE TABLE T1(pk bigint, i bigint, b string, PRIMARY KEY(pk))" + "create index i1 as select i, b from t1 order by i, b"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, 13, null), (43, 15, 'world')"); @@ -943,7 +942,7 @@ void updateStatement4SetNestedField() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT Location (longitude bigint, latitude bigint) " + "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); @@ -971,7 +970,7 @@ void testNestedValues() throws Exception { " CREATE TYPE AS STRUCT B (c C)" + " CREATE TYPE AS STRUCT A (b B) " + "CREATE TABLE T1(pk bigint, a A, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (((((34, 35))))))"); @@ -997,7 +996,7 @@ void deleteStatement1() throws Exception { final String schemaTemplate = "CREATE TYPE AS STRUCT Location (longitude bigint, latitude bigint) " + "CREATE TYPE AS STRUCT Info (ratings bigint, loc Location) " + "CREATE TABLE T1(pk bigint, i Info, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { try (var statement = ddl.setSchemaAndGetConnection().createStatement()) { final var relationalStatement = (EmbeddedRelationalStatement) statement; relationalStatement.executeInternal("insert into t1 values (42, (30, (5000, 5500)), 500, 101), (43, (34, (5001, 5501)), 501, 102)"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ConstraintValidityTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ConstraintValidityTests.java index 983db41099..9635964e51 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ConstraintValidityTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ConstraintValidityTests.java @@ -75,11 +75,11 @@ public class ConstraintValidityTests { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(ConstraintValidityTests.class, TestSchemas.score()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, ConstraintValidityTests.class, TestSchemas.score()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @Nonnull diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/RelationalPlanCacheTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/RelationalPlanCacheTests.java index cb843ca8a2..7492324ba8 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/RelationalPlanCacheTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/RelationalPlanCacheTests.java @@ -94,11 +94,11 @@ public class RelationalPlanCacheTests { @RegisterExtension @Order(2) - public final SimpleDatabaseRule database = new SimpleDatabaseRule(RelationalPlanCacheTests.class, TestSchemas.books()); + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, RelationalPlanCacheTests.class, TestSchemas.books()); @RegisterExtension @Order(3) - public final RelationalConnectionRule connection = new RelationalConnectionRule(database::getConnectionUri) + public final RelationalConnectionRule connection = new RelationalConnectionRule(relationalExtension, database::getConnectionUri) .withSchema("TEST_SCHEMA"); @Nonnull diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java index d4d1495524..471be6cdb4 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/cache/ValueSpecificConstraintTests.java @@ -37,12 +37,19 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Isolated; import javax.annotation.Nonnull; -import java.net.URI; import java.util.Map; import java.util.TreeMap; +// Marked @Isolated because this test asserts on captured log messages from the JVM-global +// PlanGenerator logger via LogAppenderRule. The appender catches events from any test +// running concurrently against the same logger, and a thread-id filter is not safe +// because the relational engine dispatches work onto async pools (FDB callbacks, +// CompletableFuture stages, etc.). @Isolated tells JUnit to suspend all other tests +// while this class runs, so the captured events are guaranteed to be ours. +@Isolated public class ValueSpecificConstraintTests { @RegisterExtension @@ -94,7 +101,7 @@ private void preparedQueryShouldHitCache(@Nonnull final RelationalConnection con @Test void constrainingLiteralTrueBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(ddl.getConnection(), "select a + 42 from t where b = true"); queryShouldHitCache(ddl.getConnection(), "select a + 42 from t where b = true"); @@ -112,7 +119,7 @@ void constrainingLiteralTrueBooleanWorks() throws Exception { @Test void constrainingPreparedTrueBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); preparedQueryShouldMissCache(connection, "select a + 42 from t where b = ?", ImmutableMap.of(1, true)); preparedQueryShouldHitCache(connection, "select a + 42 from t where b = ?", ImmutableMap.of(1, true)); @@ -132,7 +139,7 @@ void constrainingPreparedTrueBooleanWorks() throws Exception { @Test void constrainingLiteralFalseBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(connection, "select a + 42 from t where b = false"); queryShouldHitCache(connection, "select a + 42 from t where b = false"); @@ -151,7 +158,7 @@ void constrainingLiteralFalseBooleanWorks() throws Exception { @Test void constrainingPreparedFalseBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); preparedQueryShouldMissCache(connection, "select a + 42 from t where b = ?", ImmutableMap.of(1, false)); preparedQueryShouldHitCache(connection, "select a + 42 from t where b = ?", ImmutableMap.of(1, false)); @@ -171,7 +178,7 @@ void constrainingPreparedFalseBooleanWorks() throws Exception { @Test void constrainingLiteralNullBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(connection, "select a + 42 from t where b = null"); queryShouldHitCache(connection, "select a + 42 from t where b = null"); @@ -189,7 +196,7 @@ void constrainingLiteralNullBooleanWorks() throws Exception { @Test void constrainingPreparedNullBooleanWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); final Map map = new TreeMap<>(); map.put(1, null); @@ -209,7 +216,7 @@ void constrainingPreparedNullBooleanWorks() throws Exception { @Test void constrainingNonNullLiteralWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(connection, "select a + 43 from t where a = 42"); queryShouldHitCache(connection, "select a + 43 from t where a = 45"); // different literals are ok here (no index filters) @@ -223,7 +230,7 @@ void constrainingNonNullLiteralWorks() throws Exception { @Test void constrainingPreparedNotNullWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); preparedQueryShouldMissCache(connection, "select a + 43 from t where a = ?", ImmutableMap.of(1, 42)); preparedQueryShouldHitCache(connection, "select a + 43 from t where a = ?", ImmutableMap.of(1, 45)); // different literals are ok here (no index filters) @@ -239,7 +246,7 @@ void constrainingPreparedNotNullWorks() throws Exception { @Test void constrainingNullLiteralWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); queryShouldMissCache(connection, "select a + 43 from t where a = null"); @@ -254,7 +261,7 @@ void constrainingNullLiteralWorks() throws Exception { @Test void constrainingPreparedNullWorks() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); final Map map = new TreeMap<>(); map.put(1, null); @@ -270,7 +277,7 @@ void constrainingPreparedNullWorks() throws Exception { @Test void sameQueryDifferentRewriteRulesEnablement() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); final Map map = new TreeMap<>(); map.put(1, null); @@ -305,7 +312,7 @@ void hnswQueriesWithDifferentRuntimeOptionsAreCachedCorrectly() throws Exception final var queryVector = new HalfRealVector(new double[] {1.2f, -0.4f, 3.14f}); - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension) + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension) .schemaTemplate(schemaTemplate).schemaTemplateOptions((new SchemaTemplateRule.SchemaTemplateOptions(true, true))).build()) { final var connection = ddl.setSchemaAndGetConnection(); @@ -344,7 +351,7 @@ void hnswQueriesWithDifferentRuntimeOptionsAreCachedCorrectly() throws Exception @Test void sameQueryDifferentRightDeepJoinOption() throws Exception { final String schemaTemplate = "create table t(pk bigint, a bigint, b boolean, primary key(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplate).build()) { final var connection = ddl.setSchemaAndGetConnection(); final Map map = new TreeMap<>(); map.put(1, null); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/functions/IncarnationTest.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/functions/IncarnationTest.java index 1ecf643405..e3388557c9 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/functions/IncarnationTest.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/query/functions/IncarnationTest.java @@ -57,7 +57,7 @@ public class IncarnationTest { @RegisterExtension @Order(1) - public final SimpleDatabaseRule database = new SimpleDatabaseRule( + public final SimpleDatabaseRule database = new SimpleDatabaseRule(relationalExtension, IncarnationTest.class, "CREATE TABLE my_record (key string, incarnation integer, data string, PRIMARY KEY(key))"); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java index 028131a31c..d7a866ef0b 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/SqlVisitorTests.java @@ -37,7 +37,6 @@ import org.junit.jupiter.api.extension.RegisterExtension; import javax.annotation.Nonnull; -import java.net.URI; import java.util.List; import java.util.Map; import java.util.Optional; @@ -56,7 +55,7 @@ public SqlVisitorTests() { @Test public void addExtraExpressionsToSetAndWhereClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -77,7 +76,7 @@ public void addExtraExpressionsToSetAndWhereClause() throws Exception { @Test public void addReturning() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -91,7 +90,7 @@ public void addReturning() throws Exception { @Test public void addNestedWhereClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -105,7 +104,7 @@ public void addNestedWhereClause() throws Exception { @Test public void addQueryOptions() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); updateBuilder.withOption(StructuredQuery.QueryOptions.DRY_RUN); @@ -119,7 +118,7 @@ public void addQueryOptions() throws Exception { @Test public void rewriteColumnAliases() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set customer_facing_a = 42 where CUSTOMER_FACING_PK = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement, Map.of("CUSTOMER_FACING_A", List.of("a"), "CUSTOMER_FACING_PK", List.of("pk"))); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -133,7 +132,7 @@ public void rewriteColumnAliases() throws Exception { @Test public void rewriteColumnAliasesQuotes() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var connection = ddl.setSchemaAndGetConnection(); connection.setOption(Options.Name.CASE_SENSITIVE_IDENTIFIERS, true); final var updateStatement = "update T1 set \"customer_facing_a\" = 42 where \"CUSTOMER_FACING_PK\" = 444"; @@ -149,7 +148,7 @@ public void rewriteColumnAliasesQuotes() throws Exception { @Test public void rewriteColumnAliasesInsideUdf() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set customer_facing_a = 42 where CUSTOMER_FACING_PK = greatest(42, customer_FACING_B)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement, Map.of("CUSTOMER_FACING_A", List.of("a"), diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java index 7aeb6fb5ef..0e574b016e 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/recordlayer/structuredsql/StatementBuilderTests.java @@ -35,7 +35,6 @@ import org.junit.jupiter.params.provider.MethodSource; import javax.annotation.Nonnull; -import java.net.URI; import java.util.List; import java.util.Set; import java.util.stream.Stream; @@ -56,7 +55,7 @@ public StatementBuilderTests() { @Test void addExtraSetClauseToUpdate() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -70,7 +69,7 @@ void addExtraSetClauseToUpdate() throws Exception { @Test void setSameFieldMultipleTimes() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -86,7 +85,7 @@ void setSameFieldMultipleTimes() throws Exception { @Test void removeSetFieldClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, b = 44 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -100,7 +99,7 @@ void removeSetFieldClause() throws Exception { @Test void removeAllSetFieldClausesThrows() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c bigint, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set b = 44 where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -114,7 +113,7 @@ void removeAllSetFieldClausesThrows() throws Exception { @Test void examineSetFields() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); var setFields = updateBuilder.getSetClauses().keySet(); @@ -133,7 +132,7 @@ void examineSetFields() throws Exception { @Test void examineWhereClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); var whereClause = updateBuilder.getWhereClause(); @@ -147,7 +146,7 @@ void examineWhereClause() throws Exception { @Test void examineMultipleWhereClauses() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444 AND (a < 42)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); var whereClause = updateBuilder.getWhereClause(); @@ -176,7 +175,7 @@ static Stream queryOptionsParameters() { @MethodSource("queryOptionsParameters") void examineQueryOptions(@Nonnull final String queryOptionsClause, @Nonnull final Set expectedOptions) throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444 AND (a < 42) " + queryOptionsClause; var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); assertThat(updateBuilder.getOptions()) @@ -187,7 +186,7 @@ void examineQueryOptions(@Nonnull final String queryOptionsClause, @Nonnull fina @Test void addWhereClauses() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' where pk = 444 AND (a < 42)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -204,7 +203,7 @@ void addWhereClauses() throws Exception { @Test void examineReturning() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' returning *"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var returning = updateBuilder.getReturning(); @@ -218,7 +217,7 @@ void examineReturning() throws Exception { @Test void examineReturningMultipleColumns() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' returning \"old\".a, b, *, c+1, d + (5 + 4)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var returning = updateBuilder.getReturning(); @@ -240,7 +239,7 @@ void examineReturningMultipleColumns() throws Exception { @Test void setReturningClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42, c = 'bla' returning \"old\".a, b, *, c+1, d + (5 + 4)"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); var returning = updateBuilder.getReturning(); @@ -271,7 +270,7 @@ void setReturningClause() throws Exception { @Test void greatestFunction() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); @@ -296,7 +295,7 @@ void greatestFunction() throws Exception { @Test void mathInSetClause() throws Exception { final String schemaTemplateString = "CREATE TABLE T1(pk bigint, a bigint, b bigint, c string, PRIMARY KEY(pk))"; - try (var ddl = Ddl.builder().database(URI.create("/TEST/QT")).relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { + try (var ddl = Ddl.builder().database().relationalExtension(relationalExtension).schemaTemplate(schemaTemplateString).build()) { final var updateStatement = "update T1 set a = 42"; final var updateBuilder = ddl.setSchemaAndGetConnection().createStatementBuilderFactory().updateStatementBuilder(updateStatement); final var ef = ddl.getConnection().createExpressionBuilderFactory(); diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java index dd4a2213c8..6b638e3db6 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/ConnectionUtils.java @@ -28,7 +28,6 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.URI; -import java.sql.DriverManager; import java.sql.SQLException; /** Utility for interacting with easily connections/statements. */ @@ -38,21 +37,36 @@ public final class ConnectionUtils { @Nonnull private final SqlFunction getConnection; - public ConnectionUtils() { - getConnection = databaseName -> DriverManager.getConnection("jdbc:embed:" + databaseName).unwrap(RelationalConnection.class); - } - public ConnectionUtils(@Nonnull RelationalDriver driver) { getConnection = databaseName -> driver.connect(URI.create("jdbc:embed:" + databaseName)).unwrap(RelationalConnection.class); } public void runAgainstCatalog(@Nonnull final SQLConsumer action) throws SQLException, RelationalException { - runAgainstConnection(SYS_DATABASE, CATALOG_SCHEMA, action); + // Catalog operations go through the JVM-wide catalog lock + retry. See {@link CatalogOperations}. + CatalogOperations.runLockedWithRetry(() -> { + try { + runAgainstConnection(SYS_DATABASE, CATALOG_SCHEMA, action); + } catch (RelationalException e) { + // CatalogOperations.runLockedWithRetry only accepts SQLException; surface this as one. + throw e.toSqlException(); + } + }); } @Nullable public R getFromCatalog(@Nonnull final SQLFunction action) throws SQLException, RelationalException { - return getFromConnection(SYS_DATABASE, CATALOG_SCHEMA, action); + // Read-only catalog access — wrap in the catalog lock anyway since concurrent reads alongside + // sibling writes can race on the FDB read-version protocol under load. + @SuppressWarnings("unchecked") + final R[] result = (R[]) new Object[1]; + CatalogOperations.runLockedWithRetry(() -> { + try { + result[0] = getFromConnection(SYS_DATABASE, CATALOG_SCHEMA, action); + } catch (RelationalException e) { + throw e.toSqlException(); + } + }); + return result[0]; } @Nullable @@ -75,7 +89,16 @@ public void runAgainstConnection(@Nonnull final String databaseName, } public void runCatalogStatement(@Nonnull final SQLConsumer action) throws SQLException, RelationalException { - runStatement(SYS_DATABASE, CATALOG_SCHEMA, action); + // Catalog DDL — wrap in {@link CatalogOperations#runLockedWithRetry} so all CREATE/DROP + // DATABASE/SCHEMA TEMPLATE/SCHEMA operations across the test suite are serialised on a + // JVM-wide lock and transparently retried on transient races (SQLSTATE 40001, etc.). + CatalogOperations.runLockedWithRetry(() -> { + try { + runStatement(SYS_DATABASE, CATALOG_SCHEMA, action); + } catch (RelationalException e) { + throw e.toSqlException(); + } + }); } public void runStatement(@Nonnull final String databaseName, diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java index 3a01005605..d26ffaa580 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/DatabaseRule.java @@ -21,7 +21,7 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.recordlayer.Utils; +import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -30,20 +30,24 @@ import javax.annotation.Nonnull; import java.net.URI; -import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; -import java.sql.Statement; +import java.util.Objects; public class DatabaseRule implements BeforeEachCallback, BeforeAllCallback, AfterEachCallback, AfterAllCallback { + @Nonnull + private final RelationalExtension extension; + @Nonnull private final URI databasePath; @Nonnull private final Options options; - public DatabaseRule(@Nonnull final URI databasePath, @Nonnull final Options options) { + public DatabaseRule(@Nonnull final RelationalExtension extension, + @Nonnull final URI databasePath, + @Nonnull final Options options) { + this.extension = extension; this.databasePath = databasePath; this.options = options; } @@ -69,24 +73,18 @@ public void beforeEach(ExtensionContext context) throws SQLException { } private void setup() throws SQLException { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - Utils.setConnectionOptions(connection, options); - connection.setSchema("CATALOG"); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); - statement.executeUpdate("CREATE DATABASE \"" + databasePath.getPath() + "\""); - } - } + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + options, + "DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\"", + "CREATE DATABASE \"" + databasePath.getPath() + "\""); } private void tearDown() throws SQLException { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - Utils.setConnectionOptions(connection, options); - connection.setSchema("CATALOG"); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP DATABASE \"" + databasePath.getPath() + "\""); - } - } + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + options, + "DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); } @Nonnull diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java index 9578aeb4c9..5eced2d78d 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/Ddl.java @@ -22,6 +22,7 @@ import com.apple.foundationdb.relational.api.Options; import com.apple.foundationdb.relational.api.RelationalConnection; +import com.apple.foundationdb.relational.api.RelationalDriver; import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import com.apple.foundationdb.relational.recordlayer.Utils; import com.apple.foundationdb.relational.util.Assert; @@ -32,8 +33,9 @@ import javax.annotation.Nullable; import java.net.URI; import java.net.URISyntaxException; -import java.sql.DriverManager; import java.sql.SQLException; +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; public class Ddl implements AutoCloseable { @Nonnull @@ -59,9 +61,9 @@ public Ddl(@Nonnull final RelationalExtension relationalExtension, final String templateName = dbPath.getPath().substring(dbPath.getPath().lastIndexOf("/") + 1); this.relationalExtension = relationalExtension; - this.templateRule = new SchemaTemplateRule(templateName + "_TEMPLATE", options, schemaTemplateOptions, templateDefinition); - this.databaseRule = new DatabaseRule(dbPath, options); - this.schemaRule = new SchemaRule(schemaName, dbPath, templateRule.getSchemaTemplateName(), options); + this.templateRule = new SchemaTemplateRule(relationalExtension, templateName + "_TEMPLATE", options, schemaTemplateOptions, templateDefinition); + this.databaseRule = new DatabaseRule(relationalExtension, dbPath, options); + this.schemaRule = new SchemaRule(relationalExtension, schemaName, dbPath, templateRule.getSchemaTemplateName(), options); this.extensionContext = extensionContext; try { @@ -95,7 +97,10 @@ public Ddl(@Nonnull final RelationalExtension relationalExtension, throw e; } - this.connection = DriverManager.getConnection("jdbc:embed://" + databaseRule.getDbUri().toString()).unwrap(RelationalConnection.class); + final RelationalDriver driver = Objects.requireNonNull(relationalExtension.getDriver(), + "RelationalExtension has no active driver — its @BeforeEach must have run first."); + this.connection = driver + .connect(URI.create("jdbc:embed://" + databaseRule.getDbUri().toString())).unwrap(RelationalConnection.class); Utils.setConnectionOptions(connection, options); } @@ -156,6 +161,24 @@ public Builder database(@Nonnull final URI dbName) throws URISyntaxException { return this; } + /** + * Sets the database path to a freshly-generated unique value. Use this when the test + * doesn't care about the specific database name and just needs an isolated database for + * its scope. Under parallel JUnit class execution, tests that hard-code the same path + * (e.g. {@code /TEST/QT}) trip over each other's catalog state; this overload sidesteps + * that by giving every {@link Ddl} instance a unique path. + */ + @Nonnull + public Builder database() { + // 16 random hex chars = 64 bits of entropy — enough to avoid collisions across a + // single test run and short enough to keep error messages readable. We deliberately + // avoid a UUID to keep the path SQL-identifier-safe and human-scannable. + final String uniqueSuffix = Long.toHexString(ThreadLocalRandom.current().nextLong()) + + Long.toHexString(ThreadLocalRandom.current().nextLong()); + database = URI.create("/TEST/AUTO_" + uniqueSuffix); + return this; + } + @Nonnull public Builder schemaTemplate(@Nonnull final String schemaTemplate) { this.templateDefinition = schemaTemplate; diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java index 126751cc59..63631e0d0f 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaRule.java @@ -22,20 +22,21 @@ import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.recordlayer.Utils; +import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import javax.annotation.Nonnull; import java.net.URI; -import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; -import java.sql.Statement; +import java.util.Objects; public class SchemaRule implements BeforeEachCallback, AfterEachCallback { + @Nonnull + private final RelationalExtension extension; + @Nonnull private final String schemaName; @@ -48,8 +49,12 @@ public class SchemaRule implements BeforeEachCallback, AfterEachCallback { @Nonnull private final Options connectionOptions; - public SchemaRule(@Nonnull final String schemaName, @Nonnull final URI dbUri, @Nonnull final String templateName, + public SchemaRule(@Nonnull final RelationalExtension extension, + @Nonnull final String schemaName, + @Nonnull final URI dbUri, + @Nonnull final String templateName, @Nonnull final Options connectionOptions) { + this.extension = extension; this.schemaName = schemaName; this.dbUri = dbUri; this.templateName = templateName; @@ -71,23 +76,17 @@ public String getSchemaName() { return schemaName; } - private void setup() throws Exception { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("CREATE SCHEMA \"" + dbUri.getPath() + "/" + schemaName + "\" WITH TEMPLATE \"" + templateName + "\""); - } - } + private void setup() throws SQLException { + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + connectionOptions, + "CREATE SCHEMA \"" + dbUri.getPath() + "/" + schemaName + "\" WITH TEMPLATE \"" + templateName + "\""); } private void tearDown() throws SQLException { - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP SCHEMA \"" + dbUri.getPath() + "/" + schemaName + "\""); - } - } + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + connectionOptions, + "DROP SCHEMA IF EXISTS \"" + dbUri.getPath() + "/" + schemaName + "\""); } } diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java index c8486558d2..fefbd76e83 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SchemaTemplateRule.java @@ -21,26 +21,31 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; -import com.apple.foundationdb.relational.recordlayer.Utils; +import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; -import java.sql.Statement; import java.util.Collection; import java.util.Locale; +import java.util.Objects; import java.util.stream.Collectors; /** * Manages the lifecycle of a single SchemaTemplate within a unit test. + *

+ * Holds a reference to the surrounding {@link RelationalExtension} so it can issue catalog DDL + * through that extension's driver — avoiding the global {@link java.sql.DriverManager} state and + * the parallel-test race that used to come with it. */ public class SchemaTemplateRule implements BeforeEachCallback, AfterEachCallback { + @Nonnull + private final RelationalExtension extension; + @Nonnull private final String templateName; @@ -56,11 +61,13 @@ public class SchemaTemplateRule implements BeforeEachCallback, AfterEachCallback @Nonnull private final TypeCreator tableCreator; - private SchemaTemplateRule(@Nonnull final String templateName, + private SchemaTemplateRule(@Nonnull final RelationalExtension extension, + @Nonnull final String templateName, @Nonnull final Options connectionOptions, @Nullable final SchemaTemplateOptions schemaTemplateOptions, @Nonnull final TypeCreator typeCreator, @Nonnull final TypeCreator tableCreator) { + this.extension = extension; this.templateName = templateName; this.connectionOptions = connectionOptions; this.schemaTemplateOptions = schemaTemplateOptions; @@ -68,21 +75,23 @@ private SchemaTemplateRule(@Nonnull final String templateName, this.tableCreator = tableCreator; } - public SchemaTemplateRule(@Nonnull final String templateName, + public SchemaTemplateRule(@Nonnull final RelationalExtension extension, + @Nonnull final String templateName, @Nonnull final Options connectionOptions, @Nullable final SchemaTemplateOptions schemaTemplateOptions, @Nonnull final Collection tables, @Nonnull final Collection types) { - this(templateName, connectionOptions, schemaTemplateOptions, + this(extension, templateName, connectionOptions, schemaTemplateOptions, new CreatorFromDefinition("TYPE AS STRUCT", types), new CreatorFromDefinition("TABLE", tables)); } - public SchemaTemplateRule(@Nonnull final String templateName, + public SchemaTemplateRule(@Nonnull final RelationalExtension extension, + @Nonnull final String templateName, @Nonnull final Options connectionOptions, @Nullable final SchemaTemplateOptions schemaTemplateOptions, @Nonnull final String templateDefinition) { - this(templateName, connectionOptions, schemaTemplateOptions, + this(extension, templateName, connectionOptions, schemaTemplateOptions, new CreatorFromString(templateDefinition), () -> ""); } @@ -93,28 +102,14 @@ public String getSchemaTemplateName() { @Override public void afterEach(ExtensionContext context) throws SQLException { - final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE \"").append(templateName).append("\""); - - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(dropStatement.toString()); - } - } + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + connectionOptions, + "DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); } @Override public void beforeEach(ExtensionContext context) throws SQLException { - final StringBuilder dropStatement = new StringBuilder("DROP SCHEMA TEMPLATE IF EXISTS\"").append(templateName).append("\""); - - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(dropStatement.toString()); - } - } final StringBuilder createStatement = new StringBuilder("CREATE SCHEMA TEMPLATE \"").append(templateName).append("\" "); createStatement.append(typeCreator.getTypeDefinition()); createStatement.append(tableCreator.getTypeDefinition()); @@ -123,13 +118,11 @@ public void beforeEach(ExtensionContext context) throws SQLException { createStatement.append(schemaTemplateOptions.getOptionsString()); } - try (Connection connection = DriverManager.getConnection("jdbc:embed:/__SYS")) { - connection.setSchema("CATALOG"); - Utils.setConnectionOptions(connection, connectionOptions); - try (Statement statement = connection.createStatement()) { - statement.executeUpdate(createStatement.toString()); - } - } + CatalogOperations.runDdl( + Objects.requireNonNull(extension.getDriver(), "extension has no active driver"), + connectionOptions, + "DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\"", + createStatement.toString()); } public static final class SchemaTemplateOptions { diff --git a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java index 60b7310f1b..7ef64c1feb 100644 --- a/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java +++ b/fdb-relational-core/src/test/java/com/apple/foundationdb/relational/utils/SimpleDatabaseRule.java @@ -21,6 +21,7 @@ package com.apple.foundationdb.relational.utils; import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.recordlayer.RelationalExtension; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; @@ -29,6 +30,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.URI; +import java.util.concurrent.ThreadLocalRandom; /** * A JUnit extension that automatically creates all the framework necessary for a unique database and schema. @@ -50,34 +52,45 @@ public class SimpleDatabaseRule implements BeforeEachCallback, AfterEachCallback @Nonnull private final SchemaRule schemaRule; - public SimpleDatabaseRule(@Nonnull Class testClass, + public SimpleDatabaseRule(@Nonnull RelationalExtension extension, + @Nonnull Class testClass, @Nonnull String templateDefinition, @Nonnull Options connectionOptions, @Nullable SchemaTemplateRule.SchemaTemplateOptions templateOptions) { final var schemaName = "TEST_SCHEMA"; - final var dbPath = URI.create("/TEST/" + testClass.getSimpleName()); + // Per-instance unique suffix so that two instances of this rule (whether from + // concurrent test classes, or from a fresh run after a prior run crashed without + // cleaning up) never collide on the same database path. The test class name is kept as + // a prefix to make path strings in error messages and FDB tracing self-identifying. + // 16 random hex chars = 64 bits of entropy — enough to avoid collisions across any + // realistic test run and short enough to keep names scannable. + final var uniqueSuffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + final var dbPath = URI.create("/TEST/" + testClass.getSimpleName() + "_" + uniqueSuffix); final var templateName = dbPath.getPath().substring(dbPath.getPath().lastIndexOf("/") + 1); - this.templateRule = new SchemaTemplateRule(templateName + "_TEMPLATE", Options.none(), templateOptions, templateDefinition); - this.databaseRule = new DatabaseRule(dbPath, connectionOptions); - this.schemaRule = new SchemaRule(schemaName, dbPath, templateRule.getSchemaTemplateName(), connectionOptions); + this.templateRule = new SchemaTemplateRule(extension, templateName + "_TEMPLATE", Options.none(), templateOptions, templateDefinition); + this.databaseRule = new DatabaseRule(extension, dbPath, connectionOptions); + this.schemaRule = new SchemaRule(extension, schemaName, dbPath, templateRule.getSchemaTemplateName(), connectionOptions); } - public SimpleDatabaseRule(@Nonnull final Class testClass, + public SimpleDatabaseRule(@Nonnull final RelationalExtension extension, + @Nonnull final Class testClass, @Nonnull final String templateDefinition, @Nonnull final Options options) { - this(testClass, templateDefinition, options, null); + this(extension, testClass, templateDefinition, options, null); } - public SimpleDatabaseRule(@Nonnull final Class testClass, + public SimpleDatabaseRule(@Nonnull final RelationalExtension extension, + @Nonnull final Class testClass, @Nonnull final String templateDefinition, @Nullable SchemaTemplateRule.SchemaTemplateOptions templateOptions) { - this(testClass, templateDefinition, Options.none(), templateOptions); + this(extension, testClass, templateDefinition, Options.none(), templateOptions); } - public SimpleDatabaseRule(@Nonnull final Class testClass, + public SimpleDatabaseRule(@Nonnull final RelationalExtension extension, + @Nonnull final Class testClass, @Nonnull final String templateDefinition) { - this(testClass, templateDefinition, Options.none(), null); + this(extension, testClass, templateDefinition, Options.none(), null); } @Override diff --git a/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java new file mode 100644 index 0000000000..c8c3fdaf00 --- /dev/null +++ b/fdb-relational-core/src/testFixtures/java/com/apple/foundationdb/relational/utils/CatalogOperations.java @@ -0,0 +1,208 @@ +/* + * CatalogOperations.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2021-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.relational.utils; + +import com.apple.foundationdb.relational.api.Options; +import com.apple.foundationdb.relational.api.RelationalConnection; +import com.apple.foundationdb.relational.api.RelationalDriver; +import com.apple.foundationdb.relational.api.exceptions.RelationalException; + +import javax.annotation.Nonnull; +import java.net.URI; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * Test-only helpers for running catalog-mutating DDL (CREATE/DROP DATABASE, CREATE/DROP SCHEMA + * TEMPLATE, CREATE/DROP SCHEMA) against {@code /__SYS/CATALOG}. + *

+ * Historically this class also owned a JVM-wide monitor + retry loop that serialised catalog + * DDL across tests to work around two contention sources: + *

    + *
  1. Concurrent FRL construction racing on the catalog-init commit (now removed — the + * proactive initializer in {@code YamlTestCatalogInitializer} covers that once per test + * class before any FRL is constructed).
  2. + *
  3. Every {@code FDBRecordStore.deleteStore} bumping the shared meta-data-version stamp, + * which then conflicted with every concurrent catalog read (now conditional on the + * deleted store being cacheable, which the SYS catalog is but transient user stores + * are not — so most concurrent DDL no longer contends).
  4. + *
+ * With both root causes addressed, the runners here execute their action directly. The + * method names are kept ({@link #runLockedWithRetry}, {@link #runLockedWithRelationalRetry}) + * so existing test call sites don't need mechanical updates. + */ +public final class CatalogOperations { + + /** + * URI of the system catalog database every {@link #runDdl} invocation connects against + * when going through a {@link RelationalDriver}. The {@code jdbc:embed:} scheme is only + * meaningful for {@link com.apple.foundationdb.relational.api.EmbeddedRelationalDriver}. + */ + private static final URI SYS_CATALOG_URI = URI.create("jdbc:embed:/__SYS"); + + private CatalogOperations() { + } + + /** + * Runs one or more catalog-mutating DDL statements. Each call opens a fresh connection to + * {@code /__SYS} via the given driver, sets the schema to {@code CATALOG}, applies + * {@code connectionOptions}, and runs every statement in order. + * + * @param driver the driver to use for the {@code /__SYS} connection + * @param connectionOptions options to apply to the connection before running the statements + * @param statements DDL statements to run in order + * @throws SQLException if the operation fails + */ + public static void runDdl(@Nonnull final RelationalDriver driver, + @Nonnull final Options connectionOptions, + @Nonnull final String... statements) throws SQLException { + try (Connection connection = driver.connect(SYS_CATALOG_URI)) { + connection.setSchema("CATALOG"); + applyConnectionOptions(connection, connectionOptions); + try (Statement statement = connection.createStatement()) { + for (final String ddl : statements) { + statement.executeUpdate(ddl); + } + } + } + } + + /** + * Convenience overload of {@link #runDdl(RelationalDriver, Options, String...)} with + * {@link Options#NONE}. + * + * @param driver the driver to use for the {@code /__SYS} connection + * @param statements DDL statements to run in order + * @throws SQLException if the operation fails + */ + public static void runDdl(@Nonnull final RelationalDriver driver, + @Nonnull final String... statements) throws SQLException { + runDdl(driver, Options.NONE, statements); + } + + /** + * Runs {@code action} on an open connection to {@code /__SYS/CATALOG}. Use this for tests + * that interleave DDL and queries against the catalog and need to share a single connection + * across both. + * + * @param driver the driver to use for the {@code /__SYS} connection + * @param action the body to run against the open catalog connection + * @throws SQLException if the action fails + */ + public static void runOnCatalog(@Nonnull final RelationalDriver driver, + @Nonnull final ThrowingConnectionConsumer action) throws SQLException { + try (Connection connection = driver.connect(SYS_CATALOG_URI)) { + connection.setSchema("CATALOG"); + action.accept(connection); + } + } + + /** + * Applies each entry of {@code options} to {@code connection} via + * {@link RelationalConnection#setOption(Options.Name, Object)}. Inlined here (rather than + * imported from a test-source utility) so that this class can live in {@code testFixtures} + * and be consumed from downstream modules. + * + * @param connection the connection to configure + * @param options options to set (may be {@link Options#NONE} for a no-op) + * @throws SQLException if any {@code setOption} call fails + */ + private static void applyConnectionOptions(@Nonnull final Connection connection, + @Nonnull final Options options) throws SQLException { + final RelationalConnection relational = connection.unwrap(RelationalConnection.class); + for (final var entry : options.entries()) { + relational.setOption(entry.getKey(), entry.getValue()); + } + } + + /** + * Functional interface for actions that may throw {@link SQLException}, since most + * catalog-mutating DDL runs through JDBC. + */ + @FunctionalInterface + public interface ThrowingRunnable { + /** + * Runs the catalog action. + * + * @throws SQLException if the action fails + */ + void run() throws SQLException; + } + + /** + * Functional interface for catalog actions that need access to the open + * {@link Connection}. Used by {@link #runOnCatalog} for tests that interleave DDL with + * queries against the catalog. + */ + @FunctionalInterface + public interface ThrowingConnectionConsumer { + /** + * Runs the catalog action against the given connection. + * + * @param connection the open connection to {@code /__SYS/CATALOG} + * @throws SQLException if the action fails + */ + void accept(@Nonnull Connection connection) throws SQLException; + } + + /** + * Functional interface for catalog actions that may throw {@link RelationalException} + * (used by extensions that talk to the catalog via the lower-level Transaction API rather + * than JDBC, e.g. {@code EmbeddedRelationalExtension.makeDatabase}). + */ + @FunctionalInterface + public interface RelationalThrowingRunnable { + /** + * Runs the catalog action. + * + * @throws RelationalException if the action fails + */ + void run() throws RelationalException; + } + + /** + * Runs {@code action}. Historically this method wrapped the action in a JVM-wide + * monitor + retry loop; that infrastructure has been removed now that the underlying + * contention (catalog-init commits, meta-data-version-stamp bumps on non-cacheable + * {@code deleteStore}) has been fixed upstream. The method is preserved so existing test + * call sites keep compiling; new call sites can skip the wrapper and call their action + * directly. + * + * @param action the catalog DDL to run + * @throws SQLException if the action fails + */ + public static void runLockedWithRetry(@Nonnull final ThrowingRunnable action) throws SQLException { + action.run(); + } + + /** + * {@link RelationalException}-throwing variant of {@link #runLockedWithRetry(ThrowingRunnable)}. + * As with that method, no JVM-wide lock or retry is applied any more; the wrapper is kept + * only to avoid mass-editing every existing call site. + * + * @param action the catalog operation to run + * @throws RelationalException if the action fails + */ + public static void runLockedWithRelationalRetry(@Nonnull final RelationalThrowingRunnable action) throws RelationalException { + action.run(); + } +} diff --git a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCAutoCommitTest.java b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCAutoCommitTest.java index 79a153e8b4..3b9ba330fe 100644 --- a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCAutoCommitTest.java +++ b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCAutoCommitTest.java @@ -25,6 +25,7 @@ import com.apple.foundationdb.relational.api.RelationalStatement; import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; +import com.apple.foundationdb.relational.utils.CatalogOperations; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -35,6 +36,7 @@ import java.io.IOException; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.concurrent.ThreadLocalRandom; /** * Run tests with AutoCommit=OFF against a remote Relational DB. @@ -42,9 +44,24 @@ */ public class JDBCAutoCommitTest { private static final String SYSDBPATH = "/" + RelationalKeyspaceProvider.SYS; - private static final String TESTDB = "/FRL/jdbc_test_db"; public static final String TEST_SCHEMA = "test_schema"; + // Each test instance gets its own database + schema-template names so that parallel + // test runs (both within this module and across modules that share the FDB cluster) + // can't collide on the catalog. Catalog DDL that sets the database up runs through + // {@link CatalogOperations}, which serialises and retries against SQLSTATE 40001 + // conflicts JVM-wide (see its javadoc for the contract). + private final String testDb; + private final String schemaTemplate; + + public JDBCAutoCommitTest() { + // 16 random hex chars = 64 bits of entropy — plenty to avoid collisions in any + // realistic test run. Prefix stays "jdbc_test_db_" for grep-ability in FDB traces. + final String suffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + this.testDb = "/FRL/jdbc_test_db_" + suffix; + this.schemaTemplate = "test_template_" + suffix; + } + @BeforeAll public static void beforeAll() throws Exception { // Load driver. @@ -399,7 +416,7 @@ protected String getSysDbUri() { } protected String getTestDbUri() { - return "jdbc:relational://" + getHostPort() + TESTDB + "?schema=" + TEST_SCHEMA; + return "jdbc:relational://" + getHostPort() + testDb + "?schema=" + TEST_SCHEMA; } protected String getHostPort() { @@ -428,28 +445,40 @@ private static void insert2ndRow(final RelationalStatement statement) throws SQL Assertions.assertEquals(1, res); } + /** + * Wraps the catalog-mutating DDL in {@link CatalogOperations#runOnCatalog} so that this + * test's {@code CREATE DATABASE}/{@code CREATE SCHEMA TEMPLATE} commits serialise against + * other test classes' catalog work on the same JVM and retry on SQLSTATE 40001. The unique + * {@link #testDb}/{@link #schemaTemplate} names generated per test instance guarantee we + * don't collide with a sibling class's leftover state. + */ private void createDatabase() throws SQLException { - try (RelationalConnection connection = DriverManager.getConnection(getSysDbUri()) - .unwrap(RelationalConnection.class)) { - try (RelationalStatement statement = connection.createStatement()) { - statement.executeUpdate("Drop database if exists \"" + TESTDB + "\""); - statement.executeUpdate("Drop schema template if exists test_template"); - statement.executeUpdate("CREATE SCHEMA TEMPLATE test_template " + - "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))"); - statement.executeUpdate("create database \"" + TESTDB + "\""); - statement.executeUpdate("create schema \"" + TESTDB + "/test_schema\" with template test_template"); - Assertions.assertNull(statement.getWarnings()); + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection connection = DriverManager.getConnection(getSysDbUri()) + .unwrap(RelationalConnection.class)) { + try (RelationalStatement statement = connection.createStatement()) { + statement.executeUpdate("Drop database if exists \"" + testDb + "\""); + statement.executeUpdate("Drop schema template if exists " + schemaTemplate); + statement.executeUpdate("CREATE SCHEMA TEMPLATE " + schemaTemplate + " " + + "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))"); + statement.executeUpdate("create database \"" + testDb + "\""); + statement.executeUpdate("create schema \"" + testDb + "/test_schema\" with template " + schemaTemplate); + Assertions.assertNull(statement.getWarnings()); + } } - } + }); } private void cleanup() throws SQLException { - try (RelationalConnection connection = DriverManager.getConnection(getSysDbUri()) - .unwrap(RelationalConnection.class)) { - try (RelationalStatement statement = connection.createStatement()) { - statement.executeUpdate("Drop database \"" + TESTDB + "\""); + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection connection = DriverManager.getConnection(getSysDbUri()) + .unwrap(RelationalConnection.class)) { + try (RelationalStatement statement = connection.createStatement()) { + statement.executeUpdate("Drop database if exists \"" + testDb + "\""); + statement.executeUpdate("Drop schema template if exists " + schemaTemplate); + } } - } + }); } diff --git a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCParameterizedQueryComparisonTest.java b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCParameterizedQueryComparisonTest.java index 2c95bd4f1f..3c8a4d1fda 100644 --- a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCParameterizedQueryComparisonTest.java +++ b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCParameterizedQueryComparisonTest.java @@ -27,6 +27,7 @@ import com.apple.foundationdb.relational.api.RelationalStruct; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.server.InProcessRelationalServer; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.test.ParameterizedTestUtils; import com.apple.test.RandomizedTestUtils; @@ -366,21 +367,29 @@ public void setUp() throws SQLException { dbPath = "/FRL/parameters_" + uuid; templateName = "template_" + uuid; - try (RelationalConnection conn = getJdbcCatalogConnection()) { - try (RelationalStatement stmt = conn.createStatement()) { - stmt.executeUpdate("DROP DATABASE IF EXISTS \"" + dbPath + "\""); - stmt.executeUpdate("CREATE DATABASE \"" + dbPath + "\""); + // Serialise + retry catalog DDL against the JVM-wide monitor so parallel test classes + // don't race on the shared /__SYS catalog. + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection conn = getJdbcCatalogConnection()) { + try (RelationalStatement stmt = conn.createStatement()) { + stmt.executeUpdate("DROP DATABASE IF EXISTS \"" + dbPath + "\""); + stmt.executeUpdate("CREATE DATABASE \"" + dbPath + "\""); + } } - } + }); } @AfterEach public void tearDown() { - try (RelationalConnection conn = getJdbcCatalogConnection()) { - try (RelationalStatement stmt = conn.createStatement()) { - stmt.executeUpdate("DROP DATABASE \"" + dbPath + "\""); - stmt.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); - } + try { + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection conn = getJdbcCatalogConnection()) { + try (RelationalStatement stmt = conn.createStatement()) { + stmt.executeUpdate("DROP DATABASE IF EXISTS \"" + dbPath + "\""); + stmt.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); + } + } + }); } catch (Exception e) { // best-effort cleanup } @@ -507,16 +516,25 @@ private Connection getConnection(boolean useJdbc) throws SQLException { } private void createSchema(@Nonnull String columnDdl, @Nonnull String extraTypeDdl) throws SQLException { - try (RelationalConnection conn = getJdbcCatalogConnection()) { - try (RelationalStatement stmt = conn.createStatement()) { - String createTemplate = "CREATE SCHEMA TEMPLATE \"" + templateName + "\" " + - extraTypeDdl + " " + - "CREATE TABLE test_table (pk bigint, val " + columnDdl + ", PRIMARY KEY(pk))"; - stmt.executeUpdate(createTemplate); - stmt.executeUpdate("CREATE SCHEMA \"" + dbPath + "/" + SCHEMA_NAME + - "\" WITH TEMPLATE \"" + templateName + "\""); + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalConnection conn = getJdbcCatalogConnection()) { + try (RelationalStatement stmt = conn.createStatement()) { + // Drop the template first: parametrised tests reuse the same fixture, so a + // sibling invocation may already have created a template with this name. + // (The schema itself is per-database and @BeforeEach recreates the database + // fresh each time, so no matching DROP SCHEMA is needed — and the DDL + // parser accepts but does not honour DROP SCHEMA IF EXISTS anyway.) + // TODO double check if this is necessary + stmt.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); + String createTemplate = "CREATE SCHEMA TEMPLATE \"" + templateName + "\" " + + extraTypeDdl + " " + + "CREATE TABLE test_table (pk bigint, val " + columnDdl + ", PRIMARY KEY(pk))"; + stmt.executeUpdate(createTemplate); + stmt.executeUpdate("CREATE SCHEMA \"" + dbPath + "/" + SCHEMA_NAME + + "\" WITH TEMPLATE \"" + templateName + "\""); + } } - } + }); } @FunctionalInterface diff --git a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCSimpleStatementTest.java b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCSimpleStatementTest.java index 9f055a227a..ec5b37b551 100644 --- a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCSimpleStatementTest.java +++ b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/JDBCSimpleStatementTest.java @@ -28,6 +28,7 @@ import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.server.ServerTestUtil; import com.apple.foundationdb.relational.server.RelationalServer; +import com.apple.foundationdb.relational.utils.CatalogOperations; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -37,16 +38,28 @@ import java.io.IOException; import java.sql.SQLException; import java.sql.Types; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; /** * Run some simple Statement updates/executes against a remote Relational DB. */ public class JDBCSimpleStatementTest { private static final String SYSDBPATH = "/" + RelationalKeyspaceProvider.SYS; - private static final String TESTDB = "/FRL/jdbc_test_db"; private static RelationalServer relationalServer; + // Per-instance DB and template names so parallel test classes don't collide on the catalog. + private final String testDb; + private final String schemaTemplate; + + public JDBCSimpleStatementTest() { + final String suffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + this.testDb = "/FRL/jdbc_simple_test_db_" + suffix; + this.schemaTemplate = "test_template_" + suffix; + } + /** * Load our JDBCDriver via ServiceLoader so available to test. */ @@ -72,18 +85,24 @@ public void simpleStatement() throws SQLException, IOException { var jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) .unwrap(RelationalConnection.class)) { + // Catalog setup runs under the JVM-wide catalog lock (via CatalogOperations) so we + // don't race with other test classes doing their own CREATE/DROP DATABASE on the + // same FDB cluster. + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalStatement setupStmt = connection.createStatement()) { + Assertions.assertEquals(0, setupStmt.executeUpdate("Drop database if exists \"" + testDb + "\"")); + Assertions.assertEquals(0, setupStmt.executeUpdate("Drop schema template if exists " + schemaTemplate)); + Assertions.assertEquals(0, + setupStmt.executeUpdate("CREATE SCHEMA TEMPLATE " + schemaTemplate + " " + + "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))")); + Assertions.assertEquals(0, setupStmt.executeUpdate("create database \"" + testDb + "\"")); + Assertions.assertEquals(0, setupStmt.executeUpdate("create schema \"" + testDb + + "/test_schema\" with template " + schemaTemplate)); + } + }); try (RelationalStatement statement = connection.createStatement()) { // Exercise some methods to up our test coverage metrics Assertions.assertEquals(connection, statement.getConnection()); - // Make this better... currently returns zero how ever many rows we touch. - Assertions.assertEquals(0, statement.executeUpdate("Drop database if exists \"" + TESTDB + "\"")); - Assertions.assertEquals(0, statement.executeUpdate("Drop schema template if exists test_template")); - Assertions.assertEquals(0, - statement.executeUpdate("CREATE SCHEMA TEMPLATE test_template " + - "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))")); - Assertions.assertEquals(0, statement.executeUpdate("create database \"" + TESTDB + "\"")); - Assertions.assertEquals(0, statement.executeUpdate("create schema \"" + TESTDB + - "/test_schema\" with template test_template")); // Call some of the statement methods for the sake of exercising coverage. Assertions.assertNull(statement.getWarnings()); // Does nothing. @@ -91,11 +110,14 @@ public void simpleStatement() throws SQLException, IOException { // Cancel currently does nothing. statement.cancel(); Assertions.assertFalse(statement.isClosed()); - try (RelationalResultSet resultSet = statement.executeQuery("select * from databases")) { + // Filter to just our own database + SYS so other concurrent test classes' + // leftover DBs don't pollute the assertion. + try (RelationalResultSet resultSet = statement.executeQuery( + "select * from databases where database_id in ('" + testDb + "', '" + SYSDBPATH + "')")) { checkSelectStarFromDatabasesResultSet(resultSet); } try (RelationalPreparedStatement preparedStatement = - connection.prepareStatement("select * from databases")) { + connection.prepareStatement("select * from databases where database_id in ('" + testDb + "', '" + SYSDBPATH + "')")) { try (RelationalResultSet resultSet = preparedStatement.executeQuery()) { checkSelectStarFromDatabasesResultSet(resultSet); } @@ -116,14 +138,17 @@ public void simpleStatement() throws SQLException, IOException { } } } finally { - try (RelationalStatement statement = connection.createStatement()) { - statement.executeUpdate("Drop database \"" + TESTDB + "\""); - } + CatalogOperations.runLockedWithRetry(() -> { + try (RelationalStatement cleanupStmt = connection.createStatement()) { + cleanupStmt.executeUpdate("Drop database if exists \"" + testDb + "\""); + cleanupStmt.executeUpdate("Drop schema template if exists " + schemaTemplate); + } + }); } } } - private static void checkSelectStarFromDatabasesResultSet(RelationalResultSet resultSet) throws SQLException { + private void checkSelectStarFromDatabasesResultSet(RelationalResultSet resultSet) throws SQLException { Assertions.assertNotNull(resultSet); Assertions.assertTrue(resultSet.isWrapperFor(RelationalResultSetFacade.class)); // Exercise some metadata methods to get our jacoco coverage up. @@ -133,15 +158,17 @@ private static void checkSelectStarFromDatabasesResultSet(RelationalResultSet re // Label == name for now. Assertions.assertEquals(columnName, resultSet.getMetaData().getColumnLabel(1)); Assertions.assertEquals(Types.VARCHAR, resultSet.getMetaData().getColumnType(1)); - Assertions.assertTrue(resultSet.next()); - Assertions.assertEquals(TESTDB, resultSet.getString(1)); - Assertions.assertEquals(TESTDB, resultSet.getString(columnName)); - // This should work too. - Assertions.assertEquals(TESTDB, resultSet.getString(columnName.toLowerCase())); - Assertions.assertTrue(resultSet.next()); - Assertions.assertEquals(SYSDBPATH, resultSet.getString(1)); - Assertions.assertEquals(SYSDBPATH, resultSet.getString(columnName)); - Assertions.assertFalse(resultSet.next()); + // The result-set ordering isn't guaranteed relative to our test's DB and /__SYS, so + // collect into a set and assert set equality. Also exercises getString(columnName) and + // its lower-cased variant on each row. + final Set ids = new HashSet<>(); + while (resultSet.next()) { + final String id = resultSet.getString(1); + Assertions.assertEquals(id, resultSet.getString(columnName)); + Assertions.assertEquals(id, resultSet.getString(columnName.toLowerCase())); + ids.add(id); + } + Assertions.assertEquals(Set.of(testDb, SYSDBPATH), ids); resultSet.clearWarnings(); // Does nothing. // For now they are empty. Assertions.assertNull(resultSet.getWarnings()); diff --git a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/SimpleDirectAccessInsertionTests.java b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/SimpleDirectAccessInsertionTests.java index 6f219321b7..b21e828090 100644 --- a/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/SimpleDirectAccessInsertionTests.java +++ b/fdb-relational-jdbc/src/test/java/com/apple/foundationdb/relational/jdbc/SimpleDirectAccessInsertionTests.java @@ -29,6 +29,7 @@ import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; import com.apple.foundationdb.relational.server.ServerTestUtil; import com.apple.foundationdb.relational.server.RelationalServer; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.relational.utils.ResultSetAssert; import com.apple.foundationdb.relational.utils.TestSchemas; @@ -45,6 +46,7 @@ import java.sql.Statement; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; /** * Simple unit tests around direct-access insertion tests. @@ -56,12 +58,20 @@ public class SimpleDirectAccessInsertionTests { private static RelationalServer relationalServer; private static final String SCHEMA_NAME = "TEST_SCHEMA"; private static final String SYSDBPATH = "/" + RelationalKeyspaceProvider.SYS; - private static URI databasePath; - private static String templateName; + // Per-instance DB and template names so parallel test classes on the shared FDB cluster + // can't collide on the catalog (see JDBCAutoCommitTest for the same pattern). + private final URI databasePath; + private final String templateName; private static final String RESTAURANT = "RESTAURANT"; private static final String REVIEWER = "RESTAURANT_REVIEWER"; + public SimpleDirectAccessInsertionTests() { + final String suffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + this.databasePath = URI.create("/FRL/SimpleDirectAccessInsertionTests_" + suffix); + this.templateName = "SimpleDirectAccessInsertionTests_" + suffix + "_TEMPLATE"; + } + /** * Load our JDBCDriver via ServiceLoader so available to test. */ @@ -70,10 +80,6 @@ public static void beforeAll() throws SQLException, IOException { // Load driver. JDBCRelationalDriverTest.getDriver(); relationalServer = ServerTestUtil.createAndStartRelationalServer(GrpcConstants.DEFAULT_SERVER_PORT); - // Copied from Simple DatabaseRule Constructor. - databasePath = URI.create("/FRL/" + SimpleDirectAccessInsertionTests.class.getSimpleName()); - templateName = databasePath.getPath().substring(databasePath.getPath().lastIndexOf("/") + 1) + - "_TEMPLATE"; } @AfterAll @@ -89,39 +95,46 @@ public static void afterAll() throws IOException, SQLException { @BeforeEach public void beforeEach() throws SQLException { // Here we do what is done inside in the test extension SimpleDatabaseRule... before and after each test. - String jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; - try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) - .unwrap(RelationalConnection.class)) { - try (Statement statement = connection.createStatement()) { - String createStatement = "CREATE SCHEMA TEMPLATE \"" + templateName + "\" " + - TestSchemas.restaurant(); - statement.executeUpdate(createStatement); - } - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("CREATE DATABASE \"" + databasePath.getPath() + "\""); - } - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("CREATE SCHEMA \"" + databasePath.getPath() + "/" + SCHEMA_NAME + - "\" WITH TEMPLATE \"" + templateName + "\""); + // Serialise the catalog DDL against every other test in this JVM via the JVM-wide + // monitor + retry-on-conflict machinery. + CatalogOperations.runLockedWithRetry(() -> { + String jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; + try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) + .unwrap(RelationalConnection.class)) { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); + statement.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); + } + try (Statement statement = connection.createStatement()) { + String createStatement = "CREATE SCHEMA TEMPLATE \"" + templateName + "\" " + + TestSchemas.restaurant(); + statement.executeUpdate(createStatement); + } + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("CREATE DATABASE \"" + databasePath.getPath() + "\""); + } + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("CREATE SCHEMA \"" + databasePath.getPath() + "/" + SCHEMA_NAME + + "\" WITH TEMPLATE \"" + templateName + "\""); + } } - } + }); } @AfterEach public void afterEach() throws SQLException { - String jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; - try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) - .unwrap(RelationalConnection.class)) { - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP SCHEMA \"" + databasePath.getPath() + "/" + SCHEMA_NAME + "\""); - } - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP DATABASE \"" + databasePath.getPath() + "\""); - } - try (Statement statement = connection.createStatement()) { - statement.executeUpdate("DROP SCHEMA TEMPLATE \"" + templateName + "\""); + CatalogOperations.runLockedWithRetry(() -> { + String jdbcStr = "jdbc:relational://localhost:" + relationalServer.getGrpcPort() + SYSDBPATH + "?schema=" + RelationalKeyspaceProvider.CATALOG; + try (RelationalConnection connection = JDBCRelationalDriverTest.getDriver().connect(jdbcStr, null) + .unwrap(RelationalConnection.class)) { + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP DATABASE IF EXISTS \"" + databasePath.getPath() + "\""); + } + try (Statement statement = connection.createStatement()) { + statement.executeUpdate("DROP SCHEMA TEMPLATE IF EXISTS \"" + templateName + "\""); + } } - } + }); } @Test diff --git a/fdb-relational-server/fdb-relational-server.gradle b/fdb-relational-server/fdb-relational-server.gradle index 279e15e74e..056b9dd0d5 100644 --- a/fdb-relational-server/fdb-relational-server.gradle +++ b/fdb-relational-server/fdb-relational-server.gradle @@ -119,6 +119,7 @@ dependencies { testImplementation(libs.bundles.test.impl) testImplementation(project(':fdb-test-utils')) + testImplementation(testFixtures(project(":fdb-relational-core"))) testRuntimeOnly(libs.bundles.test.runtime) testCompileOnly(libs.bundles.test.compileOnly) testImplementation(libs.grpc.testing) diff --git a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java index bc07f7b266..570047af04 100644 --- a/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java +++ b/fdb-relational-server/src/main/java/com/apple/foundationdb/relational/server/FRL.java @@ -38,6 +38,7 @@ import com.apple.foundationdb.relational.api.SqlTypeNamesSupport; import com.apple.foundationdb.relational.api.Transaction; import com.apple.foundationdb.relational.api.catalog.StoreCatalog; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; import com.apple.foundationdb.relational.api.exceptions.RelationalException; import com.apple.foundationdb.relational.api.metrics.NoOpMetricRegistry; import com.apple.foundationdb.relational.jdbc.TypeConversion; @@ -78,6 +79,7 @@ // Needs to be public so can be used by sub-packages; i.e. the JDBCService @API(API.Status.EXPERIMENTAL) public class FRL implements AutoCloseable { + private final FdbConnection fdbDatabase; private final RelationalDriver driver; private boolean registeredJDBCEmbedDriver; @@ -102,14 +104,21 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } this.fdbDatabase = new DirectFdbConnection(fdbDb, NoOpMetricRegistry.INSTANCE); + // No JVM-wide lock is needed here — `registerDomainIfNotExists` is synchronized on + // the singleton and `RecordLayerStoreCatalog.initialize` is idempotent (see the + // conditional saveSchema in that method). We do retry the catalog-open commit on + // SERIALIZATION_FAILURE, though: multiple FRLs can still race on the very first init + // against a fresh cluster (multiple test-class @BeforeAll hooks constructing FRLs + // concurrently against the same cluster), or on any cross-process init when several + // FRL processes start together. Once the catalog exists, subsequent inits are + // read-only and don't need the retry, so the loop terminates quickly in the common + // case. For test wiring where multiple JVM processes race on the very first init of a + // fresh cluster, see the proactive one-shot initializer wired into + // {@code YamlTestExtension.beforeAll} (yaml-tests/YamlTestCatalogInitializer). final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); keyspaceProvider.registerDomainIfNotExists("FRL"); - KeySpace keySpace = keyspaceProvider.getKeySpace(); - StoreCatalog storeCatalog; - try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { - storeCatalog = StoreCatalogProvider.getCatalog(txn, keySpace); - txn.commit(); - } + final KeySpace keySpace = keyspaceProvider.getKeySpace(); + final StoreCatalog storeCatalog = openCatalogWithRetry(keySpace); RecordLayerConfig rlConfig = RecordLayerConfig.getDefault(); RecordLayerMetadataOperationsFactory ddlFactory = new RecordLayerMetadataOperationsFactory.Builder() @@ -143,6 +152,41 @@ public FRL(@Nonnull Options options, @Nullable String clusterFile, boolean regis } } + /** + * Opens (creating on first use) the store catalog, retrying on SQLSTATE 40001 transaction + * conflicts up to a small attempt limit. Absorbs races between concurrent FRL + * constructions — both within-JVM (parallel test class @BeforeAll hooks) and cross-JVM + * (parent test process plus external-server subprocesses) — on the catalog-init commit. + * After the very first init succeeds, subsequent calls are read-only (see the conditional + * saveSchema in {@code RecordLayerStoreCatalog#initialize}) and this loop terminates on + * the first attempt. + */ + @Nonnull + private StoreCatalog openCatalogWithRetry(@Nonnull KeySpace keySpace) throws RelationalException { + final int maxAttempts = 10; + RelationalException last = null; + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try (Transaction txn = fdbDatabase.getTransactionManager().createTransaction(Options.NONE)) { + final StoreCatalog catalog = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + return catalog; + } catch (RelationalException e) { + if (e.getErrorCode() != ErrorCode.SERIALIZATION_FAILURE) { + throw e; + } + last = e; + // Short, linearly-increasing backoff; commit-conflict windows are brief. + try { + Thread.sleep(20L * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw e; + } + } + } + throw last; + } + public RelationalDriver getDriver() { return driver; } diff --git a/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/InProcessRelationalServerTest.java b/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/InProcessRelationalServerTest.java index 200fe1b82f..6786239d89 100644 --- a/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/InProcessRelationalServerTest.java +++ b/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/InProcessRelationalServerTest.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import java.io.IOException; +import java.sql.SQLException; /** * Test server instance that runs inprocess. @@ -52,7 +53,7 @@ public static void afterAll() throws IOException { * runs and then shut it all down. Runs basic test from sister test class, RelationalServerTest. */ @Test - public void simpleJDBCServiceClientOperation() throws IOException, InterruptedException { + public void simpleJDBCServiceClientOperation() throws SQLException { String serverName = this.server.getServerName(); ManagedChannel managedChannel = InProcessChannelBuilder.forName(serverName).directExecutor().build(); try { diff --git a/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/RelationalServerTest.java b/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/RelationalServerTest.java index 49cb3c6daf..ecb4220fa0 100644 --- a/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/RelationalServerTest.java +++ b/fdb-relational-server/src/test/java/com/apple/foundationdb/relational/server/RelationalServerTest.java @@ -27,6 +27,7 @@ import com.apple.foundationdb.relational.jdbc.grpc.v1.StatementRequest; import com.apple.foundationdb.relational.jdbc.grpc.v1.StatementResponse; import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; +import com.apple.foundationdb.relational.utils.CatalogOperations; import com.apple.foundationdb.test.FDBTestEnvironment; import com.apple.test.Tags; import com.google.protobuf.TextFormat; @@ -58,9 +59,11 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class RelationalServerTest { @@ -116,19 +119,31 @@ private static ResultSet execute(JDBCServiceGrpc.JDBCServiceBlockingStub stub, S return statementResponse.hasResultSet() ? statementResponse.getResultSet() : null; } - static void simpleJDBCServiceClientOperation(ManagedChannel managedChannel) { + static void simpleJDBCServiceClientOperation(ManagedChannel managedChannel) throws SQLException { String sysDbPath = "/" + RelationalKeyspaceProvider.SYS; - String testdb = "/FRL/server_test_db"; + // Each invocation gets its own database + template names so that parallel test JVMs + // (this test can be triggered from multiple entry points, and the underlying FDB + // cluster is shared with jdbc-tests etc.) can't collide on the catalog. + final String suffix = Long.toHexString(ThreadLocalRandom.current().nextLong()); + final String testdb = "/FRL/server_test_db_" + suffix; + final String schemaTemplate = "test_template_" + suffix; JDBCServiceGrpc.JDBCServiceBlockingStub stub = JDBCServiceGrpc.newBlockingStub(managedChannel); try { - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop database if exists \"" + testdb + "\""); - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop schema template if exists test_template"); - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, - "CREATE SCHEMA TEMPLATE test_template " + - "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))"); - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "create database \"" + testdb + "\""); - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "create schema \"" + testdb + "/test_schema\" with template test_template"); - ResultSet resultSet = execute(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "select * from databases"); + // Serialise catalog DDL against every other test in this JVM via the JVM-wide + // monitor + retry-on-conflict machinery. + CatalogOperations.runLockedWithRetry(() -> { + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop database if exists \"" + testdb + "\""); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop schema template if exists " + schemaTemplate); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, + "CREATE SCHEMA TEMPLATE " + schemaTemplate + " " + + "CREATE TABLE test_table (rest_no bigint, name string, PRIMARY KEY(rest_no))"); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "create database \"" + testdb + "\""); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "create schema \"" + testdb + "/test_schema\" with template " + schemaTemplate); + }); + // Filter the databases result to the two we care about so sibling tests' DBs on + // the shared catalog don't skew the count/order. + ResultSet resultSet = execute(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, + "select * from databases where database_id in ('" + testdb + "', '" + sysDbPath + "')"); Assertions.assertEquals(2, resultSet.getRowCount()); Assertions.assertEquals(1, resultSet.getRow(0).getColumns().getColumnCount()); Assertions.assertEquals(1, resultSet.getRow(1).getColumns().getColumnCount()); @@ -145,7 +160,10 @@ static void simpleJDBCServiceClientOperation(ManagedChannel managedChannel) { } throw t; } finally { - update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop database \"" + testdb + "\""); + CatalogOperations.runLockedWithRetry(() -> { + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop database if exists \"" + testdb + "\""); + update(stub, sysDbPath, RelationalKeyspaceProvider.CATALOG, "Drop schema template if exists " + schemaTemplate); + }); } } @@ -155,7 +173,7 @@ static void simpleJDBCServiceClientOperation(ManagedChannel managedChannel) { * shut it all down. */ @Test - public void simpleJDBCServiceClientOperation() throws IOException, InterruptedException { + public void simpleJDBCServiceClientOperation() throws SQLException { ManagedChannel managedChannel = ManagedChannelBuilder.forTarget("localhost:" + relationalServer.getGrpcPort()).usePlaintext().build(); try { @@ -169,7 +187,7 @@ public void simpleJDBCServiceClientOperation() throws IOException, InterruptedEx * Check the {@link HealthGrpc} Service is up and working. */ @Test - public void healthServiceClientOperation() throws IOException, InterruptedException { + public void healthServiceClientOperation() throws InterruptedException { ManagedChannel managedChannel = ManagedChannelBuilder .forTarget("localhost:" + relationalServer.getGrpcPort()) .usePlaintext().build(); @@ -196,7 +214,7 @@ public void healthServiceClientOperation() throws IOException, InterruptedExcept * Prometheus metrics are made for dashboarding and exotic querying, not for easy evalution in unit tests. */ @Test - public void testMetrics() throws IOException, InterruptedException { + public void testMetrics() throws IOException, InterruptedException, SQLException { // Metrics names recorded for grpc -- all we currently record for prometheus -- can be gotten from // down the page on https://github.com/grpc-ecosystem/java-grpc-prometheus CollectorRegistry collectorRegistry = relationalServer.getCollectorRegistry(); @@ -206,7 +224,7 @@ public void testMetrics() throws IOException, InterruptedException { // Run some queries which will tickle grpc. simpleJDBCServiceClientOperation(); double after = countSampleValues(metricName, metricName + "_total", collectorRegistry); - Assertions.assertEquals(after - before, 7.0/* Expected Difference -- 4 calls*/); + Assertions.assertEquals(after - before, 8.0/* 5 setup + 1 execute + 2 cleanup grpc calls */); // Streaming is not implemented yet so these should be zero. var receivedAfter = findRecordedMetricOrThrow("grpc_server_msg_received", collectorRegistry); Assertions.assertEquals(0, receivedAfter.samples.size()); diff --git a/gradle/testing.gradle b/gradle/testing.gradle index 8a66ebd406..41fcc266a0 100644 --- a/gradle/testing.gradle +++ b/gradle/testing.gradle @@ -29,6 +29,19 @@ jacoco { toolVersion = libs.versions.jacoco.get() } +// Resolved to the mockito-core jar so it can be installed as a -javaagent on every Test JVM. +// Required on JDK 21+ because Mockito's inline mock maker can no longer self-attach to a +// running VM (see https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3). +// Under parallel JUnit class execution this also avoids the Byte-Buddy self-attach race that +// manifests as `IllegalStateException: Could not initialize plugin: interface +// org.mockito.plugins.MockMaker`. Hooked into every Test task in `tasks.withType(Test)` below. +configurations { + mockitoAgent +} +dependencies { + mockitoAgent(libs.mockito) { transitive = false } +} + // if you add to or modify these testing configurations, make sure to update the CONTRIBUTING.md to include any updates test { useJUnitPlatform { @@ -311,6 +324,15 @@ tasks.withType(Test).configureEach { task -> // useJUnitPlatform() here is a no-op for tasks that already have it. task.useJUnitPlatform() + // Install Mockito as a JVM agent on every Test task. See the comment on the + // `mockitoAgent` configuration above for why this is necessary. Deferred to `doFirst` + // so that the `mockitoAgent` configuration is not resolved during evaluation (which + // would prevent the root build's `resolutionStrategy.eachDependency` callback from + // attaching to it). + task.doFirst { + task.jvmArgs("-javaagent:${configurations.mockitoAgent.asPath}") + } + // Configure whether or not tests will validate that asyncToSync isn't being called in async // context. See BlockingInAsyncDetection class for details on values. // disable BLOCKING_DETECTION to better simulate performance @@ -359,12 +381,12 @@ tasks.withType(Test).configureEach { task -> if (task.name == 'test') { task.systemProperty('junit.jupiter.execution.parallel.enabled', 'true') task.systemProperty('junit.jupiter.execution.parallel.mode.default', 'same_thread') - task.systemProperty('junit.jupiter.execution.parallel.mode.classes.default', 'same_thread') + task.systemProperty('junit.jupiter.execution.parallel.mode.classes.default', 'concurrent') // So far, using the dynamic strategy results in tests that should be fairly fast timing out // at 5 minutes. The current theory is some thread pool is saturated and unable to unblock // itself. In the meantime, try a fixed 2 threads. task.systemProperty('junit.jupiter.execution.parallel.config.strategy', 'fixed') - task.systemProperty('junit.jupiter.execution.parallel.config.fixed.parallelism', '2') + task.systemProperty('junit.jupiter.execution.parallel.config.fixed.parallelism', '4') // We use worker_thread_pool rather than fork_join_pool because we heavily depend on ForkJoinPool for // FDB operations, which causes junit to incorrectly increase the number of concurrent tests to well // beyond what we want. diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/PerMethodResourceLocksProvider.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/PerMethodResourceLocksProvider.java new file mode 100644 index 0000000000..df6ab555d4 --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/PerMethodResourceLocksProvider.java @@ -0,0 +1,61 @@ +/* + * PerMethodResourceLocksProvider.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.relational.yamltests; + +import org.junit.jupiter.api.parallel.ResourceAccessMode; +import org.junit.jupiter.api.parallel.ResourceLocksProvider; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +/** + * A {@link ResourceLocksProvider} that hands each {@code @YamlTest} test method its own unique + * lock keyed by the method's fully-qualified name. + * + *

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

Wiring: {@code @YamlTest} is meta-annotated with + * {@code @ResourceLock(providers = PerMethodResourceLocksProvider.class)}. JUnit invokes + * {@link #provideForMethod} once per test method, and the returned lock is applied uniformly + * to every {@link org.junit.jupiter.api.TestTemplate} invocation of that method — so + * invocations of one method compete for the shared key and run one at a time, while invocations + * of different methods hold different keys and can proceed concurrently. + */ +public class PerMethodResourceLocksProvider implements ResourceLocksProvider { + + private static final String KEY_PREFIX = "yaml-test/"; + + @Override + public Set provideForMethod(List> enclosingInstanceTypes, Class testClass, Method testMethod) { + // The key uses the fully-qualified class name so that if two @YamlTest classes happen to + // declare a method with the same simple name they still get independent locks. + final String key = KEY_PREFIX + testClass.getName() + "#" + testMethod.getName(); + return Set.of(new Lock(key, ResourceAccessMode.READ_WRITE)); + } +} diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/SimpleYamlConnection.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/SimpleYamlConnection.java index 558d7ec8fe..721b16abe7 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/SimpleYamlConnection.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/SimpleYamlConnection.java @@ -59,6 +59,17 @@ public SimpleYamlConnection(@Nonnull Connection connection, @Nonnull SemanticVer this.versions = List.of(version); this.connectionLabel = connectionLabel; this.clusterFile = clusterFile; + // Diagnostic: ask the underlying FDB context to record the ranges that caused any + // commit-time serialization failure. On failure, EmbeddedRelationalConnection.commitInternal + // will attach the ranges to the exception's context and warn-log them. Overhead is only + // paid on a failed commit (an extra read of the conflict special-key-space). + // + // Restricted to embedded connections because the JDBC/gRPC option-serialization layer + // does not yet know how to encode this new option name (throws "Cannot encode option in + // protobuf" later when the options are pushed to the server). + if (underlying instanceof EmbeddedRelationalConnection) { + underlying.setOption(Options.Name.REPORT_CONFLICTING_KEYS, true); + } } @Nonnull diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTest.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTest.java index fe186d0c90..732d3f5955 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTest.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTest.java @@ -22,6 +22,9 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.api.parallel.ResourceLock; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -41,12 +44,26 @@ * annotation {@link ExcludeYamlTestConfig}. Ideally, these are short lived, primarily to support adding a config, * and then fixing some tests that may fail with that config. *

+ *

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

*/ @Retention(RetentionPolicy.RUNTIME) @ExtendWith(YamlTestExtension.class) // Right now these are the only tests that have the capability to run in mixed mode, but if we create other tests, this // should be moved to a shared static location @Tag("MixedMode") +@Execution(ExecutionMode.CONCURRENT) +@ResourceLock(providers = PerMethodResourceLocksProvider.class) public @interface YamlTest { /** * Simple interface to run a {@code .yamsql} file, based on the config. diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java new file mode 100644 index 0000000000..14b4c65c01 --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestCatalogInitializer.java @@ -0,0 +1,140 @@ +/* + * YamlTestCatalogInitializer.java + * + * This source file is part of the FoundationDB open source project + * + * Copyright 2015-2026 Apple Inc. and the FoundationDB project authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.foundationdb.relational.yamltests; + +import com.apple.foundationdb.Range; +import com.apple.foundationdb.record.provider.foundationdb.FDBDatabase; +import com.apple.foundationdb.record.provider.foundationdb.FDBDatabaseFactory; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContext; +import com.apple.foundationdb.record.provider.foundationdb.FDBRecordContextConfig; +import com.apple.foundationdb.record.provider.foundationdb.keyspace.KeySpace; +import com.apple.foundationdb.relational.api.Transaction; +import com.apple.foundationdb.relational.api.catalog.StoreCatalog; +import com.apple.foundationdb.relational.api.exceptions.ErrorCode; +import com.apple.foundationdb.relational.api.exceptions.RelationalException; +import com.apple.foundationdb.relational.recordlayer.RecordContextTransaction; +import com.apple.foundationdb.relational.recordlayer.RelationalKeyspaceProvider; +import com.apple.foundationdb.relational.recordlayer.catalog.StoreCatalogProvider; +import com.apple.foundationdb.relational.recordlayer.util.ConflictKeyFormatter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nonnull; +import java.util.List; + +/** + * Proactive one-shot initialiser for the SYS catalog on every cluster the yaml-tests will + * touch. + *

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

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

+ * Lives inside {@code yaml-tests} rather than pulling + * {@code fdb-relational-core}'s testFixtures onto the yaml-tests classpath — the yaml-tests + * package already depends on the record-layer primitives this needs directly, so a private + * helper here is cheaper than adding another cross-module dependency edge. + */ +public final class YamlTestCatalogInitializer { + + private static final Logger LOGGER = LoggerFactory.getLogger(YamlTestCatalogInitializer.class); + + private static final int MAX_ATTEMPTS = 10; + private static final long RETRY_BASE_MILLIS = 20L; + + private YamlTestCatalogInitializer() { + } + + /** + * Initialise the SYS catalog on every given cluster, in order. Idempotent under retry — + * if the store already exists, {@link StoreCatalogProvider#getCatalog} opens it. If the + * commit races with a concurrent initialiser on the same cluster, retries on SQLSTATE + * 40001 with a short back-off. + * + * @param clusterFiles cluster files to initialise; a {@code null} entry means "the + * default cluster" per {@link FDBDatabaseFactory#getDatabase(String)} + * @throws RelationalException if any cluster's init ultimately fails after retries + */ + public static void initializeCatalog(@Nonnull final List clusterFiles) throws RelationalException { + // Register the FRL keyspace domain once up front. It's idempotent, but doing it here + // ensures the keyspace tree is ready before we resolve any paths against it below. + final RelationalKeyspaceProvider keyspaceProvider = RelationalKeyspaceProvider.instance(); + keyspaceProvider.registerDomainIfNotExists("FRL"); + final KeySpace keySpace = keyspaceProvider.getKeySpace(); + for (final String clusterFile : clusterFiles) { + initializeOne(clusterFile, keySpace); + } + } + + private static void initializeOne(final String clusterFile, @Nonnull final KeySpace keySpace) throws RelationalException { + final FDBDatabase database = FDBDatabaseFactory.instance().getDatabase(clusterFile); + // Ask FDB to record the specific conflict ranges on any commit failure so we can log + // exactly which key(s) collided when a retry happens. Setting a timer ensures the + // transaction is wrapped in InstrumentedTransaction so its mutations can be traced. + final FDBRecordContextConfig config = FDBRecordContextConfig.newBuilder() + .setReportConflictingKeys(true) + .setTimer(new com.apple.foundationdb.record.provider.foundationdb.FDBStoreTimer()) + .build(); + RelationalException last = null; + for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + final FDBRecordContext ctx = database.openContext(config); + try (Transaction txn = new RecordContextTransaction(ctx)) { + final StoreCatalog ignored = StoreCatalogProvider.getCatalog(txn, keySpace); + txn.commit(); + return; + } catch (RelationalException e) { + if (e.getErrorCode() != ErrorCode.SERIALIZATION_FAILURE) { + throw e; + } + final List ranges = ctx.getNotCommittedConflictingKeys(); + if (ranges != null) { + final String rendered = ConflictKeyFormatter.formatRanges(ranges); + LOGGER.warn("YamlTestCatalogInitializer commit conflict on {} (attempt {}/{}): count={} ranges={}", + clusterFile == null ? "" : clusterFile, attempt, MAX_ATTEMPTS, ranges.size(), rendered); + } + last = e; + // Short, linear back-off; the commit-conflict window is brief and clearing it + // just requires the other party's commit to land. + try { + Thread.sleep(RETRY_BASE_MILLIS * attempt); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + e.addSuppressed(ie); + throw e; + } + } + } + throw last; + } +} diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java index 9b0f5c18bc..63d6565475 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java @@ -108,6 +108,14 @@ public YamlTestExtension(@Nonnull final List clusterFiles, final boolean @Override public void beforeAll(final ExtensionContext context) throws Exception { + // Proactively initialise the SYS catalog on every cluster this extension will use, + // BEFORE any config's beforeAll spins up an FRL / EmbeddedRelationalDriver / external + // server. Running it here means the catalog is either already-created (this call) or + // being created by us (retry-safe) — sibling FRLs constructed later in the various + // configs (embedded / JDBC in-process / external subprocesses) all open an existing + // store and skip the racing init-commit path that we previously worked around with + // per-operation locks. See YamlTestCatalogInitializer for the details. + YamlTestCatalogInitializer.initializeCatalog(clusterFiles); maintainConfigs = List.of( new CorrectExplains(new EmbeddedConfig(clusterFiles)), new CorrectMetrics(new EmbeddedConfig(clusterFiles)), diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java index 8e0f4a4d14..2db4cfa60b 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/IncludeBlock.java @@ -53,14 +53,22 @@ public class IncludeBlock extends SupportBlock { public static final String INCLUDE = "include"; private static final Logger logger = LogManager.getLogger(IncludeBlock.class); - private static final Yaml YAML_ENGINE; - static { - LoaderOptions loaderOptions = new LoaderOptions(); + /** + * Builds a fresh {@link Yaml} instance for parsing an included resource. + *

+ * SnakeYAML's {@link Yaml} (and its underlying {@code Scanner}/{@code Parser}/{@code StreamReader}) + * is documented as not thread-safe. When yaml-tests run with class-level parallel execution + * enabled (see {@code gradle/testing.gradle}), multiple test classes call {@link #parse} concurrently, + * and a shared static engine corrupted into {@code ScannerException}/{@code ParserException}/ + * {@code ConcurrentModificationException}/{@code ClassCastException} on parser-internal state. + */ + @Nonnull + private static Yaml newYamlEngine() { + final LoaderOptions loaderOptions = new LoaderOptions(); loaderOptions.setAllowDuplicateKeys(true); - DumperOptions dumperOptions = new DumperOptions(); - - YAML_ENGINE = new Yaml(new CustomYamlConstructor(loaderOptions), new Representer(dumperOptions), + final DumperOptions dumperOptions = new DumperOptions(); + return new Yaml(new CustomYamlConstructor(loaderOptions), new Representer(dumperOptions), new DumperOptions(), loaderOptions, new Resolver()); } @@ -88,7 +96,7 @@ public static List parse(@Nonnull final YamlReference.YamlResource resour int blockNumber = 0; executionContext.registerResource(resource); try (var inputStream = getInputStream(resource)) { - final var docs = StreamSupport.stream(YAML_ENGINE.loadAll(inputStream).spliterator(), false).collect(Collectors.toList()); + final var docs = StreamSupport.stream(newYamlEngine().loadAll(inputStream).spliterator(), false).collect(Collectors.toList()); for (var doc : docs) { final var blocks = Block.parse(resource, doc, blockNumber, executionContext, resource.isTopLevel()); allBlocks.addAll(blocks); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java index 0ca83b7923..b349a8ea12 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/block/SetupBlock.java @@ -316,8 +316,11 @@ public static DestructTemplateBlock withDatabaseAndSchema(@Nonnull final YamlRef @Nonnull String schemaTemplateName, @Nonnull String databasePath) { try { final var steps = new ArrayList(); - steps.add("DROP DATABASE " + databasePath); - steps.add("DROP SCHEMA TEMPLATE " + schemaTemplateName); + // Use IF EXISTS so a retry after a partial success (first attempt dropped the + // database but conflicted on the schema-template DROP) doesn't fail on the second + // attempt's "Cannot delete unknown database" / "Schema template doesn't exist". + steps.add("DROP DATABASE IF EXISTS " + databasePath); + steps.add("DROP SCHEMA TEMPLATE IF EXISTS " + schemaTemplateName); final var executables = new ArrayList>(); for (final var step : steps) { final var resolvedCommand = QueryCommand.withQueryString(reference, step, executionContext); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java index c0f67b7b82..0961c01d0a 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryExecutor.java @@ -155,17 +155,44 @@ private Object executeStatementAndCheckCacheIfNeeded(@Nonnull Statement s, final return executeStatementAndCheckForceContinuations(s, statementHasQuery, queryString, connection, maxRows); } final var preMetricCollector = connection.getMetricCollector(); - final var preValue = preMetricCollector != null && - preMetricCollector.hasCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) ? - preMetricCollector.getCountsForCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) : 0; + final long preTertiaryHit = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT); + // Snapshot all cache counters so we can log per-counter deltas if the plan-hit + // assertion fails. Distinguishes: + // - miss (populate happened this call) → PLAN_CACHE_TERTIARY_MISS incremented + // - eviction wiped the entry we expected → *_LRU_EVICTION incremented + // - loader-race (another thread populated) → all miss/hit counters unchanged for us + // - primary/secondary wipeout that killed the tertiary → PRIMARY_MISS / SECONDARY_MISS + final long prePrimaryMiss = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_PRIMARY_MISS); + final long preSecondaryMiss = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_SECONDARY_MISS); + final long preTertiaryMiss = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_MISS); + final long prePrimaryLru = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_PRIMARY_LRU_EVICTION); + final long preSecondaryLru = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_SECONDARY_LRU_EVICTION); + final long preTertiaryLru = readCounter(preMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_LRU_EVICTION); final var toReturn = executeStatementAndCheckForceContinuations(s, statementHasQuery, queryString, connection, maxRows); final var postMetricCollector = connection.getMetricCollector(); if (postMetricCollector != null) { final var postValue = postMetricCollector.hasCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) ? postMetricCollector.getCountsForCounter(RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_HIT) : 0; - final var planFound = preMetricCollector != postMetricCollector ? postValue == 1 : postValue == preValue + 1; + final var planFound = preMetricCollector != postMetricCollector ? postValue == 1 : postValue == preTertiaryHit + 1; if (!planFound) { - reportTestFailure("‼️ Expected to retrieve the plan from the cache at " + reference); + final long postPrimaryMiss = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_PRIMARY_MISS); + final long postSecondaryMiss = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_SECONDARY_MISS); + final long postTertiaryMiss = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_MISS); + final long postPrimaryLru = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_PRIMARY_LRU_EVICTION); + final long postSecondaryLru = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_SECONDARY_LRU_EVICTION); + final long postTertiaryLru = readCounter(postMetricCollector, RelationalMetric.RelationalCount.PLAN_CACHE_TERTIARY_LRU_EVICTION); + final String delta = String.format( + " [samePMC=%s, tertiaryHit %d→%d, tertiaryMiss +%d, secondaryMiss +%d, primaryMiss +%d," + + " tertiaryLru +%d, secondaryLru +%d, primaryLru +%d]", + preMetricCollector == postMetricCollector, + preTertiaryHit, postValue, + postTertiaryMiss - preTertiaryMiss, + postSecondaryMiss - preSecondaryMiss, + postPrimaryMiss - prePrimaryMiss, + postTertiaryLru - preTertiaryLru, + postSecondaryLru - preSecondaryLru, + postPrimaryLru - prePrimaryLru); + reportTestFailure("‼️ Expected to retrieve the plan from the cache at " + reference + delta); } else { logger.debug("🎁 Retrieved the plan from the cache!"); } @@ -173,6 +200,11 @@ private Object executeStatementAndCheckCacheIfNeeded(@Nonnull Statement s, final return toReturn; } + private static long readCounter(@Nullable com.apple.foundationdb.relational.api.metrics.MetricCollector mc, + @Nonnull RelationalMetric.RelationalCount c) throws RelationalException { + return mc != null && mc.hasCounter(c) ? mc.getCountsForCounter(c) : 0L; + } + @Nullable private Continuation executeQuery(@Nonnull YamlConnection connection, @Nonnull QueryConfig config, @Nonnull String currentQuery, boolean checkCache, @Nullable Integer maxRows, diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java index f4fcddb7fe..f35cd6fe74 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryInterpreter.java @@ -95,7 +95,20 @@ public final class QueryInterpreter { */ private final List> injections; - private static final Yaml INTERPRETER = new Yaml(new QueryParameterYamlConstructor(new LoaderOptions())); + /** + * Builds a fresh {@link Yaml} instance for parsing query parameter injections (the {@code !!…!!} + * snippets). + *

+ * SnakeYAML's {@link Yaml} is not thread-safe: its underlying {@code Scanner}/{@code Parser}/ + * {@code StreamReader} hold per-parse state. Tests run with class-level parallel execution (see + * {@code gradle/testing.gradle}), so a shared static instance would let two test classes corrupt + * each other's parse state — observed as {@code ScannerException}, {@code ParserException}, + * {@code ClassCastException} (MappingEndEvent → NodeEvent), {@code NoSuchElementException}, etc. + */ + @Nonnull + private static Yaml newInterpreter() { + return new Yaml(new QueryParameterYamlConstructor(new LoaderOptions())); + } private static final class QueryParameterYamlConstructor extends SafeConstructor { private static final Tag RANDOM_TAG = new Tag("!r"); @@ -301,7 +314,7 @@ private List> getInjections(@Nonnull String query) { int end = query.indexOf("!!", start + 2); Assert.thatUnchecked(end != -1, "Illegal format: Parameter injection not formed correctly in query " + query); cursor = end + 2; - lst.add(Pair.of(query.substring(start, end + 2), INTERPRETER.load(query.substring(start + 2, end)))); + lst.add(Pair.of(query.substring(start, end + 2), newInterpreter().load(query.substring(start + 2, end)))); } return lst; } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java index d3c50b6b77..163f529ec0 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/EmbeddedConfig.java @@ -26,6 +26,7 @@ import com.apple.foundationdb.relational.server.FRL; import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; +import com.apple.foundationdb.relational.yamltests.YamlTestCatalogInitializer; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.apple.foundationdb.relational.yamltests.connectionfactory.EmbeddedYamlConnectionFactory; @@ -57,11 +58,32 @@ public EmbeddedConfig(@Nonnull final List clusterFiles) { @Override @SuppressWarnings("PMD.CloseResource") // FRLs are tracked in the list and closed in afterAll() public void beforeAll() throws Exception { + // Proactively initialise the SYS catalog on every cluster before we spin up any + // in-JVM FRL for this config. This absorbs the concurrent-init commit conflict that + // otherwise fires when parallel @YamlTest / test-class @BeforeAll hooks all + // construct their own EmbeddedConfig against the same cluster. The initializer is + // idempotent — after the first successful commit against a cluster, subsequent + // invocations are read-only and effectively free. See YamlTestCatalogInitializer + // for the details. + YamlTestCatalogInitializer.initializeCatalog(clusterFiles); var options = Options.builder() .withOption(Options.Name.PLAN_CACHE_PRIMARY_TIME_TO_LIVE_MILLIS, 3_600_000L) .withOption(Options.Name.PLAN_CACHE_SECONDARY_TIME_TO_LIVE_MILLIS, 3_600_000L) .withOption(Options.Name.PLAN_CACHE_TERTIARY_TIME_TO_LIVE_MILLIS, 3_600_000L) - .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 10) + // Moderate bump (roughly 10x defaults) of all three tiers so that under heavy + // class-level parallel execution, plans accumulated across many sequential test + // methods in the same FRL don't get evicted before the framework's "should have + // hit the cache by now" assertion runs. Tertiary default (8) is the tight one. + // + // Larger sizes (e.g. 2000+) verified to expose a real cache-invalidation gap: + // SELECT/scan plans cached against a schema or record store that a subsequent + // test drops are not invalidated, so the next test that re-uses the cached plan + // fails with RecordStoreDoesNotExistException or "SchemaTemplate=… is not in + // catalog". Keeping the bump moderate sidesteps the gap until it's fixed + // separately. + .withOption(Options.Name.PLAN_CACHE_PRIMARY_MAX_ENTRIES, 100) + .withOption(Options.Name.PLAN_CACHE_SECONDARY_MAX_ENTRIES, 1000) + .withOption(Options.Name.PLAN_CACHE_TERTIARY_MAX_ENTRIES, 100) .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 diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCInProcessConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCInProcessConfig.java index d7bef4b69e..a7fa45d2db 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCInProcessConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/JDBCInProcessConfig.java @@ -23,6 +23,7 @@ import com.apple.foundationdb.relational.server.InProcessRelationalServer; import com.apple.foundationdb.relational.yamltests.YamlConnectionFactory; import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; +import com.apple.foundationdb.relational.yamltests.YamlTestCatalogInitializer; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.apple.foundationdb.relational.yamltests.connectionfactory.JDBCInProcessYamlConnectionFactory; @@ -49,6 +50,10 @@ public JDBCInProcessConfig(@Nonnull final List clusterFiles) { @Override @SuppressWarnings("PMD.CloseResource") // Servers are tracked in the list and closed in afterAll() public void beforeAll() throws Exception { + // Proactively initialise the SYS catalog on every cluster before we spin up any + // in-process server. Same reasoning as EmbeddedConfig — parallel test-class @BeforeAll + // hooks would otherwise race on the catalog-init commit. Idempotent under retry. + YamlTestCatalogInitializer.initializeCatalog(clusterFiles); clusters = Clusters.fromClusterFilesAsEntries(clusterFiles, clusterFile -> { try { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java index 562c29e9f3..dcf65d00fc 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/server/ExternalServer.java @@ -22,8 +22,6 @@ import com.apple.foundationdb.record.logging.KeyValueLogMessage; import com.apple.foundationdb.record.logging.LogMessageKeys; -import com.apple.foundationdb.relational.api.exceptions.RelationalException; -import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.BuildVersion; import com.apple.foundationdb.relational.yamltests.connectionfactory.Clusters; import com.google.common.base.Verify; @@ -42,12 +40,10 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -60,6 +56,16 @@ public class ExternalServer implements Clusters.BoundToCluster { private static final Logger logger = LogManager.getLogger(ExternalServer.class); public static final String EXTERNAL_SERVER_PROPERTY_NAME = "yaml_testing_external_server"; + /** + * JVM-wide set of ports currently reserved by any {@link ExternalServer} instance. Closes the race + * where two concurrent {@link #startMultiple(Collection)} calls (e.g. from parallel test classes' + * {@code @BeforeAll}) each see the same port as available — this would otherwise let two test + * classes both try to bind the same port (e.g. 1111). The atomic {@link Set#add(Object)} on a + * {@link ConcurrentHashMap#newKeySet()} acts as a CAS: the winner returns {@code true} and owns + * the port until {@link #stop()} releases it. + */ + private static final Set JVM_WIDE_CLAIMED_PORTS = ConcurrentHashMap.newKeySet(); + @Nonnull private final File serverJar; private int grpcPort; @@ -135,60 +141,98 @@ public String clusterFile() { return clusterFile; } - public void start(Set unavailablePorts) throws Exception { - grpcPort = getAvailablePort(unavailablePorts); - httpPort = getAvailablePort(unavailablePorts); - ProcessBuilder processBuilder = new ProcessBuilder("java", - // TODO add support for debugging by adding, but need to take care with ports - // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n", - "-jar", serverJar.getAbsolutePath(), - "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); - boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); - @Nullable - File outFile; - @Nullable - File errFile; - if (saveServerLogs) { - // Include current time in file names so that things sort nicely. We want the out and err logs - // for the same server start to be adjacent, and it would be nice for the log files to sort - // chronologically + public void start() throws Exception { + grpcPort = getAvailablePort(); + boolean success = false; + try { + httpPort = getAvailablePort(); + ProcessBuilder processBuilder = new ProcessBuilder("java", + // TODO add support for debugging by adding, but need to take care with ports + // "-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n", + "-jar", serverJar.getAbsolutePath(), + "--grpcPort", Integer.toString(grpcPort), "--httpPort", Integer.toString(httpPort)); + boolean saveServerLogs = Boolean.parseBoolean(System.getProperty("tests.saveServerLogs", "false")); + // Always capture stderr to a tempfile so we can include its tail in the failure message + // if the subprocess dies or never becomes available. Stdout is only retained when the + // tests.saveServerLogs sysProp is set, since it can be voluminous in the happy path. + // Include current time in file names so they sort nicely. long currentTime = System.currentTimeMillis(); - outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); - errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); - } else { - outFile = null; - errFile = null; - } - ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); - ProcessBuilder.Redirect err = errFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(errFile); - processBuilder.redirectOutput(out); - processBuilder.redirectError(err); - if (clusterFile != null) { - processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); - } + @Nullable + File outFile; + if (saveServerLogs) { + outFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-out.", ".log"); + } else { + outFile = null; + } + final File errFile = File.createTempFile("fdb-relational-server-" + version + "-" + currentTime + "-" + grpcPort + "-err.", ".log"); + // Don't keep the diagnostic file around in the success path (clutters tmp on dev boxes). + errFile.deleteOnExit(); + ProcessBuilder.Redirect out = outFile == null ? ProcessBuilder.Redirect.DISCARD : ProcessBuilder.Redirect.to(outFile); + processBuilder.redirectOutput(out); + processBuilder.redirectError(ProcessBuilder.Redirect.to(errFile)); + if (clusterFile != null) { + processBuilder.environment().put("FDB_CLUSTER_FILE", clusterFile); + } - if (!startServer(processBuilder)) { - if (logger.isWarnEnabled()) { - logger.warn(KeyValueLogMessage.of("Failed to start external server", + if (!startServer(processBuilder)) { + final String errTail = tailFile(errFile, 4096); + if (logger.isWarnEnabled()) { + logger.warn(KeyValueLogMessage.of("Failed to start external server", + "jar", serverJar, + LogMessageKeys.VERSION, version, + "grpc_port", grpcPort, + "http_port", httpPort, + "out_file", outFile, + "err_file", errFile, + "subprocess_alive", serverProcess != null && serverProcess.isAlive(), + "exit_value", serverProcess != null && !serverProcess.isAlive() ? serverProcess.exitValue() : "n/a")); + } + Assertions.fail("Failed to start the external server (grpc_port=" + grpcPort + + ", http_port=" + httpPort + + ", alive=" + (serverProcess != null && serverProcess.isAlive()) + + (serverProcess != null && !serverProcess.isAlive() ? ", exit=" + serverProcess.exitValue() : "") + + ")\n--- last bytes of subprocess stderr (" + errFile + ") ---\n" + + errTail + + "\n--- end ---"); + } + + if (logger.isInfoEnabled()) { + logger.info(KeyValueLogMessage.of("Started external server", "jar", serverJar, LogMessageKeys.VERSION, version, "grpc_port", grpcPort, "http_port", httpPort, "out_file", outFile, - "err_file", errFile)); + "err_file", errFile + )); + } + success = true; + } finally { + // If we claimed ports but the server failed to come up (Assertions.fail, IOException + // from process.start, etc.), release the JVM-wide claims so the ports become available + // to future allocators. stop() will not be called for a failed start, so this is the + // only cleanup hook. + if (!success) { + JVM_WIDE_CLAIMED_PORTS.remove(grpcPort); + JVM_WIDE_CLAIMED_PORTS.remove(httpPort); } - Assertions.fail("Failed to start the external server"); } + } - if (logger.isInfoEnabled()) { - logger.info(KeyValueLogMessage.of("Started external server", - "jar", serverJar, - LogMessageKeys.VERSION, version, - "grpc_port", grpcPort, - "http_port", httpPort, - "out_file", outFile, - "err_file", errFile - )); + /** Read the trailing {@code maxBytes} of {@code file} as a UTF-8 string, never throwing. */ + @Nonnull + private static String tailFile(@Nonnull File file, int maxBytes) { + try { + final long size = file.length(); + try (java.io.RandomAccessFile raf = new java.io.RandomAccessFile(file, "r")) { + final int read = (int) Math.min(maxBytes, size); + raf.seek(size - read); + final byte[] buf = new byte[read]; + raf.readFully(buf); + return (size > read ? "…(truncated)…\n" : "") + new String(buf, java.nio.charset.StandardCharsets.UTF_8); + } + } catch (IOException e) { + return "(failed to read " + file + ": " + e + ")"; } } @@ -233,12 +277,35 @@ private boolean startServer(ProcessBuilder processBuilder) throws IOException, S } private boolean attemptConnectionWithRetry() throws SQLException, InterruptedException { - final int maxAttempts = 20; - boolean started = false; + // Time-based budget rather than a fixed retry count: under JVM-wide contention from a + // parallel test run (e.g. many test classes each spawning their own external servers, + // YamlIntegrationTests starts ~5) the subprocess can take well over 10s to finish JVM + // warmup, FDB connect, and bind its gRPC port. In isolation the same start completes in + // ~3s, so the larger ceiling only kicks in on contended runs. + final long deadlineMillis = System.currentTimeMillis() + 30_000L; int attempts = 0; - while (!started && attempts < maxAttempts) { - long delay = 50L * attempts; - Thread.sleep(delay); + boolean started = false; + while (!started) { + // If the subprocess has died, no amount of retrying will help — bail immediately so + // the test fails quickly with an actionable signal instead of consuming the full budget. + if (serverProcess != null && !serverProcess.isAlive()) { + if (logger.isWarnEnabled()) { + logger.warn(KeyValueLogMessage.of("External server subprocess exited before becoming available", + LogMessageKeys.VERSION, version, + "grpc_port", getPort(), + "exit_value", serverProcess.exitValue(), + "attempts", attempts)); + } + return false; + } + if (System.currentTimeMillis() >= deadlineMillis) { + return false; + } + // Linear backoff capped at 1s so we don't pace much beyond steady state. + final long delay = Math.min(50L * attempts, 1000L); + if (delay > 0) { + Thread.sleep(delay); + } started = attemptConnection(); attempts++; if (logger.isDebugEnabled()) { @@ -276,26 +343,26 @@ public void validateConnectionVersion(@Nonnull Connection connection) throws SQL } /** - * Get a port that is currently available for the server. - * @param unavailablePorts a set of ports that are known to be unavailable. This is useful for two reasons: - * (1) when starting multiple servers, we include all previously allocated ports to avoid starting multiple - * servers on the same ports, and (2) each server needs two ports, one for GRPC, and one for HTTP, so the - * GRPC port can be noted as unavailable when asking for the http port. If nothing is unavailable, use an empty set. - * The provided set must be mutable, as it will be updated during run as ports are allocated - * @return a port that is not currently in use on the system. + * Get a port that is currently available for the server and atomically claim it in + * {@link #JVM_WIDE_CLAIMED_PORTS} so no other {@link ExternalServer} instance — in this batch + * or any concurrent batch in another test class — will pick it. The claim is released by + * {@link #stop()} or by {@link #start()} on a failed-startup path. + * @return a port that is not currently in use on the system and not claimed by another ExternalServer. */ - private int getAvailablePort(@Nonnull final Set unavailablePorts) { + private int getAvailablePort() { // running locally on my laptop, testing if a port is available takes 0 milliseconds, so no need to optimize for (int i = 1111; i < 9999; i++) { - // Add the port immediately to the set of unavailable ports. We do this because there are - // three possibilities: (1) it has already been allocated and so add returns false, (2) it - // is determined to be unavailable by isAvailable, or (3) we allocate it to this server. - // In any case, we don't want to consider this port again. Checking the set before calling - // isAvailable() also ensures that we never need to call isAvailable() more than once for any - // port - if (unavailablePorts.add(i) && isAvailable(i)) { + // Atomically claim across the whole JVM. If another ExternalServer already grabbed this + // port, add() returns false and we keep scanning. + if (!JVM_WIDE_CLAIMED_PORTS.add(i)) { + continue; + } + if (isAvailable(i)) { return i; } + // OS-level check failed (port used by something outside the JVM). Release the claim so + // a later allocator can reconsider it once whatever is occupying it goes away. + JVM_WIDE_CLAIMED_PORTS.remove(i); } return Assertions.fail("Could not find available port between 1111 and 9999"); } @@ -310,30 +377,25 @@ public static boolean isAvailable(int port) { } public void stop() { - if ((serverProcess != null) && serverProcess.isAlive()) { - serverProcess.destroy(); + try { + if ((serverProcess != null) && serverProcess.isAlive()) { + serverProcess.destroy(); + } + } finally { + // Release JVM-wide port claims unconditionally so the ports become available to other + // ExternalServer instances even if the subprocess already exited on its own. The default + // int value (0) is never added to JVM_WIDE_CLAIMED_PORTS, so calling stop() before + // start() is harmless. + JVM_WIDE_CLAIMED_PORTS.remove(grpcPort); + JVM_WIDE_CLAIMED_PORTS.remove(httpPort); } } public static void startMultiple(@Nonnull Collection servers) throws Exception { - startMultiple(servers, new HashSet<>()); - } - - public static void startMultiple(@Nonnull Collection servers, @Nonnull Set unavailablePorts) throws Exception { - final Map allocatedPorts = new HashMap<>(); + // Port uniqueness within and across batches is guaranteed by JVM_WIDE_CLAIMED_PORTS — each + // server's start() atomically claims its grpc and http ports before binding. for (ExternalServer server : servers) { - server.start(unavailablePorts); - // Threading through unavailablePorts should be enough to make sure each port is unique. - // Double check both ports, though - checkPortIsUnique(server.getPort(), server, allocatedPorts); - checkPortIsUnique(server.getHttpPort(), server, allocatedPorts); - } - } - - private static void checkPortIsUnique(int port, @Nonnull ExternalServer server, @Nonnull Map allocatedPorts) throws RelationalException { - @Nullable ExternalServer preExistingServer = allocatedPorts.putIfAbsent(port, server); - if (preExistingServer != null) { - Assert.fail("allocated duplicate port (" + server.getPort() + ") to servers for versions " + server.getVersion() + " and " + preExistingServer.getVersion()); + server.start(); } } } diff --git a/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java b/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java index 183a5acbb5..49b408e1ac 100644 --- a/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java +++ b/yaml-tests/src/test/java/com/apple/foundationdb/relational/yamltests/server/ExternalServerTest.java @@ -30,9 +30,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -73,27 +71,6 @@ void startMultiple() throws Exception { } } - @Test - void startMultipleWithAdditionalExclusions() throws Exception { - final List servers = new ArrayList<>(); - final String clusterFile = FDBTestEnvironment.randomClusterFile(); - for (int i = 0; i < 3; i++) { - servers.add(new ExternalServer(currentServerPath, clusterFile)); - } - try { - final Set explicitExcluded = Set.of(1111, 1115, 1116); - ExternalServer.startMultiple(servers, new HashSet<>(explicitExcluded)); - assertDistinctPorts(servers); - assertThat(servers) - .flatMap(ExternalServer::getPort, ExternalServer::getHttpPort) - .doesNotContainAnyElementsOf(explicitExcluded); - } finally { - for (final ExternalServer server : servers) { - server.stop(); - } - } - } - private static void assertDistinctPorts(@Nonnull Collection servers) { // we can't assert about the actual values, because one of the ports may be busy, // so assert that each server has its own port, and none of them have the same port diff --git a/yaml-tests/src/test/resources/log4j2-test.properties b/yaml-tests/src/test/resources/log4j2-test.properties index 0ec15c0797..0b5b19fa9b 100644 --- a/yaml-tests/src/test/resources/log4j2-test.properties +++ b/yaml-tests/src/test/resources/log4j2-test.properties @@ -1,10 +1,10 @@ appenders = console -loggers = yamlTestsLogger +loggers = yamlTestsLogger, embeddedConnLogger, recordStoreLogger, catalogInitLogger, instrumentedTxnLogger appender.console.type = Console appender.console.name = console appender.console.layout.type = PatternLayout -appender.console.layout.pattern = %.-5level\t%date{DEFAULT}\t%msg%n +appender.console.layout.pattern = %.-5level\t%date{DEFAULT}\t%logger{1}\t%msg%n rootLogger.level = OFF @@ -12,3 +12,27 @@ logger.yamlTestsLogger.name=com.apple.foundationdb.relational.yamltests logger.yamlTestsLogger.level = INFO logger.yamlTestsLogger.appenderRefs = stdout logger.yamlTestsLogger.appenderRef.stdout.ref = console + +# Diagnostic: surface commit-conflict enrichment from EmbeddedRelationalConnection +logger.embeddedConnLogger.name=com.apple.foundationdb.relational.recordlayer.EmbeddedRelationalConnection +logger.embeddedConnLogger.level = WARN +logger.embeddedConnLogger.appenderRefs = stdout +logger.embeddedConnLogger.appenderRef.stdout.ref = console + +# Diagnostic: surface checkVersion "dirty header" reasons from FDBRecordStore +logger.recordStoreLogger.name=com.apple.foundationdb.record.provider.foundationdb.FDBRecordStore +logger.recordStoreLogger.level = INFO +logger.recordStoreLogger.appenderRefs = stdout +logger.recordStoreLogger.appenderRef.stdout.ref = console + +logger.catalogInitLogger.name=com.apple.foundationdb.relational.yamltests.YamlTestCatalogInitializer +logger.catalogInitLogger.level = WARN +logger.catalogInitLogger.appenderRefs = stdout +logger.catalogInitLogger.appenderRef.stdout.ref = console + +# Diagnostic: TRACE emits every clear()/addWriteConflictRange() with the affected key range and +# a captured stack trace. Extremely noisy — leave off unless you're hunting a range-conflict. +logger.instrumentedTxnLogger.name=com.apple.foundationdb.record.provider.foundationdb.InstrumentedTransaction +logger.instrumentedTxnLogger.level = TRACE +logger.instrumentedTxnLogger.appenderRefs = stdout +logger.instrumentedTxnLogger.appenderRef.stdout.ref = console