Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -426,11 +426,16 @@ static Stream<InComparisonCase> selectMaxWithInOrderByMax() {
);
}

// SQL Port status: (#4843) ported, plan generated
// SQL Port: cascades-query-tests/permuted-min-max.yamsql
@DualPlannerTest(planner = DualPlannerTest.Planner.CASCADES)
@ParameterizedTest
@MethodSource
void selectMaxWithInOrderByMax(InComparisonCase inComparisonCase) throws Exception {
Assumptions.assumeTrue(useCascadesPlanner);
// LiteralValue/Parameter IN Source is not compatible with Cascades (#4816)
Assumptions.assumeFalse(inComparisonCase.getComparison() instanceof Comparisons.ListComparison ||
inComparisonCase.getComparison() instanceof Comparisons.ParameterComparison);
final RecordMetaDataHook hook = metaData -> metaData.addIndex(metaData.getRecordType("MySimpleRecord"), maxUniqueBy2And3());
complexQuerySetup(hook);
try (FDBRecordContext context = openContext()) {
Expand Down Expand Up @@ -508,11 +513,15 @@ static Stream<InComparisonCase> testMaxWithInAndDupes() {
);
}

// SQL Port status: (#4843) ported, plan generated
@DualPlannerTest(planner = DualPlannerTest.Planner.CASCADES)
@ParameterizedTest
@MethodSource
void testMaxWithInAndDupes(InComparisonCase inComparisonCase) throws Exception {
Assumptions.assumeTrue(useCascadesPlanner);
// LiteralValue/Parameter IN Source is not compatible with Cascades (#4816)
Assumptions.assumeFalse(inComparisonCase.getComparison() instanceof Comparisons.ListComparison ||
inComparisonCase.getComparison() instanceof Comparisons.ParameterComparison);
final RecordMetaDataHook hook = metaData -> metaData.addIndex(metaData.getRecordType("MySimpleRecord"), max2ByStrValueAnd3());
complexQuerySetup(hook);

Expand Down Expand Up @@ -650,11 +659,15 @@ static Stream<InComparisonCase> testSortedMaxWithInOnRepeater() {
);
}

// SQL Port status: (#4843) ported, plan generated
@DualPlannerTest(planner = DualPlannerTest.Planner.CASCADES)
@ParameterizedTest
@MethodSource
void testSortedMaxWithInOnRepeater(InComparisonCase inComparisonCase) {
Assumptions.assumeTrue(useCascadesPlanner);
// LiteralValue/Parameter IN Source is not compatible with Cascades (#4816)
Assumptions.assumeFalse(inComparisonCase.getComparison() instanceof Comparisons.ListComparison ||
inComparisonCase.getComparison() instanceof Comparisons.ParameterComparison);
final RecordMetaDataHook hook = metaData -> metaData.addIndex(metaData.getRecordType("MySimpleRecord"), max3ByStrValueRepeaterAnd2());
setUpWithRepeaters(hook);

Expand Down Expand Up @@ -760,11 +773,15 @@ static Stream<InComparisonCase> testMaxUniqueByStr2And3WithDifferentOrderingKeys
);
}

// SQL Port status: (#4843) ported, plan generated
@DualPlannerTest(planner = DualPlannerTest.Planner.CASCADES)
@ParameterizedTest(name = "testMaxUniqueByStr2And3WithDifferentOrderingKeys[inComparisonCase={0}]")
@MethodSource
void testMaxUniqueByStr2And3WithDifferentOrderingKeys(InComparisonCase inComparisonCase) throws Exception {
Assumptions.assumeTrue(useCascadesPlanner);
// LiteralValue/Parameter IN Source is not compatible with Cascades (#4816)
Assumptions.assumeFalse(inComparisonCase.getComparison() instanceof Comparisons.ListComparison ||
inComparisonCase.getComparison() instanceof Comparisons.ParameterComparison);
final RecordMetaDataHook hook = metaData -> {
metaData.addIndex(metaData.getRecordType("MySimpleRecord"), maxUniqueByStrValueOrderBy2And3());
metaData.removeIndex("MySimpleRecord$num_value_unique"); // get rid of unique index as we don't want uniqueness constraint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -704,11 +704,14 @@ void testSubselectHasNullOnEmptyAndIsNullPredicate() {
*
* @param inComparison whether the inner existential predicate should be an IN or EQUALS predicate
*/
// SQL Port status: (#4843) ported, plan generated
@DualPlannerTest(planner = DualPlannerTest.Planner.CASCADES)
@ParameterizedTest
@BooleanSource
void testSimpleExistentialPredicateOnSimpleIndex(boolean inComparison) {
Assumptions.assumeTrue(useCascadesPlanner);
// LiteralValue/Parameter IN Source is not compatible with Cascades (#4816)
Assumptions.assumeFalse(inComparison);
final Index index = new Index("Restaurant$tag.value", Key.Expressions.field("tags").nest(Key.Expressions.field("values", KeyExpression.FanType.FanOut).nest("value")));
CascadesPlanner cascadesPlanner = setUpWithNullableArray(metaDataBuilder -> metaDataBuilder.addIndex("RestaurantRecord", index));
final var tagValueParam = "t";
Expand Down Expand Up @@ -762,9 +765,11 @@ void testSimpleExistentialPredicateOnSimpleIndex(boolean inComparison) {
}
}

// SQL Port status: (#4843) ported, plan generated
@DualPlannerTest(planner = DualPlannerTest.Planner.CASCADES)
void testEqualityAndSimpleExistentialPredicate() {
Assumptions.assumeTrue(useCascadesPlanner);
// LiteralValue/Parameter IN Source is not compatible with Cascades (#4816)
Assumptions.abort();
final Index index = new Index("Restaurant$name-tagValue", Key.Expressions.concat(
Key.Expressions.field("name"),
Key.Expressions.field("tags").nest(Key.Expressions.field("values", KeyExpression.FanType.FanOut).nest("value"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ public boolean supportsTestTemplate(ExtensionContext context) {
FDBRecordStoreQueryTestBase.class.isAssignableFrom(context.getRequiredTestClass());
}

/**
* Allows a parameterized test that only runs the old planner to provide no invocation contexts of its own, as the
* {@link ParameterizedTest} extension already runs it with the old planner.
*/
@Override
public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext context) {
return AnnotationUtils.isAnnotated(context.getTestMethod(), ParameterizedTest.class) &&
AnnotationUtils.findAnnotation(context.getTestMethod(), DualPlannerTest.class)
.map(annotation -> annotation.planner() == DualPlannerTest.Planner.OLD)
.orElse(false);
}

@Override
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
final Optional<DualPlannerTest> annotationOptional =
Expand All @@ -79,7 +91,8 @@ public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContex
// Create a Cascades invocation context. The non-Cascades planner will be run via the ParameterizedTest
// extension running the test on its own, which will run the old planner by default.
// Note that, as a consequence, this means that CASCADES (only) mode does not work with parameterized tests,
// and the old planner will always be run.
// and the old planner will always be run. OLD (only) mode provides no contexts here, which
// mayReturnZeroTestTemplateInvocationContexts allows.
// See: https://github.com/FoundationDB/fdb-record-layer/issues/2354
if (annotation.planner() != DualPlannerTest.Planner.OLD) {
return nestedProvider.provideTestTemplateInvocationContexts(context).map(existingContext ->
Expand Down
127 changes: 127 additions & 0 deletions yaml-tests/src/test/java/CascadesQueryTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* CascadesQueryTests.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.
*/

import com.apple.foundationdb.relational.yamltests.YamlTest;
import org.junit.jupiter.api.TestTemplate;

/**
* Query-planning coverage ported from the Cascades tests in {@code fdb-record-layer-core}'s
* {@code com.apple.foundationdb.record.provider.foundationdb.query} package.
*
* <p>Those tests drive the Cascades planner through the {@code RecordQuery} builder API; the SQL layer
* drives the same planner from SQL text, so most of them can be expressed declaratively here
* instead. Each yamsql file corresponds to one source test class, and every query carries a comment
* naming the source test method it came from. Where a source class needs more than one index
* configuration the file is split, with the variant named in a suffix — the planner has to be forced
* into a plan by the schema it is given, and {@code USE INDEX} cannot stand in for a missing index
* because a hinted query drops the primary-scan candidate entirely.
*
* <p>See {@code cascades-query-tests/README.md} for the source-class mapping, the authoring
* conventions, and the list of tests that cannot be ported.
*/
@YamlTest
class CascadesQueryTests {
private static final String PREFIX = "cascades-query-tests";

@TestTemplate
void inQueryCompoundIndex(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-compound-index.yamsql");
}

@TestTemplate
void inQueryHeader(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-header.yamsql");
}

@TestTemplate
void inQueryMultiIndex(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-multi-index.yamsql");
}

@TestTemplate
void inQuery(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query.yamsql");
}

@TestTemplate
void inQueryOrCompound(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-or-compound.yamsql");
}

@TestTemplate
void inQueryOr(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-or.yamsql");
}

@TestTemplate
void inQueryUnionIntersectionNoNv3(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-union-intersection-no-nv3.yamsql");
}

@TestTemplate
void inQueryUnionIntersection(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-union-intersection.yamsql");
}

@TestTemplate
void inQueryUnionSargable(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-union-sargable.yamsql");
}

@TestTemplate
void inQueryOrOverlap(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-or-overlap.yamsql");
}

@TestTemplate
void permutedMinMaxNumValue2(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/permuted-min-max-num-value-2.yamsql");
}

@TestTemplate
void permutedMinMaxOrderingKeys(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/permuted-min-max-ordering-keys.yamsql");
}

@TestTemplate
void permutedMinMaxRepeater(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/permuted-min-max-repeater.yamsql");
}

@TestTemplate
void permutedMinMax(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/permuted-min-max.yamsql");
}

@TestTemplate
void simpleQueryGraphNameTagIndex(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/simple-query-graph-name-tag-index.yamsql");
}

@TestTemplate
void simpleQueryGraphTagIndex(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/simple-query-graph-tag-index.yamsql");
}

@TestTemplate
void inQuerySortedExtraUnorderedColumns(YamlTest.Runner runner) throws Exception {
runner.runYamsql(PREFIX + "/in-query-sorted-extra-unordered-columns.yamsql");
}
}
Loading
Loading