blob: 5f9a0adad0bc49c0d86faa57cee3cc01fc8a6af3 [file] [log] [blame]
/*
* 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.config.CalciteConnectionConfig;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.plan.Context;
import org.apache.calcite.plan.Contexts;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptSchema;
import org.apache.calcite.plan.RelOptSchemaWithSampling;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.prepare.Prepare;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelDistribution;
import org.apache.calcite.rel.RelDistributions;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelReferentialConstraint;
import org.apache.calcite.rel.RelRoot;
import org.apache.calcite.rel.RelShuttle;
import org.apache.calcite.rel.core.Correlate;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.core.RelFactories;
import org.apache.calcite.rel.logical.LogicalTableScan;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.schema.ColumnStrategy;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.test.SqlTestFactory;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.util.SqlOperatorTables;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorCatalogReader;
import org.apache.calcite.sql.validate.SqlValidatorImpl;
import org.apache.calcite.sql.validate.SqlValidatorTable;
import org.apache.calcite.sql2rel.RelFieldTrimmer;
import org.apache.calcite.sql2rel.SqlToRelConverter;
import org.apache.calcite.sql2rel.StandardConvertletTable;
import org.apache.calcite.test.catalog.MockCatalogReader;
import org.apache.calcite.test.catalog.MockCatalogReaderDynamic;
import org.apache.calcite.test.catalog.MockCatalogReaderSimple;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.TestUtil;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* SqlToRelTestBase is an abstract base for tests which involve conversion from
* SQL to relational algebra.
*
* <p>SQL statements to be translated can use the schema defined in
* {@link MockCatalogReader}; note that this is slightly different from
* Farrago's SALES schema. If you get a parser or validator error from your test
* SQL, look down in the stack until you see "Caused by", which will usually
* tell you the real error.
*/
public abstract class SqlToRelTestBase {
//~ Static fields/initializers ---------------------------------------------
protected static final String NL = System.getProperty("line.separator");
protected static final Supplier<RelDataTypeFactory> DEFAULT_TYPE_FACTORY_SUPPLIER =
Suppliers.memoize(() ->
new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT))::get;
//~ Instance fields --------------------------------------------------------
protected final Tester tester = createTester();
// Same as tester but without implicit type coercion.
protected final Tester strictTester = tester.enableTypeCoercion(false);
protected Tester createTester() {
final TesterImpl tester =
new TesterImpl(getDiffRepos(), false, false, false, true, null, null,
MockRelOptPlanner::new, UnaryOperator.identity(),
SqlConformanceEnum.DEFAULT, UnaryOperator.identity(), DEFAULT_TYPE_FACTORY_SUPPLIER);
return tester.withConfig(c ->
c.withTrimUnusedFields(true)
.withExpand(true)
.addRelBuilderConfigTransform(b ->
b.withAggregateUnique(true)
.withPruneInputOfAggregate(false)));
}
protected Tester getTesterWithDynamicTable() {
return tester.withCatalogReaderFactory(MockCatalogReaderDynamic::new);
}
/**
* Returns the default diff repository for this test, or null if there is
* no repository.
*
* <p>The default implementation returns null.
*
* <p>Sub-classes that want to use a diff repository can override.
* Sub-sub-classes can override again, inheriting test cases and overriding
* selected test results.
*
* <p>And individual test cases can override by providing a different
* tester object.
*
* @return Diff repository
*/
protected DiffRepository getDiffRepos() {
return null;
}
/**
* Checks that every node of a relational expression is valid.
*
* @param rel Relational expression
*/
public static void assertValid(RelNode rel) {
SqlToRelConverterTest.RelValidityChecker checker =
new SqlToRelConverterTest.RelValidityChecker();
checker.go(rel);
assertEquals(0, checker.invalidCount);
}
//~ Inner Interfaces -------------------------------------------------------
/**
* Helper class which contains default implementations of methods used for
* running sql-to-rel conversion tests.
*/
public interface Tester {
/**
* Converts a SQL string to a {@link RelNode} tree.
*
* @param sql SQL statement
* @return Relational expression, never null
*/
RelRoot convertSqlToRel(String sql);
/**
* Converts an expression string to {@link RexNode}.
*
* @param expr The expression
*/
RexNode convertExprToRex(String expr);
SqlNode parseQuery(String sql) throws Exception;
/**
* Factory method to create a {@link SqlValidator}.
*/
SqlValidator createValidator(
SqlValidatorCatalogReader catalogReader,
RelDataTypeFactory typeFactory);
/**
* Factory method for a
* {@link org.apache.calcite.prepare.Prepare.CatalogReader}.
*/
Prepare.CatalogReader createCatalogReader(
RelDataTypeFactory typeFactory);
RelOptPlanner createPlanner();
/**
* Returns the {@link SqlOperatorTable} to use.
*/
SqlOperatorTable getOperatorTable();
/**
* Returns the SQL dialect to test.
*/
SqlConformance getConformance();
/**
* Checks that a SQL statement converts to a given plan.
*
* @param sql SQL query
* @param plan Expected plan
*/
void assertConvertsTo(
String sql,
String plan);
/**
* Checks that a SQL statement converts to a given plan, optionally
* trimming columns that are not needed.
*
* @param sql SQL query
* @param plan Expected plan
* @param trim Whether to trim columns that are not needed
*/
void assertConvertsTo(
String sql,
String plan,
boolean trim);
/**
* Checks that a SQL statement converts to a given plan, optionally
* trimming columns that are not needed.
*
* @param sql SQL query or expression
* @param plan Expected plan
* @param trim Whether to trim columns that are not needed
* @param query True if {@code sql} is a query, false if it is an expression
*/
void assertConvertsTo(
String sql,
String plan,
boolean trim,
boolean query);
/**
* Returns the diff repository.
*
* @return Diff repository
*/
DiffRepository getDiffRepos();
/**
* Returns the validator.
*
* @return Validator
*/
SqlValidator getValidator();
/** Returns a tester that optionally decorrelates queries. */
Tester withDecorrelation(boolean enable);
/** Returns a tester that optionally decorrelates queries after planner
* rules have fired. */
Tester withLateDecorrelation(boolean enable);
/** Returns a tester that applies a transform to its
* {@code SqlToRelConverter.Config} before it uses it. */
Tester withConfig(UnaryOperator<SqlToRelConverter.Config> transform);
/** Returns a tester with a {@link SqlConformance}. */
Tester withConformance(SqlConformance conformance);
/** Returns a tester with a specified if allows type coercion. */
Tester enableTypeCoercion(boolean typeCoercion);
Tester withCatalogReaderFactory(
SqlTestFactory.MockCatalogReaderFactory factory);
/** Returns a tester that optionally trims unused fields. */
Tester withTrim(boolean enable);
Tester withClusterFactory(Function<RelOptCluster, RelOptCluster> function);
boolean isLateDecorrelate();
/** Returns a tester that uses a given context. */
Tester withContext(UnaryOperator<Context> transform);
/** Returns a tester that uses a type factory. */
Tester withTypeFactorySupplier(Supplier<RelDataTypeFactory> typeFactorySupplier);
/** Trims a RelNode. */
RelNode trimRelNode(RelNode relNode);
SqlNode parseExpression(String expr) throws Exception;
/** Returns a tester that applies the given transform to a validator before
* using it. */
Tester withValidatorTransform(UnaryOperator<SqlValidator> transform);
}
//~ Inner Classes ----------------------------------------------------------
/**
* Mock implementation of {@link RelOptSchema}.
*/
protected static class MockRelOptSchema implements RelOptSchemaWithSampling {
private final SqlValidatorCatalogReader catalogReader;
private final RelDataTypeFactory typeFactory;
public MockRelOptSchema(
SqlValidatorCatalogReader catalogReader,
RelDataTypeFactory typeFactory) {
this.catalogReader = catalogReader;
this.typeFactory = typeFactory;
}
public RelOptTable getTableForMember(List<String> names) {
final SqlValidatorTable table =
catalogReader.getTable(names);
final RelDataType rowType = table.getRowType();
final List<RelCollation> collationList = deduceMonotonicity(table);
if (names.size() < 3) {
String[] newNames2 = {"CATALOG", "SALES", ""};
List<String> newNames = new ArrayList<>();
int i = 0;
while (newNames.size() < newNames2.length) {
newNames.add(i, newNames2[i]);
++i;
}
names = newNames;
}
return createColumnSet(table, names, rowType, collationList);
}
private List<RelCollation> deduceMonotonicity(SqlValidatorTable table) {
final RelDataType rowType = table.getRowType();
final List<RelCollation> collationList = new ArrayList<>();
// Deduce which fields the table is sorted on.
int i = -1;
for (RelDataTypeField field : rowType.getFieldList()) {
++i;
final SqlMonotonicity monotonicity =
table.getMonotonicity(field.getName());
if (monotonicity != SqlMonotonicity.NOT_MONOTONIC) {
final RelFieldCollation.Direction direction =
monotonicity.isDecreasing()
? RelFieldCollation.Direction.DESCENDING
: RelFieldCollation.Direction.ASCENDING;
collationList.add(
RelCollations.of(new RelFieldCollation(i, direction)));
}
}
return collationList;
}
public RelOptTable getTableForMember(
List<String> names,
final String datasetName,
boolean[] usedDataset) {
final RelOptTable table = getTableForMember(names);
// If they're asking for a sample, just for test purposes,
// assume there's a table called "<table>:<sample>".
RelOptTable datasetTable =
new DelegatingRelOptTable(table) {
public List<String> getQualifiedName() {
final List<String> list =
new ArrayList<>(super.getQualifiedName());
list.set(
list.size() - 1,
list.get(list.size() - 1) + ":" + datasetName);
return ImmutableList.copyOf(list);
}
};
if (usedDataset != null) {
assert usedDataset.length == 1;
usedDataset[0] = true;
}
return datasetTable;
}
protected MockColumnSet createColumnSet(
SqlValidatorTable table,
List<String> names,
final RelDataType rowType,
final List<RelCollation> collationList) {
return new MockColumnSet(names, rowType, collationList);
}
public RelDataTypeFactory getTypeFactory() {
return typeFactory;
}
public void registerRules(RelOptPlanner planner) {
}
/** Mock column set. */
protected class MockColumnSet implements RelOptTable {
private final List<String> names;
private final RelDataType rowType;
private final List<RelCollation> collationList;
protected MockColumnSet(
List<String> names,
RelDataType rowType,
final List<RelCollation> collationList) {
this.names = ImmutableList.copyOf(names);
this.rowType = rowType;
this.collationList = collationList;
}
public <T> T unwrap(Class<T> clazz) {
if (clazz.isInstance(this)) {
return clazz.cast(this);
}
return null;
}
public List<String> getQualifiedName() {
return names;
}
public double getRowCount() {
// use something other than 0 to give costing tests
// some room, and make emps bigger than depts for
// join asymmetry
if (Iterables.getLast(names).equals("EMP")) {
return 1000;
} else {
return 100;
}
}
public RelDataType getRowType() {
return rowType;
}
public RelOptSchema getRelOptSchema() {
return MockRelOptSchema.this;
}
public RelNode toRel(ToRelContext context) {
return LogicalTableScan.create(context.getCluster(), this,
context.getTableHints());
}
public List<RelCollation> getCollationList() {
return collationList;
}
public RelDistribution getDistribution() {
return RelDistributions.BROADCAST_DISTRIBUTED;
}
public boolean isKey(ImmutableBitSet columns) {
return false;
}
public List<ImmutableBitSet> getKeys() {
return ImmutableList.of();
}
public List<RelReferentialConstraint> getReferentialConstraints() {
return ImmutableList.of();
}
public List<ColumnStrategy> getColumnStrategies() {
throw new UnsupportedOperationException();
}
public Expression getExpression(Class clazz) {
return null;
}
public RelOptTable extend(List<RelDataTypeField> extendedFields) {
final RelDataType extendedRowType =
getRelOptSchema().getTypeFactory().builder()
.addAll(rowType.getFieldList())
.addAll(extendedFields)
.build();
return new MockColumnSet(names, extendedRowType, collationList);
}
}
}
/** Table that delegates to a given table. */
private static class DelegatingRelOptTable implements RelOptTable {
private final RelOptTable parent;
DelegatingRelOptTable(RelOptTable parent) {
this.parent = parent;
}
public <T> T unwrap(Class<T> clazz) {
if (clazz.isInstance(this)) {
return clazz.cast(this);
}
return parent.unwrap(clazz);
}
public Expression getExpression(Class clazz) {
return parent.getExpression(clazz);
}
public RelOptTable extend(List<RelDataTypeField> extendedFields) {
return parent.extend(extendedFields);
}
public List<String> getQualifiedName() {
return parent.getQualifiedName();
}
public double getRowCount() {
return parent.getRowCount();
}
public RelDataType getRowType() {
return parent.getRowType();
}
public RelOptSchema getRelOptSchema() {
return parent.getRelOptSchema();
}
public RelNode toRel(ToRelContext context) {
return LogicalTableScan.create(context.getCluster(), this,
context.getTableHints());
}
public List<RelCollation> getCollationList() {
return parent.getCollationList();
}
public RelDistribution getDistribution() {
return parent.getDistribution();
}
public boolean isKey(ImmutableBitSet columns) {
return parent.isKey(columns);
}
public List<ImmutableBitSet> getKeys() {
return parent.getKeys();
}
public List<RelReferentialConstraint> getReferentialConstraints() {
return parent.getReferentialConstraints();
}
public List<ColumnStrategy> getColumnStrategies() {
return parent.getColumnStrategies();
}
}
/**
* Default implementation of {@link Tester}, using mock classes
* {@link MockRelOptSchema} and {@link MockRelOptPlanner}.
*/
public static class TesterImpl implements Tester {
private RelOptPlanner planner;
private SqlOperatorTable opTab;
private final DiffRepository diffRepos;
private final boolean enableDecorrelate;
private final boolean enableLateDecorrelate;
private final boolean enableTrim;
private final boolean enableTypeCoercion;
private final Function<Context, RelOptPlanner> plannerFactory;
private final SqlConformance conformance;
private final SqlTestFactory.MockCatalogReaderFactory catalogReaderFactory;
private final Function<RelOptCluster, RelOptCluster> clusterFactory;
private final Supplier<RelDataTypeFactory> typeFactorySupplier;
private final UnaryOperator<SqlToRelConverter.Config> configTransform;
private final UnaryOperator<Context> contextTransform;
private final UnaryOperator<SqlValidator> validatorTransform;
/** Creates a TesterImpl with default options. */
protected TesterImpl(DiffRepository diffRepos) {
this(diffRepos, true, true, false, true, null, null,
MockRelOptPlanner::new, UnaryOperator.identity(),
SqlConformanceEnum.DEFAULT, c -> Contexts.empty(), DEFAULT_TYPE_FACTORY_SUPPLIER);
}
/**
* Creates a TesterImpl.
*
* @param diffRepos Diff repository
* @param enableDecorrelate Whether to decorrelate
* @param enableTrim Whether to trim unused fields
* @param catalogReaderFactory Function to create catalog reader, or null
* @param clusterFactory Called after a cluster has been created
*/
protected TesterImpl(DiffRepository diffRepos, boolean enableDecorrelate,
boolean enableTrim, boolean enableLateDecorrelate,
boolean enableTypeCoercion,
SqlTestFactory.MockCatalogReaderFactory catalogReaderFactory,
Function<RelOptCluster, RelOptCluster> clusterFactory,
Function<Context, RelOptPlanner> plannerFactory,
UnaryOperator<SqlToRelConverter.Config> configTransform,
SqlConformance conformance, UnaryOperator<Context> contextTransform,
Supplier<RelDataTypeFactory> typeFactorySupplier) {
this(diffRepos, enableDecorrelate, enableTrim, enableLateDecorrelate,
enableTypeCoercion, catalogReaderFactory, clusterFactory, plannerFactory,
configTransform, conformance, contextTransform, typeFactorySupplier,
transform -> transform);
}
protected TesterImpl(DiffRepository diffRepos, boolean enableDecorrelate,
boolean enableTrim, boolean enableLateDecorrelate,
boolean enableTypeCoercion,
SqlTestFactory.MockCatalogReaderFactory catalogReaderFactory,
Function<RelOptCluster, RelOptCluster> clusterFactory,
Function<Context, RelOptPlanner> plannerFactory,
UnaryOperator<SqlToRelConverter.Config> configTransform,
SqlConformance conformance, UnaryOperator<Context> contextTransform,
Supplier<RelDataTypeFactory> typeFactorySupplier,
UnaryOperator<SqlValidator> validatorTransform) {
this.diffRepos = diffRepos;
this.enableDecorrelate = enableDecorrelate;
this.enableTrim = enableTrim;
this.enableLateDecorrelate = enableLateDecorrelate;
this.enableTypeCoercion = enableTypeCoercion;
this.catalogReaderFactory = catalogReaderFactory;
this.clusterFactory = clusterFactory;
this.configTransform = Objects.requireNonNull(configTransform, "configTransform");
this.plannerFactory = Objects.requireNonNull(plannerFactory, "plannerFactory");
this.conformance = Objects.requireNonNull(conformance, "conformance");
this.contextTransform = Objects.requireNonNull(contextTransform, "contextTransform");
this.typeFactorySupplier = Objects.requireNonNull(typeFactorySupplier, "typeFactorySupplier");
this.validatorTransform = Objects.requireNonNull(validatorTransform, "validatorTransform");
}
public RelRoot convertSqlToRel(String sql) {
Objects.requireNonNull(sql, "sql");
final SqlNode sqlQuery;
try {
sqlQuery = parseQuery(sql);
} catch (RuntimeException | Error e) {
throw e;
} catch (Exception e) {
throw TestUtil.rethrow(e);
}
final RelDataTypeFactory typeFactory = getTypeFactory();
final Prepare.CatalogReader catalogReader =
createCatalogReader(typeFactory);
final SqlValidator validator =
createValidator(catalogReader, typeFactory);
SqlToRelConverter converter =
createSqlToRelConverter(validator, catalogReader);
final SqlNode validatedQuery = validator.validate(sqlQuery);
RelRoot root =
converter.convertQuery(validatedQuery, false, true);
assert root != null;
if (enableDecorrelate || enableTrim) {
root = root.withRel(converter.flattenTypes(root.rel, true));
}
if (enableDecorrelate) {
root = root.withRel(converter.decorrelate(sqlQuery, root.rel));
}
if (enableTrim) {
root = root.withRel(converter.trimUnusedFields(true, root.rel));
}
return root;
}
public RelNode trimRelNode(RelNode relNode) {
final RelDataTypeFactory typeFactory = getTypeFactory();
final Prepare.CatalogReader catalogReader =
createCatalogReader(typeFactory);
final SqlValidator validator =
createValidator(catalogReader, typeFactory);
final SqlToRelConverter converter =
createSqlToRelConverter(validator, catalogReader);
relNode = converter.flattenTypes(relNode, true);
relNode = converter.trimUnusedFields(true, relNode);
return relNode;
}
private SqlToRelConverter createSqlToRelConverter(SqlValidator validator,
Prepare.CatalogReader catalogReader) {
final Context context = getContext();
context.maybeUnwrap(CalciteConnectionConfig.class)
.ifPresent(calciteConfig -> {
validator.transform(config ->
config.withDefaultNullCollation(calciteConfig.defaultNullCollation()));
});
final SqlToRelConverter.Config config =
configTransform.apply(SqlToRelConverter.config());
return createSqlToRelConverter(
validator,
catalogReader,
getTypeFactory(),
config);
}
protected SqlToRelConverter createSqlToRelConverter(
final SqlValidator validator,
final Prepare.CatalogReader catalogReader,
final RelDataTypeFactory typeFactory,
final SqlToRelConverter.Config config) {
final RexBuilder rexBuilder = new RexBuilder(typeFactory);
RelOptCluster cluster =
RelOptCluster.create(getPlanner(), rexBuilder);
if (clusterFactory != null) {
cluster = clusterFactory.apply(cluster);
}
RelOptTable.ViewExpander viewExpander =
new MockViewExpander(validator, catalogReader, cluster, config);
return new SqlToRelConverter(viewExpander, validator, catalogReader, cluster,
StandardConvertletTable.INSTANCE, config);
}
protected final RelDataTypeFactory getTypeFactory() {
return typeFactorySupplier.get();
}
protected final RelOptPlanner getPlanner() {
if (planner == null) {
planner = createPlanner();
}
return planner;
}
public SqlNode parseQuery(String sql) throws Exception {
final SqlParser.Config config =
SqlParser.config().withConformance(getConformance());
SqlParser parser = SqlParser.create(sql, config);
return parser.parseQuery();
}
@Override public SqlNode parseExpression(String expr) throws Exception {
final SqlParser.Config config =
SqlParser.config().withConformance(getConformance());
SqlParser parser = SqlParser.create(expr, config);
return parser.parseExpression();
}
public SqlConformance getConformance() {
return conformance;
}
public SqlValidator createValidator(
SqlValidatorCatalogReader catalogReader,
RelDataTypeFactory typeFactory) {
final SqlOperatorTable operatorTable = getOperatorTable();
final SqlConformance conformance = getConformance();
final List<SqlOperatorTable> list = new ArrayList<>();
list.add(operatorTable);
if (conformance.allowGeometry()) {
list.add(SqlOperatorTables.spatialInstance());
}
SqlValidator validator = new FarragoTestValidator(
SqlOperatorTables.chain(list),
catalogReader,
typeFactory,
SqlValidator.Config.DEFAULT
.withSqlConformance(conformance)
.withTypeCoercionEnabled(enableTypeCoercion)
.withIdentifierExpansion(true));
return validatorTransform.apply(validator);
}
public final SqlOperatorTable getOperatorTable() {
if (opTab == null) {
opTab = createOperatorTable();
}
return opTab;
}
/**
* Creates an operator table.
*
* @return New operator table
*/
protected SqlOperatorTable createOperatorTable() {
return getContext().maybeUnwrap(SqlOperatorTable.class)
.orElseGet(() -> {
final MockSqlOperatorTable opTab =
new MockSqlOperatorTable(SqlStdOperatorTable.instance());
MockSqlOperatorTable.addRamp(opTab);
return opTab;
});
}
private Context getContext() {
return contextTransform.apply(Contexts.empty());
}
public Prepare.CatalogReader createCatalogReader(
RelDataTypeFactory typeFactory) {
MockCatalogReader catalogReader;
if (this.catalogReaderFactory != null) {
catalogReader = catalogReaderFactory.create(typeFactory, true);
} else {
catalogReader = new MockCatalogReaderSimple(typeFactory, true);
}
return catalogReader.init();
}
public RelOptPlanner createPlanner() {
return plannerFactory.apply(getContext());
}
public void assertConvertsTo(
String sql,
String plan) {
assertConvertsTo(sql, plan, false);
}
public void assertConvertsTo(
String sql,
String plan,
boolean trim) {
assertConvertsTo(sql, plan, false, true);
}
public void assertConvertsTo(
String sql,
String plan,
boolean trim,
boolean query) {
if (query) {
assertSqlConvertsTo(sql, plan, trim);
} else {
assertExprConvertsTo(sql, plan);
}
}
private void assertExprConvertsTo(
String expr,
String plan) {
String expr2 = getDiffRepos().expand("sql", expr);
RexNode rex = convertExprToRex(expr2);
assertNotNull(rex);
// NOTE jvs 28-Mar-2006: insert leading newline so
// that plans come out nicely stacked instead of first
// line immediately after CDATA start
String actual = NL + rex.toString() + NL;
diffRepos.assertEquals("plan", plan, actual);
}
private void assertSqlConvertsTo(
String sql,
String plan,
boolean trim) {
String sql2 = getDiffRepos().expand("sql", sql);
RelNode rel = convertSqlToRel(sql2).project();
assertNotNull(rel);
assertValid(rel);
if (trim) {
final RelBuilder relBuilder =
RelFactories.LOGICAL_BUILDER.create(rel.getCluster(), null);
final RelFieldTrimmer trimmer = createFieldTrimmer(relBuilder);
rel = trimmer.trim(rel);
assertNotNull(rel);
assertValid(rel);
}
// NOTE jvs 28-Mar-2006: insert leading newline so
// that plans come out nicely stacked instead of first
// line immediately after CDATA start
String actual = NL + RelOptUtil.toString(rel);
diffRepos.assertEquals("plan", plan, actual);
}
public RexNode convertExprToRex(String expr) {
Objects.requireNonNull(expr, "expr");
final SqlNode sqlQuery;
try {
sqlQuery = parseExpression(expr);
} catch (RuntimeException | Error e) {
throw e;
} catch (Exception e) {
throw TestUtil.rethrow(e);
}
final RelDataTypeFactory typeFactory = getTypeFactory();
final Prepare.CatalogReader catalogReader =
createCatalogReader(typeFactory);
final SqlValidator validator =
createValidator(
catalogReader, typeFactory);
SqlToRelConverter converter = createSqlToRelConverter(validator, catalogReader);
final SqlNode validatedQuery = validator.validate(sqlQuery);
return converter.convertExpression(validatedQuery);
}
/**
* Creates a RelFieldTrimmer.
*
* @param relBuilder Builder
* @return Field trimmer
*/
public RelFieldTrimmer createFieldTrimmer(RelBuilder relBuilder) {
return new RelFieldTrimmer(getValidator(), relBuilder);
}
public DiffRepository getDiffRepos() {
return diffRepos;
}
public SqlValidator getValidator() {
final RelDataTypeFactory typeFactory = getTypeFactory();
final SqlValidatorCatalogReader catalogReader =
createCatalogReader(typeFactory);
return createValidator(catalogReader, typeFactory);
}
public TesterImpl withDecorrelation(boolean enableDecorrelate) {
return this.enableDecorrelate == enableDecorrelate
? this
: new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public TesterImpl withLateDecorrelation(boolean enableLateDecorrelate) {
return this.enableLateDecorrelate == enableLateDecorrelate
? this
: new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public Tester withConfig(UnaryOperator<SqlToRelConverter.Config> transform) {
final UnaryOperator<SqlToRelConverter.Config> configTransform =
this.configTransform.andThen(transform)::apply;
return new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public TesterImpl withTrim(boolean enableTrim) {
return this.enableTrim == enableTrim
? this
: new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public TesterImpl withConformance(SqlConformance conformance) {
return new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public Tester enableTypeCoercion(boolean enableTypeCoercion) {
return new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public Tester withCatalogReaderFactory(
SqlTestFactory.MockCatalogReaderFactory catalogReaderFactory) {
return new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public Tester withClusterFactory(
Function<RelOptCluster, RelOptCluster> clusterFactory) {
return new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public Tester withPlannerFactory(
Function<Context, RelOptPlanner> plannerFactory) {
return this.plannerFactory == plannerFactory
? this
: new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public Tester withTypeFactorySupplier(
Supplier<RelDataTypeFactory> typeFactorySupplier) {
return this.typeFactorySupplier == typeFactorySupplier
? this
: new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier);
}
public TesterImpl withContext(UnaryOperator<Context> context) {
return new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
context, typeFactorySupplier);
}
public boolean isLateDecorrelate() {
return enableLateDecorrelate;
}
public Tester withValidatorTransform(UnaryOperator<SqlValidator> transform) {
return new TesterImpl(diffRepos, enableDecorrelate, enableTrim,
enableLateDecorrelate, enableTypeCoercion, catalogReaderFactory,
clusterFactory, plannerFactory, configTransform, conformance,
contextTransform, typeFactorySupplier, transform);
}
}
/** Validator for testing. */
private static class FarragoTestValidator extends SqlValidatorImpl {
FarragoTestValidator(
SqlOperatorTable opTab,
SqlValidatorCatalogReader catalogReader,
RelDataTypeFactory typeFactory,
Config config) {
super(opTab, catalogReader, typeFactory, config);
}
}
/**
* {@link RelOptTable.ViewExpander} implementation for testing usage.
*/
private static class MockViewExpander implements RelOptTable.ViewExpander {
private final SqlValidator validator;
private final Prepare.CatalogReader catalogReader;
private final RelOptCluster cluster;
private final SqlToRelConverter.Config config;
MockViewExpander(
SqlValidator validator,
Prepare.CatalogReader catalogReader,
RelOptCluster cluster,
SqlToRelConverter.Config config) {
this.validator = validator;
this.catalogReader = catalogReader;
this.cluster = cluster;
this.config = config;
}
@Override public RelRoot expandView(RelDataType rowType, String queryString,
List<String> schemaPath, List<String> viewPath) {
try {
SqlNode parsedNode = SqlParser.create(queryString).parseStmt();
SqlNode validatedNode = validator.validate(parsedNode);
SqlToRelConverter converter = new SqlToRelConverter(
this, validator, catalogReader, cluster,
StandardConvertletTable.INSTANCE, config);
return converter.convertQuery(validatedNode, false, true);
} catch (SqlParseException e) {
throw new RuntimeException("Error happened while expanding view.", e);
}
}
}
/**
* Custom implementation of Correlate for testing.
*/
public static class CustomCorrelate extends Correlate {
public CustomCorrelate(
RelOptCluster cluster,
RelTraitSet traits,
RelNode left,
RelNode right,
CorrelationId correlationId,
ImmutableBitSet requiredColumns,
JoinRelType joinType) {
super(cluster, traits, left, right, correlationId, requiredColumns, joinType);
}
@Override public Correlate copy(RelTraitSet traitSet,
RelNode left, RelNode right, CorrelationId correlationId,
ImmutableBitSet requiredColumns, JoinRelType joinType) {
return new CustomCorrelate(getCluster(), traitSet, left, right,
correlationId, requiredColumns, joinType);
}
@Override public RelNode accept(RelShuttle shuttle) {
return shuttle.visit(this);
}
}
}