Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
019b379
WIP. Fix plans for tpch
nizhikov Apr 20, 2026
9aa088e
WIP. Fix plans for tpch
nizhikov Apr 20, 2026
d113bea
WIP. Fix plans for tpch
nizhikov Apr 21, 2026
11cbb58
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
68f4b23
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
1fef5b1
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
a955be1
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
c741c69
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
15d0932
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
60ac5a0
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
27aee27
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
5f99d84
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
0526a2c
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
e0e37b3
WIP. Fix plans for tpch
nizhikov Apr 22, 2026
99b8e56
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
4f2b0ca
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
5a4577d
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
c3bc7e1
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
ef8f4f1
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
588bb39
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
a3af4ad
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
6784fe0
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
f17b799
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
95e5714
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
6e97d22
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
7cd83ca
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
b49c93c
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
8472c03
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
d81e611
WIP. Fix plans for tpch
nizhikov Apr 23, 2026
59b0bdc
Merge branch 'master' into IGNITE-28596
nizhikov Apr 23, 2026
b9de799
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
34bb65b
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
e706a74
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
179de98
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
6605cec
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
6f04b0d
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
6a4806d
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
16a4fe6
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
035eedb
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
fdc78dc
WIP. Fix plans for tpch
nizhikov Apr 24, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ private TpchHelper() {
* @param ignite Ignite instance.
*/
public static void createTables(Ignite ignite) {
try (InputStream inputStream = TpchHelper.class.getResourceAsStream("ddl.sql")) {
try (InputStream inputStream = TpchHelper.class.getResourceAsStream("/tpch/ddl.sql")) {
if (inputStream == null)
throw new RuntimeException("Failed to create TPC-H tables: ddl.sql not found in resources");

Expand Down Expand Up @@ -148,7 +148,11 @@ public static void generateDataset(double scale, Path datasetDir) throws IOExcep
* @param queryId Query identifier.
*/
public static String getQuery(int queryId) {
try (InputStream inputStream = TpchHelper.class.getResourceAsStream(String.format("q%d.sql", queryId))) {
String path = queryId == 15
? String.format("q%d.sql", queryId)
: String.format("/tpch/q%d.sql", queryId);

try (InputStream inputStream = TpchHelper.class.getResourceAsStream(path)) {
if (inputStream == null)
throw new RuntimeException(String.format("Query Q%d is not found in resources", queryId));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.internal.processors.query.calcite.planner.tpc;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import com.google.common.io.CharStreams;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.ignite.Ignite;
import org.apache.ignite.cache.query.FieldsQueryCursor;
import org.apache.ignite.cache.query.SqlFieldsQuery;
import org.apache.ignite.cache.query.annotations.QuerySqlFunction;
import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.IgnitionEx;
import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor;
import org.apache.ignite.internal.processors.query.calcite.integration.tpch.TpchHelper;
import org.apache.ignite.internal.processors.query.calcite.planner.AbstractPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.tpc.PlanChecker.AfterPlansTest;
import org.apache.ignite.internal.processors.query.calcite.planner.tpc.PlanChecker.BeforePlansTest;
import org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
import org.apache.ignite.testframework.GridTestUtils;
import org.junit.Test;
import org.junit.runners.Parameterized;

import static org.apache.ignite.internal.processors.query.calcite.planner.tpc.PlanChecker.RSRC_DIR;
import static org.apache.ignite.internal.processors.query.calcite.planner.tpc.PlanChecker.sqlTestName;

/**
* Abstract test class to ensure a planner generates expected plan for TPC queries.
*/
public class AbstractTpcQueryPlannerTest extends AbstractPlannerTest {
/** Set to {@code true} to write plan files, instead of checking. */
private static final boolean UPDATE_PLAN = false;

/** Server node to run queries on. */
private static IgniteEx srv;

/** Query id. Set by {@link PlanChecker}. */
@Parameterized.Parameter
public String queryId;

/** Run once, before all queries check. */
@BeforePlansTest
public static void startAll(Class<?> testClass) throws Exception {
AbstractTpcQueryPlannerTest mock = new AbstractTpcQueryPlannerTest();

mock.beforeFirstTest();

IgniteConfiguration cfg = mock.getConfiguration("server");

cfg.getSqlConfiguration()
.setQueryEnginesConfiguration(new CalciteQueryEngineConfiguration().setDefault(true));

srv = (IgniteEx)IgnitionEx.start(cfg);

srv.getOrCreateCache(new CacheConfiguration<>("mock")
.setSqlFunctionClasses(AbstractTpcQueryPlannerTest.TpchUDF.class)
.setSqlSchema("PUBLIC"));

TpchHelper.createTables(srv);

/*
TpchHelper.fillTables(srv, 0.01);

Check warning on line 90 in modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/tpc/AbstractTpcQueryPlannerTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=apache_ignite&issues=AZ2_79AVHqlQ1y1TQu2U&open=AZ2_79AVHqlQ1y1TQu2U&pullRequest=13068

TpchHelper.collectSqlStatistics(srv);
*/
}

/** Run once, after all queries check. */
@AfterPlansTest
public static void stopAll(Class<?> testClass) {
if (srv != null) {
srv.close();

srv = null;
}
}

/** Test single query. */
@Test
public void testQuery() throws Exception {
String actualPlan = queryPlan();

if (UPDATE_PLAN) {
updatePlan(actualPlan);

return;
}

PlanTemplate pt = new PlanTemplate(loadFromResource(String.format("%s/%s.plan", sqlTestName(getClass()), queryId)));

if (!pt.match(actualPlan)) {
// This assertion will print nice diff in IDE that will help to investigate.
// Test will fail anyway.
assertEquals(pt.template, actualPlan);

assert false : "Should not happen";
}
}

/** {@inheritDoc} */
@Override protected boolean isSafeTopology() {
return false;
}

/** */
private String queryPlan() throws Exception {
Map<String, IgniteSchema> schemas = GridTestUtils.getFieldValue(
((CalciteQueryProcessor)srv.context().query().defaultQueryEngine()).schemaHolder(),
"igniteSchemas"
);

PlanningContext ctx = plannerCtx(loadFromResource(sqlTestName(getClass()) + "/" + queryId + ".sql"), schemas.values(), null);

return new PlanTemplate(RelOptUtil.toString(physicalPlan(ctx), SqlExplainLevel.ALL_ATTRIBUTES)).template;
}

/** */
private void updatePlan(String newPlan) {
Path targetDir = Path.of(RSRC_DIR + sqlTestName(getClass()));

// A targetDirectory must be specified by hand when expected plans are generated.
if (targetDir == null) {
throw new RuntimeException("Please provide target directory to where save generated plans."
+ " Usually plans are kept in resource folder of tests within the same module.");
}

try {
Files.createDirectories(targetDir);

Files.writeString(targetDir.resolve(String.format("%s.plan", queryId)), newPlan);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* Execute SQL query.
*
* @param ignite Ignite.
* @param sql SQL query.
* @param params Query parameters.
*/
public static List<List<?>> sql(Ignite ignite, String sql, Object... params) {
SqlFieldsQuery qry = new SqlFieldsQuery(sql).setArgs(params);

try (FieldsQueryCursor<List<?>> cur = ((IgniteEx)ignite).context().query().querySqlFields(qry, false)) {
return cur.getAll();
}
}

/** */
public static class TpchUDF {
/** */
@QuerySqlFunction(alias = "SUBSTR")
public static String substr(String str, int from, int cnt) {
return str.substring(from, from + cnt);
}
}

/**
* Loads resource with given name as string.
*
* @param resource Name of the resource to load.
* @return Resource as string.
*/
private static String loadFromResource(String resource) {
if (resource.startsWith(RSRC_DIR))
resource = resource.substring(RSRC_DIR.length() + 1);

try (InputStream is = AbstractTpcQueryPlannerTest.class.getClassLoader().getResourceAsStream(resource)) {
if (is == null)
throw new IllegalArgumentException("Resource does not exist: " + resource);

try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
return CharStreams.toString(reader);
}
}
catch (IOException e) {
throw new UncheckedIOException("I/O operation failed: " + resource, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.ignite.internal.processors.query.calcite.planner.tpc;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.TestClass;
import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters;
import org.junit.runners.parameterized.TestWithParameters;

/**
* Suite to check queries plans.
* Makes pretty test names to simplify CI tracking.
*/
public class PlanChecker extends Suite {
/** */
public static final String RSRC_DIR = "./src/test/resources/";

/**
* Only called reflectively. Do not use programmatically.
*/
public PlanChecker(Class<?> klass) throws Throwable {
super(klass, createRunnersForParameters(new TestClass(klass)).collect(Collectors.toList()));
}

/** */
private static Stream<Runner> createRunnersForParameters(TestClass testClass) throws IOException {
return Files.list(Path.of(RSRC_DIR, sqlTestName(testClass.getJavaClass())))
.filter(p -> p.toString().endsWith(".sql") && !p.toString().endsWith("ddl.sql"))
.sorted()
.map(p -> {
String qryId = p.getFileName().toString().replace(".sql", "");

try {
return new BlockJUnit4ClassRunnerWithParameters(
new TestWithParameters("[queryId=" + qryId + "]", testClass, Collections.singletonList(qryId))
);
}
catch (InitializationError e) {
throw new RuntimeException(e);
}
});
}

/** {@inheritDoc} */
@Override public void run(RunNotifier notifier) {
runAnnotated(BeforePlansTest.class);

try {
super.run(notifier);
}
finally {
runAnnotated(AfterPlansTest.class);
}
}

/** Runs static void method of test class annotated with the specific annotation. */
private void runAnnotated(Class<? extends Annotation> annotation) {
getTestClass().getAnnotatedMethods(annotation).forEach(m -> {
List<Throwable> errors = new ArrayList<>();

m.validatePublicVoid(true, errors);

try {
if (!errors.isEmpty())
throw errors.get(0);

m.invokeExplosively(getTestClass().getJavaClass(), getTestClass().getJavaClass());
}
catch (Throwable e) {
throw new RuntimeException(e);
}
});
}

/** @return SQL test name. */
public static String sqlTestName(Class<?> klass) {
PlanChecker.PlansTest desc = klass.getAnnotation(PlanChecker.PlansTest.class);

if (desc == null)
throw new IllegalStateException("Test class must be annotated with @" + PlanChecker.PlansTest.class.getSimpleName());

if (desc.name().isEmpty())
throw new IllegalStateException("Please, set test name with the @PlanTest(name=\"XXX\")");

return desc.name();
}

/** */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PlansTest {
/** @return Test name. */
String name() default "";
}

/** */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface BeforePlansTest {
// No-op.
}

/** */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AfterPlansTest {
// No-op.
}
}
Loading
Loading