diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java index 7431ab6dda..3bb7c6bc4c 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlExecutionContext.java @@ -94,6 +94,7 @@ public final class YamlExecutionContext { public static final ContextOption OPTION_CORRECT_METRICS = new ContextOption<>("optionCorrectMetrics"); public static final ContextOption OPTION_CORRECT_RESULT_METADATA = new ContextOption<>("optionCorrectResultMetadata"); public static final ContextOption OPTION_ADD_RESULT_METADATA = new ContextOption<>("optionAddResultMetadata"); + public static final ContextOption OPTION_ADD_EXPLAIN = new ContextOption<>("optionAddExplain"); public static final ContextOption OPTION_SHOW_PLAN_ON_DIFF = new ContextOption<>("optionShowPlanOnDiff"); private static final URI SYSTEM_CATALOG_ADDRESS = URI.create("jdbc:embed:/__SYS?schema=CATALOG"); @@ -139,7 +140,7 @@ public static class YamlExecutionError extends RuntimeException { this.connectionFactory = factory; this.topLevelResource = topLevelResource; this.additionalOptions = additionalOptions; - if (isInCI() && (shouldCorrectExplains() || shouldCorrectMetrics() || shouldCorrectResultMetadata() || shouldAddResultMetadata())) { + if (isInCI() && (shouldCorrectExplains() || shouldCorrectMetrics() || shouldCorrectResultMetadata() || shouldAddResultMetadata() || shouldAddExplains())) { logger.error("‼️ Yamsql files cannot be modified during CI runs."); Assertions.fail("‼️ Yamsql files cannot be modified during CI runs. " + "Make sure maintenance annotations have not been checked in."); @@ -155,7 +156,7 @@ public void registerResource(@Nonnull final YamlReference.YamlResource resource) if (registeredResources.contains(resource)) { throw new RuntimeException("The resource " + resource + " is already registered."); } - if (shouldCorrectExplains() || shouldCorrectResultMetadata() || shouldAddResultMetadata()) { + if (shouldCorrectExplains() || shouldCorrectResultMetadata() || shouldAddResultMetadata() || shouldAddExplains()) { this.editedFileStream.put(resource, loadYamlResource(resource)); } if (this.expectedMetricsMap == null) { @@ -196,6 +197,10 @@ public boolean shouldAddResultMetadata() { return additionalOptions.getOrDefault(OPTION_ADD_RESULT_METADATA, false); } + public boolean shouldAddExplains() { + return additionalOptions.getOrDefault(OPTION_ADD_EXPLAIN, false); + } + public boolean correctResultMetadata(@Nonnull final YamlReference reference, @Nonnull final List actualColumns) { if (!shouldCorrectResultMetadata()) { @@ -240,7 +245,7 @@ public boolean addResultMetadata(@Nonnull final YamlReference queryReference, } public boolean correctExplain(@Nonnull final YamlReference reference, @Nonnull String actual) { - if (!shouldCorrectExplains()) { + if (!shouldCorrectExplains() && !shouldAddExplains()) { return false; } if (editedFileStream.get(reference.getResource()) == null) { @@ -472,6 +477,83 @@ public void apply(@Nonnull final List lines) { } } + public boolean addExplain(@Nonnull final YamlReference queryReference, @Nonnull String actual) { + if (!shouldAddExplains()) { + return false; + } + if (editedFileStream.get(queryReference.getResource()) == null) { + return false; + } + synchronized (this) { + final List corrections = pendingCorrections + .computeIfAbsent(queryReference.getResource(), k -> new ArrayList<>()); + final int lineNumber = queryReference.getLineNumber(); + final boolean alreadyPending = corrections.stream() + .anyMatch(c -> c instanceof AddExplainCorrection && c.getLineNumber() == lineNumber); + if (!alreadyPending) { + corrections.add(new AddExplainCorrection(queryReference, actual)); + isDirty.put(queryReference.getResource(), true); + } + } + return true; + } + + /** + * Inserts a new {@code explain:} line into the YAMSQL source file immediately before the first config entry + * that follows the {@code query:} line. Used when {@link #OPTION_ADD_EXPLAIN} is set and the query had no + * {@code explain:} block. + */ + public static final class AddExplainCorrection implements YamlCorrection { + @Nonnull + private final YamlReference queryReference; + @Nonnull + private final String actual; + + public AddExplainCorrection(@Nonnull final YamlReference queryReference, @Nonnull final String actual) { + this.queryReference = queryReference; + this.actual = actual; + } + + @Override + public int getLineNumber() { + return queryReference.getLineNumber(); + } + + @Override + public void apply(@Nonnull final List lines) { + final int queryLineIdx = queryReference.getLineNumber() - 1; // 1-based → 0-based + if (queryLineIdx < 0 || queryLineIdx >= lines.size()) { + return; + } + final String queryLine = lines.get(queryLineIdx); + + // Determine indentation from the "- query:" line + int indent = 0; + while (indent < queryLine.length() && queryLine.charAt(indent) == ' ') { + indent++; + } + + final String itemPrefix = " ".repeat(indent); + final String explainLine = itemPrefix + "- explain: \"" + actual + "\""; + + // Scan forward past any query-string continuation lines to find the first config entry + // at the same indentation level, and insert the explain line before it. + int insertIdx = queryLineIdx + 1; + for (int i = queryLineIdx + 1; i < lines.size(); i++) { + final String line = lines.get(i); + if (line.startsWith(itemPrefix + "- ")) { + insertIdx = i; + break; + } + // Stop if we've moved to the next test item (lower indentation) + if (indent > 0 && line.length() >= indent && !line.startsWith(itemPrefix)) { + break; + } + } + lines.add(insertIdx, explainLine); + } + } + @Nullable public ImmutableMap getMetricsMap() { return expectedMetricsMap; diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestConfigFilters.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestConfigFilters.java index 001a6f2865..d1dc290028 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestConfigFilters.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestConfigFilters.java @@ -108,6 +108,15 @@ boolean filter(final YamlTestConfig config) { boolean filter(final YamlTestConfig config) { return config.getRunnerOptions().getOrDefault(YamlExecutionContext.OPTION_ADD_RESULT_METADATA, false); } + }, + /** + * Used to add {@code explain} blocks to queries that don't yet have one, and to correct existing ones. + */ + ADD_EXPLAINS { + @Override + boolean filter(final YamlTestConfig config) { + return config.getRunnerOptions().getOrDefault(YamlExecutionContext.OPTION_ADD_EXPLAIN, false); + } }; /** 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 c1f62c66c7..9b0f5c18bc 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/YamlTestExtension.java @@ -20,6 +20,7 @@ package com.apple.foundationdb.relational.yamltests; +import com.apple.foundationdb.relational.yamltests.configs.AddExplains; import com.apple.foundationdb.relational.yamltests.configs.AddResultMetadata; import com.apple.foundationdb.annotation.API; import com.apple.foundationdb.relational.yamltests.configs.CorrectExplains; @@ -114,6 +115,7 @@ public void beforeAll(final ExtensionContext context) throws Exception { new CorrectExpectations(new EmbeddedConfig(clusterFiles)), new CorrectResultMetadata(new EmbeddedConfig(clusterFiles)), new AddResultMetadata(new EmbeddedConfig(clusterFiles)), + new AddExplains(new EmbeddedConfig(clusterFiles)), new ShowPlanOnDiff(new EmbeddedConfig(clusterFiles)) ); if (Boolean.parseBoolean(System.getProperty("tests.runQuick", "false"))) { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryCommand.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryCommand.java index a83e5076cb..729611b1d9 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryCommand.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryCommand.java @@ -27,6 +27,7 @@ import com.apple.foundationdb.relational.recordlayer.util.ExceptionUtil; import com.apple.foundationdb.relational.util.Assert; import com.apple.foundationdb.relational.util.Environment; +import com.apple.foundationdb.relational.yamltests.command.queryconfigs.CheckExplainConfig; import com.apple.foundationdb.relational.yamltests.command.queryconfigs.CheckResultMetadataConfig; import com.apple.foundationdb.relational.yamltests.CustomYamlConstructor; import com.apple.foundationdb.relational.yamltests.Matchers; @@ -130,6 +131,25 @@ public static Command parse(@Nonnull final YamlReference.YamlResource resource, configs = mutableConfigs; } + // When OPTION_ADD_EXPLAIN is set, inject a synthetic explain config for queries that have a + // result/unorderedResult config but no explain block yet, so the actual plan is captured and + // written into the YAMSQL source file. + if (executionContext.shouldAddExplains() + && QueryConfig.shouldExecuteExplain(executionContext) + && configs.stream().noneMatch(c -> QueryConfig.QUERY_CONFIG_EXPLAIN.equals(c.getConfigName()) + || QueryConfig.QUERY_CONFIG_EXPLAIN_CONTAINS.equals(c.getConfigName())) + && configs.stream().anyMatch(c -> QueryConfig.QUERY_CONFIG_RESULT.equals(c.getConfigName()) + || QueryConfig.QUERY_CONFIG_UNORDERED_RESULT.equals(c.getConfigName()))) { + final List mutableConfigs = new ArrayList<>(configs); + // Insert after supported_version if it is the first config, otherwise at the front. + int insertIdx = (!mutableConfigs.isEmpty() + && QueryConfig.QUERY_CONFIG_SUPPORTED_VERSION.equals(mutableConfigs.get(0).getConfigName())) + ? 1 : 0; + mutableConfigs.add(insertIdx, new CheckExplainConfig( + QueryConfig.QUERY_CONFIG_EXPLAIN, null, reference, executionContext, true, blockName)); + configs = mutableConfigs; + } + final boolean hasDebuggerConfig = configs.stream() .anyMatch(config -> Objects.equals(config.getConfigName(), QueryConfig.QUERY_CONFIG_DEBUGGER)); diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryConfig.java index ef6321673f..d20cfdaff9 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/QueryConfig.java @@ -581,7 +581,7 @@ public SemanticVersion getMaxVersion() { } } - private static boolean shouldExecuteExplain(final YamlExecutionContext executionContext) { + static boolean shouldExecuteExplain(final YamlExecutionContext executionContext) { return (! executionContext.getConnectionFactory().isMultiServer()); } diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/queryconfigs/CheckExplainConfig.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/queryconfigs/CheckExplainConfig.java index 775c358040..75fffa383f 100644 --- a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/queryconfigs/CheckExplainConfig.java +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/command/queryconfigs/CheckExplainConfig.java @@ -97,7 +97,8 @@ protected void checkResultInternal(@Nonnull String currentQuery, @Nonnull Object checkExplain(queryDescription, actualPlan, actualDot, expectedDot); final var actualPlannerMetrics = resultSet.getStruct(6); - if (isExact && actualPlannerMetrics != null) { + if (isExact && getVal() != null && actualPlannerMetrics != null + && (expectedPlannerMetricsInfo != null || executionContext.shouldCorrectMetrics())) { Objects.requireNonNull(actualDot); checkMetrics(currentQuery, setups, actualPlannerMetrics, expectedPlannerMetricsInfo, actualPlan, actualDot); } @@ -107,6 +108,18 @@ private void checkExplain(final @Nonnull String queryDescription, final @Nonnull String actualPlan, final @Nullable String actualDot, final @Nullable String expectedDot) { + // Synthetic explain config (value==null): add the actual plan to the file without comparing. + if (getVal() == null) { + if (isExact && executionContext.shouldAddExplains()) { + if (!executionContext.addExplain(getReference(), actualPlan)) { + QueryCommand.reportTestFailure("‼️ Cannot add explain plan at " + getReference()); + } else { + logger.debug(() -> "⭐️ Successfully added plan at " + getReference()); + } + } + return; + } + var success = isExact ? getVal().equals(actualPlan) : actualPlan.contains((String) getVal()); if (success) { logger.debug("✅️ plan match!"); @@ -133,7 +146,7 @@ private void checkExplain(final @Nonnull String queryDescription, for (final var diffRow : diffRows) { planDiffs.append(diffRow.getOldLine()).append('\n').append(diffRow.getNewLine()).append('\n'); } - if (isExact && executionContext.shouldCorrectExplains()) { + if (isExact && (executionContext.shouldCorrectExplains() || executionContext.shouldAddExplains())) { if (!executionContext.correctExplain(getReference(), actualPlan)) { QueryCommand.reportTestFailure("‼️ Cannot correct explain plan at " + getReference()); } else { diff --git a/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/AddExplains.java b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/AddExplains.java new file mode 100644 index 0000000000..7eceabe25e --- /dev/null +++ b/yaml-tests/src/main/java/com/apple/foundationdb/relational/yamltests/configs/AddExplains.java @@ -0,0 +1,45 @@ +/* + * AddExplains.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.configs; + +import com.apple.foundationdb.relational.yamltests.YamlExecutionContext; + +import javax.annotation.Nonnull; + +/** + * A configuration that runs an underlying configuration and adds {@code explain} blocks to all + * queries in YAMSQL files that do not already have one, and corrects existing {@code explain} blocks + * whose values have changed. + *

+ * For each query without an {@code explain:} block, the actual query plan is written into the YAMSQL + * source file immediately after the {@code query:} line (before any other config entries). For queries + * that already have an {@code explain:} block whose value differs from the actual plan, the block is + * updated in place. + *

+ *

+ * See {@link YamlExecutionContext#OPTION_ADD_EXPLAIN}. + *

+ */ +public class AddExplains extends ConfigWithOptions { + public AddExplains(@Nonnull final YamlTestConfig underlying) { + super(underlying, YamlExecutionContext.ContextOptions.of(YamlExecutionContext.OPTION_ADD_EXPLAIN, true)); + } +} diff --git a/yaml-tests/src/test/java/CheckExplainTest.java b/yaml-tests/src/test/java/CheckExplainTest.java new file mode 100644 index 0000000000..f057e0bf2f --- /dev/null +++ b/yaml-tests/src/test/java/CheckExplainTest.java @@ -0,0 +1,209 @@ +/* + * CheckExplainTest.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.YamlExecutionContext; +import com.apple.foundationdb.relational.yamltests.YamlReference; +import com.apple.foundationdb.relational.yamltests.YamlRunner; +import com.apple.foundationdb.relational.yamltests.configs.EmbeddedConfig; +import com.apple.foundationdb.relational.yamltests.configs.YamlTestConfig; +import com.apple.foundationdb.test.FDBTestEnvironment; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests that {@code explain} and {@code explainContains} checks fail correctly on a plan mismatch + * and pass when the plan matches, and that {@link YamlExecutionContext.AddExplainCorrection} inserts + * an {@code explain} line at the right position in the YAMSQL source. + *

+ * The shouldFail / shouldPass tests exercise the full {@link YamlRunner} stack (requires FDB). + * The AddExplainCorrection tests are pure-Java unit tests with no database dependency. + *

+ */ +public class CheckExplainTest { + + private static final YamlTestConfig config = new EmbeddedConfig(FDBTestEnvironment.allClusterFiles()); + + @BeforeAll + static void beforeAll() throws Exception { + config.beforeAll(); + } + + @AfterAll + static void afterAll() throws Exception { + config.afterAll(); + } + + private void doRun(String fileName) throws Exception { + new YamlRunner(fileName, config.createConnectionFactory(), YamlExecutionContext.ContextOptions.EMPTY_OPTIONS).run(); + } + + // ── negative tests ──────────────────────────────────────────────────────── + + /** + * Files that must raise {@link YamlExecutionContext.YamlExecutionError} because the + * {@code explain} / {@code explainContains} block does not match the actual plan. + */ + static Stream shouldFail() { + return Stream.of( + "wrong-plan", // exact explain string doesn't match the actual plan + "wrong-contains" // explainContains fragment is not present in the actual plan + ); + } + + @ParameterizedTest + @MethodSource("shouldFail") + void shouldFail(String filename) { + assertThrows(YamlExecutionContext.YamlExecutionError.class, () -> + doRun("check-explain/shouldFail/" + filename + ".yamsql")); + } + + // ── positive tests ──────────────────────────────────────────────────────── + + /** + * Files that must complete without error because the {@code explainContains} fragment + * is present in the actual plan. + */ + static Stream shouldPass() { + return Stream.of( + "contains" // "SCAN" is present in every full-table-scan plan + ); + } + + @ParameterizedTest + @MethodSource("shouldPass") + void shouldPass(String filename) throws Exception { + doRun("check-explain/shouldPass/" + filename + ".yamsql"); + } + + // ── AddExplainCorrection unit tests (no FDB required) ──────────────────── + + /** + * Basic case: query and result on consecutive lines, explain inserted between them. + */ + @Test + void addExplainCorrectionInsertsBeforeFirstConfig() { + final List lines = new ArrayList<>(List.of( + " - query: SELECT id FROM t1", + " - result: [{!l 1}]" + )); + applyAddExplainCorrection(lines, 1, "SCAN([IS T1])"); + assertEquals(3, lines.size()); + assertEquals(" - explain: \"SCAN([IS T1])\"", lines.get(1)); + assertEquals(" - result: [{!l 1}]", lines.get(2)); + } + + /** + * Multi-line query (block scalar): the correction must skip past the query body lines + * and insert before the first config entry at the query's indentation. + */ + @Test + void addExplainCorrectionSkipsMultiLineQueryBody() { + final List lines = new ArrayList<>(List.of( + " - query: |", + " SELECT id", + " FROM t1", + " - result: [{!l 1}]" + )); + applyAddExplainCorrection(lines, 1, "SCAN([IS T1])"); + assertEquals(5, lines.size()); + assertEquals(" - explain: \"SCAN([IS T1])\"", lines.get(3)); + assertEquals(" - result: [{!l 1}]", lines.get(4)); + } + + /** + * When there are already other config entries (e.g. {@code planHash:}) before {@code result:}, + * the explain is inserted as the first config. + */ + @Test + void addExplainCorrectionInsertsAsFirstConfig() { + final List lines = new ArrayList<>(List.of( + " - query: SELECT id FROM t1", + " - planHash: 12345", + " - result: [{!l 1}]" + )); + applyAddExplainCorrection(lines, 1, "SCAN([IS T1])"); + assertEquals(4, lines.size()); + assertEquals(" - explain: \"SCAN([IS T1])\"", lines.get(1)); + assertEquals(" - planHash: 12345", lines.get(2)); + assertEquals(" - result: [{!l 1}]", lines.get(3)); + } + + /** + * Indentation is derived from the {@code query:} line, so different indentation levels + * are handled correctly. + */ + @Test + void addExplainCorrectionRespectsIndentation() { + final List lines = new ArrayList<>(List.of( + " - query: SELECT id FROM t1", + " - result: [{!l 1}]" + )); + applyAddExplainCorrection(lines, 1, "MY PLAN"); + assertEquals(3, lines.size()); + assertEquals(" - explain: \"MY PLAN\"", lines.get(1)); + } + + /** + * When the query is the last entry in the block (no following config lines at the same + * indentation), the explain is appended immediately after the query line. + */ + @Test + void addExplainCorrectionFallsBackToAfterQueryLine() { + final List lines = new ArrayList<>(List.of( + " - query: SELECT id FROM t1" + )); + applyAddExplainCorrection(lines, 1, "SCAN([IS T1])"); + assertEquals(2, lines.size()); + assertEquals(" - explain: \"SCAN([IS T1])\"", lines.get(1)); + } + + /** + * Applying the same correction twice (deduplication is the caller's responsibility; + * this test verifies {@code apply()} itself is idempotent in position when the line + * number is stable). + */ + @Test + void addExplainCorrectionHandlesPlanWithQuotes() { + final List lines = new ArrayList<>(List.of( + " - query: SELECT id FROM t1", + " - result: [{!l 1}]" + )); + applyAddExplainCorrection(lines, 1, "SCAN | MAP"); + assertEquals(" - explain: \"SCAN | MAP\"", lines.get(1)); + } + + // ── helpers ────────────────────────────────────────────────────────────── + + private static void applyAddExplainCorrection(List lines, int oneBasedLineNumber, String plan) { + final YamlReference.YamlResource resource = YamlReference.YamlResource.base("test.yamsql"); + final YamlReference ref = resource.withLineNumber(oneBasedLineNumber); + new YamlExecutionContext.AddExplainCorrection(ref, plan).apply(lines); + } +} diff --git a/yaml-tests/src/test/resources/check-explain/shouldFail/wrong-contains.yamsql b/yaml-tests/src/test/resources/check-explain/shouldFail/wrong-contains.yamsql new file mode 100644 index 0000000000..c9e490f4f3 --- /dev/null +++ b/yaml-tests/src/test/resources/check-explain/shouldFail/wrong-contains.yamsql @@ -0,0 +1,35 @@ +# +# wrong-contains.yamsql +# +# 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. + +# Failure: explainContains substring is not present in the actual query plan. +--- +schema_template: + create table t1(id bigint, primary key(id)) +--- +setup: + steps: + - query: INSERT INTO t1 VALUES (1) +--- +test_block: + tests: + - + - query: SELECT id FROM t1 + - explainContains: "ZZZZZZZ_IMPOSSIBLE_PLAN_FRAGMENT_ZZZZZZZ" + - result: [{!l 1}] +... diff --git a/yaml-tests/src/test/resources/check-explain/shouldFail/wrong-plan.yamsql b/yaml-tests/src/test/resources/check-explain/shouldFail/wrong-plan.yamsql new file mode 100644 index 0000000000..1a2564e12f --- /dev/null +++ b/yaml-tests/src/test/resources/check-explain/shouldFail/wrong-plan.yamsql @@ -0,0 +1,35 @@ +# +# wrong-plan.yamsql +# +# 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. + +# Failure: exact explain value does not match the actual query plan. +--- +schema_template: + create table t1(id bigint, primary key(id)) +--- +setup: + steps: + - query: INSERT INTO t1 VALUES (1) +--- +test_block: + tests: + - + - query: SELECT id FROM t1 + - explain: "THIS IS NOT THE CORRECT PLAN" + - result: [{!l 1}] +... diff --git a/yaml-tests/src/test/resources/check-explain/shouldPass/contains.yamsql b/yaml-tests/src/test/resources/check-explain/shouldPass/contains.yamsql new file mode 100644 index 0000000000..70d4a486c8 --- /dev/null +++ b/yaml-tests/src/test/resources/check-explain/shouldPass/contains.yamsql @@ -0,0 +1,35 @@ +# +# contains.yamsql +# +# 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. + +# Happy-path: explainContains with a fragment present in any full-table-scan plan. +--- +schema_template: + create table t1(id bigint, primary key(id)) +--- +setup: + steps: + - query: INSERT INTO t1 VALUES (1) +--- +test_block: + tests: + - + - query: SELECT id FROM t1 + - explainContains: "SCAN" + - result: [{!l 1}] +...