blob: 9d8a6414cc9b65e624b8f46069edc0311d356daa [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.sql.test;
import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.Quoting;
import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
import org.apache.calcite.rel.type.DelegatingTypeSystem;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.advise.SqlAdvisor;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorCatalogReader;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.sql.validate.SqlValidatorWithHints;
import org.apache.calcite.test.CalciteAssert;
import org.apache.calcite.test.MockSqlOperatorTable;
import org.apache.calcite.test.catalog.MockCatalogReader;
import org.apache.calcite.test.catalog.MockCatalogReaderSimple;
import org.apache.calcite.util.SourceStringReader;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedMap;
import java.util.Map;
import java.util.Objects;
/**
* Default implementation of {@link SqlTestFactory}.
*
* <p>Suitable for most tests. If you want different behavior, you can extend;
* if you want a factory with different properties (e.g. SQL conformance level
* or identifier quoting), use {@link #with(String, Object)} to create a new factory.</p>
*/
public class SqlTestFactory {
public static final ImmutableMap<String, Object> DEFAULT_OPTIONS =
ImmutableSortedMap.<String, Object>naturalOrder()
.put("quoting", Quoting.DOUBLE_QUOTE)
.put("quotedCasing", Casing.UNCHANGED)
.put("unquotedCasing", Casing.TO_UPPER)
.put("caseSensitive", true)
.put("lenientOperatorLookup", false)
.put("enableTypeCoercion", true)
.put("conformance", SqlConformanceEnum.DEFAULT)
.put("operatorTable", SqlStdOperatorTable.instance())
.put("connectionFactory",
CalciteAssert.EMPTY_CONNECTION_FACTORY
.with(
new CalciteAssert.AddSchemaSpecPostProcessor(
CalciteAssert.SchemaSpec.HR)))
.build();
public static final SqlTestFactory INSTANCE =
new SqlTestFactory();
private final ImmutableMap<String, Object> options;
private final MockCatalogReaderFactory catalogReaderFactory;
private final ValidatorFactory validatorFactory;
private final Supplier<RelDataTypeFactory> typeFactory;
private final Supplier<SqlOperatorTable> operatorTable;
private final Supplier<SqlValidatorCatalogReader> catalogReader;
private final Supplier<SqlParser.Config> parserConfig;
protected SqlTestFactory() {
this(DEFAULT_OPTIONS, MockCatalogReaderSimple::new, SqlValidatorUtil::newValidator);
}
protected SqlTestFactory(ImmutableMap<String, Object> options,
MockCatalogReaderFactory catalogReaderFactory,
ValidatorFactory validatorFactory) {
this.options = options;
this.catalogReaderFactory = catalogReaderFactory;
this.validatorFactory = validatorFactory;
this.operatorTable = Suppliers.memoize(
() -> createOperatorTable((SqlOperatorTable) options.get("operatorTable")));
this.typeFactory = Suppliers.memoize(
() -> createTypeFactory((SqlConformance) options.get("conformance")));
Boolean caseSensitive = (Boolean) options.get("caseSensitive");
this.catalogReader = Suppliers.memoize(
() -> catalogReaderFactory.create(typeFactory.get(), caseSensitive).init());
this.parserConfig = Suppliers.memoize(
() -> createParserConfig(options));
}
private static SqlOperatorTable createOperatorTable(SqlOperatorTable opTab0) {
MockSqlOperatorTable opTab = new MockSqlOperatorTable(opTab0);
MockSqlOperatorTable.addRamp(opTab);
return opTab;
}
public SqlParser.Config getParserConfig() {
return parserConfig.get();
}
public SqlParser createParser(String sql) {
return SqlParser.create(new SourceStringReader(sql), parserConfig.get());
}
public static SqlParser.Config createParserConfig(ImmutableMap<String, Object> options) {
return SqlParser.config()
.withQuoting((Quoting) options.get("quoting"))
.withUnquotedCasing((Casing) options.get("unquotedCasing"))
.withQuotedCasing((Casing) options.get("quotedCasing"))
.withConformance((SqlConformance) options.get("conformance"))
.withCaseSensitive((boolean) options.get("caseSensitive"));
}
public SqlValidator getValidator() {
final SqlConformance conformance =
(SqlConformance) options.get("conformance");
final boolean lenientOperatorLookup =
(boolean) options.get("lenientOperatorLookup");
final boolean enableTypeCoercion = (boolean) options.get("enableTypeCoercion");
final SqlValidator.Config config = SqlValidator.Config.DEFAULT
.withSqlConformance(conformance)
.withTypeCoercionEnabled(enableTypeCoercion)
.withLenientOperatorLookup(lenientOperatorLookup);
return validatorFactory.create(operatorTable.get(),
catalogReader.get(),
typeFactory.get(),
config);
}
public SqlAdvisor createAdvisor() {
SqlValidator validator = getValidator();
if (validator instanceof SqlValidatorWithHints) {
return new SqlAdvisor((SqlValidatorWithHints) validator, parserConfig.get());
}
throw new UnsupportedOperationException(
"Validator should implement SqlValidatorWithHints, actual validator is " + validator);
}
public SqlTestFactory with(String name, Object value) {
if (Objects.equals(value, options.get(name))) {
return this;
}
ImmutableMap.Builder<String, Object> builder = ImmutableSortedMap.naturalOrder();
// Protect from IllegalArgumentException: Multiple entries with same key
for (Map.Entry<String, Object> entry : options.entrySet()) {
if (name.equals(entry.getKey())) {
continue;
}
builder.put(entry);
}
builder.put(name, value);
return new SqlTestFactory(builder.build(), catalogReaderFactory, validatorFactory);
}
public SqlTestFactory withCatalogReader(MockCatalogReaderFactory newCatalogReaderFactory) {
return new SqlTestFactory(options, newCatalogReaderFactory, validatorFactory);
}
public SqlTestFactory withValidator(ValidatorFactory newValidatorFactory) {
return new SqlTestFactory(options, catalogReaderFactory, newValidatorFactory);
}
public final Object get(String name) {
return options.get(name);
}
private static RelDataTypeFactory createTypeFactory(SqlConformance conformance) {
RelDataTypeSystem typeSystem = RelDataTypeSystem.DEFAULT;
if (conformance.shouldConvertRaggedUnionTypesToVarying()) {
typeSystem = new DelegatingTypeSystem(typeSystem) {
public boolean shouldConvertRaggedUnionTypesToVarying() {
return true;
}
};
}
if (conformance.allowExtendedTrim()) {
typeSystem = new DelegatingTypeSystem(typeSystem) {
public boolean allowExtendedTrim() {
return true;
}
};
}
return new JavaTypeFactoryImpl(typeSystem);
}
/**
* Creates {@link SqlValidator} for tests.
*/
public interface ValidatorFactory {
SqlValidator create(
SqlOperatorTable opTab,
SqlValidatorCatalogReader catalogReader,
RelDataTypeFactory typeFactory,
SqlValidator.Config config);
}
/**
* Creates {@link MockCatalogReader} for tests.
* Note: {@link MockCatalogReader#init()} is to be invoked later, so a typical implementation
* should be via constructor reference like {@code MockCatalogReaderSimple::new}.
*/
public interface MockCatalogReaderFactory {
MockCatalogReader create(RelDataTypeFactory typeFactory, boolean caseSensitive);
}
}