All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.calcite.test.RelOptFixture Maven / Gradle / Ivy

/*
 * 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.calcite.test;

import org.apache.calcite.adapter.enumerable.EnumerableConvention;
import org.apache.calcite.plan.Context;
import org.apache.calcite.plan.ConventionTraitDef;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.hep.HepPlanner;
import org.apache.calcite.plan.hep.HepProgram;
import org.apache.calcite.plan.hep.HepProgramBuilder;
import org.apache.calcite.plan.volcano.VolcanoPlanner;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.RelFactories;
import org.apache.calcite.rel.metadata.ChainedRelMetadataProvider;
import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider;
import org.apache.calcite.rel.metadata.RelMetadataProvider;
import org.apache.calcite.rel.rules.CoreRules;
import org.apache.calcite.runtime.FlatLists;
import org.apache.calcite.runtime.Hook;
import org.apache.calcite.sql.test.SqlTestFactory;
import org.apache.calcite.sql.test.SqlTester;
import org.apache.calcite.sql.util.SqlOperatorTables;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql2rel.RelDecorrelator;
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.test.catalog.MockCatalogReaderDynamic;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.util.Closer;

import com.google.common.collect.ImmutableMap;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.UnaryOperator;

import static org.apache.calcite.test.Matchers.relIsValid;
import static org.apache.calcite.test.SqlToRelTestBase.NL;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import static java.util.Objects.requireNonNull;

/**
 * A fixture for testing planner rules.
 *
 * 

It provides a fluent API so that you can write tests by chaining method * calls. * *

A fixture is immutable. If you have two test cases that require a similar * set up (for example, the same SQL expression and set of planner rules), it is * safe to use the same fixture object as a starting point for both tests. */ class RelOptFixture { static final RelOptFixture DEFAULT = new RelOptFixture(SqlToRelFixture.TESTER, SqlTestFactory.INSTANCE, null, RelSupplier.NONE, null, null, ImmutableMap.of(), (f, r) -> r, (f, r) -> r, false, false) .withFactory(f -> f.withValidatorConfig(c -> c.withIdentifierExpansion(true))) .withRelBuilderConfig(b -> b.withPruneInputOfAggregate(false)); /** * The tester for this test. The field is vestigial; there is no * {@code withTester} method, and the same tester is always used. */ final SqlTester tester; final RelSupplier relSupplier; final SqlTestFactory factory; final @Nullable DiffRepository diffRepos; final @Nullable HepProgram preProgram; final RelOptPlanner planner; final ImmutableMap> hooks; final BiFunction before; final BiFunction after; final boolean decorrelate; final boolean lateDecorrelate; RelOptFixture(SqlTester tester, SqlTestFactory factory, @Nullable DiffRepository diffRepos, RelSupplier relSupplier, @Nullable HepProgram preProgram, RelOptPlanner planner, ImmutableMap> hooks, BiFunction before, BiFunction after, boolean decorrelate, boolean lateDecorrelate) { this.tester = requireNonNull(tester, "tester"); this.factory = factory; this.diffRepos = diffRepos; this.relSupplier = requireNonNull(relSupplier, "relSupplier"); this.before = requireNonNull(before, "before"); this.after = requireNonNull(after, "after"); this.preProgram = preProgram; this.planner = planner; this.hooks = requireNonNull(hooks, "hooks"); this.decorrelate = decorrelate; this.lateDecorrelate = lateDecorrelate; } public RelOptFixture withDiffRepos(DiffRepository diffRepos) { if (diffRepos.equals(this.diffRepos)) { return this; } return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withRelSupplier(RelSupplier relSupplier) { if (relSupplier.equals(this.relSupplier)) { return this; } return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture sql(String sql) { return withRelSupplier(RelSupplier.of(sql)); } RelOptFixture relFn(Function relFn) { return withRelSupplier(RelSupplier.of(relFn)); } public RelOptFixture withBefore( BiFunction transform) { BiFunction before0 = this.before; final BiFunction before = (sql, r) -> transform.apply(this, before0.apply(this, r)); return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withAfter( BiFunction transform) { final BiFunction after0 = this.after; final BiFunction after = (sql, r) -> transform.apply(this, after0.apply(this, r)); return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withDynamicTable() { return withCatalogReaderFactory(MockCatalogReaderDynamic::create); } public RelOptFixture withFactory(UnaryOperator transform) { final SqlTestFactory factory = transform.apply(this.factory); if (factory.equals(this.factory)) { return this; } return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withPre(HepProgram preProgram) { if (preProgram.equals(this.preProgram)) { return this; } return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withPreRule(RelOptRule... rules) { final HepProgramBuilder builder = HepProgram.builder(); for (RelOptRule rule : rules) { builder.addRuleInstance(rule); } return withPre(builder.build()); } public RelOptFixture withPlanner(RelOptPlanner planner) { if (planner.equals(this.planner)) { return this; } return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withProgram(HepProgram program) { return withPlanner(new HepPlanner(program)); } public RelOptFixture withRule(RelOptRule... rules) { final HepProgramBuilder builder = HepProgram.builder(); for (RelOptRule rule : rules) { builder.addRuleInstance(rule); } return withProgram(builder.build()); } /** * Adds a hook and a handler for that hook. Calcite will create a thread * hook (by calling {@link Hook#addThread(Consumer)}) * just before running the query, and remove the hook afterwards. */ @SuppressWarnings({"rawtypes", "unchecked"}) public RelOptFixture withHook(Hook hook, Consumer handler) { final ImmutableMap> hooks = FlatLists.append((Map) this.hooks, hook, (Consumer) handler); if (hooks.equals(this.hooks)) { return this; } return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withProperty(Hook hook, V value) { return withHook(hook, Hook.propertyJ(value)); } public RelOptFixture withRelBuilderSimplify(boolean simplify) { return withProperty(Hook.REL_BUILDER_SIMPLIFY, simplify); } public RelOptFixture withExpand(final boolean expand) { return withConfig(c -> c.withExpand(expand)); } public RelOptFixture withConfig( UnaryOperator transform) { return withFactory(f -> f.withSqlToRelConfig(transform)); } public RelOptFixture withRelBuilderConfig( UnaryOperator transform) { return withConfig(c -> c.addRelBuilderConfigTransform(transform)); } public RelOptFixture withLateDecorrelate(final boolean lateDecorrelate) { if (lateDecorrelate == this.lateDecorrelate) { return this; } return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withDecorrelate(final boolean decorrelate) { if (decorrelate == this.decorrelate) { return this; } return new RelOptFixture(tester, factory, diffRepos, relSupplier, preProgram, planner, hooks, before, after, decorrelate, lateDecorrelate); } public RelOptFixture withTrim(final boolean trim) { return withConfig(c -> c.withTrimUnusedFields(trim)); } public RelOptFixture withCatalogReaderFactory( SqlTestFactory.CatalogReaderFactory factory) { return withFactory(f -> f.withCatalogReader(factory)); } public RelOptFixture withConformance(final SqlConformance conformance) { return withFactory(f -> f.withValidatorConfig(c -> c.withConformance(conformance)) .withOperatorTable(t -> conformance.allowGeometry() ? SqlOperatorTables.chain(t, SqlOperatorTables.spatialInstance()) : t)); } public RelOptFixture withContext(final UnaryOperator transform) { return withFactory(f -> f.withPlannerContext(transform)); } public RelNode toRel() { return relSupplier.apply(this); } /** * Checks the plan for a SQL statement before/after executing a given rule, * with an optional pre-program specified by {@link #withPre(HepProgram)} * to prepare the tree. */ public void check() { check(false); } /** * Checks that the plan is the same before and after executing a given * planner. Useful for checking circumstances where rules should not fire. */ public void checkUnchanged() { check(true); } private void check(boolean unchanged) { try (Closer closer = new Closer()) { for (Map.Entry> entry : hooks.entrySet()) { closer.add(entry.getKey().addThread(entry.getValue())); } checkPlanning(unchanged); } } /** * Checks the plan for a given {@link RelNode} supplier before/after executing * a given rule, with a pre-program to prepare the tree. * * @param unchanged Whether the rule is to have no effect */ private void checkPlanning(boolean unchanged) { final RelNode relInitial = toRel(); assertNotNull(relInitial); List list = new ArrayList<>(); list.add(DefaultRelMetadataProvider.INSTANCE); RelMetadataProvider plannerChain = ChainedRelMetadataProvider.of(list); final RelOptCluster cluster = relInitial.getCluster(); cluster.setMetadataProvider(plannerChain); // Rather than a single mutable 'RelNode r', this method uses lots of // final variables (relInitial, r1, relBefore, and so forth) so that the // intermediate states of planning are visible in the debugger. final RelNode r1; if (preProgram == null) { r1 = relInitial; } else { HepPlanner prePlanner = new HepPlanner(preProgram); prePlanner.setRoot(relInitial); r1 = prePlanner.findBestExp(); } final RelNode relBefore = before.apply(this, r1); assertThat(relBefore, notNullValue()); final String planBefore = NL + RelOptUtil.toString(relBefore); final DiffRepository diffRepos = diffRepos(); diffRepos.assertEquals("planBefore", "${planBefore}", planBefore); assertThat(relBefore, relIsValid()); final RelNode r2; if (planner instanceof VolcanoPlanner) { r2 = planner.changeTraits(relBefore, relBefore.getTraitSet().replace(EnumerableConvention.INSTANCE)); } else { r2 = relBefore; } planner.setRoot(r2); final RelNode r3 = planner.findBestExp(); final RelNode r4; if (lateDecorrelate) { final String planMid = NL + RelOptUtil.toString(r3); diffRepos.assertEquals("planMid", "${planMid}", planMid); assertThat(r3, relIsValid()); final RelBuilder relBuilder = RelFactories.LOGICAL_BUILDER.create(cluster, null); r4 = RelDecorrelator.decorrelateQuery(r3, relBuilder); } else { r4 = r3; } final RelNode relAfter = after.apply(this, r4); final String planAfter = NL + RelOptUtil.toString(relAfter); if (unchanged) { assertThat(planAfter, is(planBefore)); } else { diffRepos.assertEquals("planAfter", "${planAfter}", planAfter); if (planBefore.equals(planAfter)) { throw new AssertionError("Expected plan before and after is the same.\n" + "You must use unchanged=true or call checkUnchanged"); } } assertThat(relAfter, relIsValid()); } public RelOptFixture withVolcanoPlanner(boolean topDown) { return withVolcanoPlanner(topDown, p -> RelOptUtil.registerDefaultRules(p, false, false)); } public RelOptFixture withVolcanoPlanner(boolean topDown, Consumer init) { final VolcanoPlanner planner = new VolcanoPlanner(); planner.setTopDownOpt(topDown); planner.addRelTraitDef(ConventionTraitDef.INSTANCE); init.accept(planner); return withPlanner(planner) .withDecorrelate(true) .withFactory(f -> f.withCluster(cluster -> RelOptCluster.create(planner, cluster.getRexBuilder()))); } public RelOptFixture withSubQueryRules() { return withExpand(false) .withRule(CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE, CoreRules.FILTER_SUB_QUERY_TO_CORRELATE, CoreRules.JOIN_SUB_QUERY_TO_CORRELATE); } /** * Returns the diff repository, checking that it is not null. * (It is allowed to be null because some tests that don't use a diff * repository.) */ public DiffRepository diffRepos() { return DiffRepository.castNonNull(diffRepos); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy