ILuffZhe commented on code in PR #4206: URL: https://github.com/apache/calcite/pull/4206#discussion_r1963331115
########## core/src/test/java/org/apache/calcite/rel/rel2sql/DialectTestConfig.java: ########## @@ -0,0 +1,302 @@ +/* + * 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.rel.rel2sql; + +import org.apache.calcite.avatica.AvaticaConnection; +import org.apache.calcite.config.CalciteConnectionProperty; +import org.apache.calcite.jdbc.CalciteJdbc41Factory; +import org.apache.calcite.jdbc.CalciteSchema; +import org.apache.calcite.jdbc.Driver; +import org.apache.calcite.sql.SqlDialect; +import org.apache.calcite.sql.validate.SqlConformanceEnum; +import org.apache.calcite.test.CalciteAssert; + +import com.google.common.collect.ImmutableMap; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import static java.util.Objects.requireNonNull; + +/** Description of the dialects that are enabled for a particular test. + * + * <p>Each dialect has a name, optionally a connection factory, + * and a state (enabled, recording, replaying). + * + * <p>It is immutable. + */ +class DialectTestConfig { + final ImmutableMap<String, Dialect> dialectMap; + + /** The code of the reference dialect. If not null, the queries from this + * dialect as used as exemplars for other dialects: the other dialects are + * expected to return the same set of rows as the reference. */ + final @Nullable DialectCode refDialectCode; + + /** The name of the class relative to which the resource file containing + * query responses is located. */ + @SuppressWarnings("rawtypes") + private final Class testClass; + + /** A function that maps a dialect name to the name of the file containing + * its query responses. */ + private final Function<String, String> function; + + private DialectTestConfig(Map<String, Dialect> dialectMap, + @Nullable DialectCode refDialectCode, + @SuppressWarnings("rawtypes") Class testClass, + Function<String, String> function) { + this.dialectMap = ImmutableMap.copyOf(dialectMap); + this.refDialectCode = refDialectCode; + this.testClass = requireNonNull(testClass, "testClass"); + this.function = requireNonNull(function, "function"); + } + + /** Creates a DialectTestConfig. */ + static DialectTestConfig of(Iterable<Dialect> dialects) { + final ImmutableMap.Builder<String, Dialect> map = ImmutableMap.builder(); + dialects.forEach(dialect -> map.put(dialect.name, dialect)); + return new DialectTestConfig(map.build(), null, RelToSqlConverterTest.class, + UnaryOperator.identity()); + } + + /** Applies a transform to the dialect with a given code. + * + * <p>Throws if there is no such dialect. */ + public DialectTestConfig withDialect(DialectCode code, + UnaryOperator<Dialect> dialectTransform) { + return withDialect(code.name(), dialectTransform); + } + + /** Applies a transform to each dialect. */ + public DialectTestConfig withDialects( + UnaryOperator<Dialect> dialectTransform) { + final ImmutableMap.Builder<String, Dialect> b = + ImmutableMap.builder(); + dialectMap.forEach((name, dialect) -> + b.put(dialect.name, dialectTransform.apply(dialect))); + final ImmutableMap<String, Dialect> dialectMap2 = b.build(); + if (dialectMap2.equals(dialectMap)) { + return this; + } + return new DialectTestConfig(dialectMap2, refDialectCode, testClass, + function); + } + + /** Applies a transform to the dialect with a given name. + * + * <p>Throws if there is no such dialect. */ + public DialectTestConfig withDialect(String name, + UnaryOperator<Dialect> dialectTransform) { + final Dialect dialect = dialectMap.get(name); + final Dialect dialect2 = dialectTransform.apply(dialect); + if (dialect == dialect2) { + return this; + } + final Map<String, Dialect> dialectMap2 = new LinkedHashMap<>(dialectMap); + dialectMap2.put(name, dialect2); + return new DialectTestConfig(dialectMap2, refDialectCode, testClass, + function); + } + + /** Sets the name of the reference dialect. */ + public DialectTestConfig withReference(DialectCode refDialectCode) { + if (refDialectCode == this.refDialectCode) { + return this; + } + return new DialectTestConfig(dialectMap, refDialectCode, testClass, + function); + } + + /** Sets the path for any given dialect's corpus. */ + public DialectTestConfig withPath( + @SuppressWarnings("rawtypes") Class testClass, + Function<String, String> function) { + if (testClass == this.testClass && function == this.function) { + return this; + } + return new DialectTestConfig(dialectMap, refDialectCode, testClass, + function); + } + + /** Returns the dialect with the given code. */ + public Dialect get(DialectCode dialectCode) { + return requireNonNull(dialectMap.get(dialectCode.name()), + () -> "dialect " + dialectCode); + } + + /** Which phase of query execution. */ + public enum Phase { + /** Parses the query but does not validate. */ + PARSE, + PREPARE, + EXECUTE, + } + + /** Definition of a dialect. */ + static class Dialect { + /** The name of this dialect. */ + final String name; + + /** The code of this dialect. + * Having a code isn't strictly necessary, but it makes tests more concise. */ + final DialectCode code; + + /** The dialect object. */ + final SqlDialect sqlDialect; + + /** Whether the dialect is enabled in the test. */ + final boolean enabled; + + /** Whether the test should execute queries in this dialect. If there is a + * reference, compares the results to the reference. */ + final boolean execute; + + /** The query that we expect to be generated for this dialect in this test + * run. Is only set during a test run, and is always null in the base + * configuration. */ + final @Nullable String expectedQuery; + + /** The error that we expect to be thrown for this dialect in this test + * run. Is only set during a test run, and is always null in the base + * configuration. */ + final @Nullable String expectedError; + + Dialect(String name, DialectCode code, SqlDialect sqlDialect, + boolean enabled, boolean execute, @Nullable String expectedQuery, + @Nullable String expectedError) { + this.name = requireNonNull(name, "name"); + this.code = requireNonNull(code, "code"); + this.sqlDialect = requireNonNull(sqlDialect, "sqlDialect"); + this.enabled = enabled; + this.execute = execute; + this.expectedQuery = expectedQuery; + this.expectedError = expectedError; + } + + /** Creates a Dialect based on a + * {@link org.apache.calcite.sql.SqlDialect.DatabaseProduct}. */ + public static Dialect of(DialectCode dialectCode, + SqlDialect.DatabaseProduct databaseProduct) { + return of(dialectCode, databaseProduct.getDialect()); + } + + /** Creates a Dialect. */ + public static Dialect of(DialectCode dialectCode, SqlDialect dialect) { + return new Dialect(dialectCode.name(), dialectCode, dialect, true, false, + null, null); + } + + @Override public String toString() { + return name + + (enabled ? " (enabled)" : " (disabled)"); + } + + public Dialect withEnabled(boolean enabled) { + if (enabled == this.enabled) { + return this; + } + return new Dialect(name, code, sqlDialect, enabled, execute, + expectedQuery, expectedError); + } + + public Dialect withExecute(boolean execute) { + if (execute == this.execute) { + return this; + } + return new Dialect(name, code, sqlDialect, enabled, execute, + expectedQuery, expectedError); + } + + public Dialect withExpectedQuery(String expectedQuery) { + if (Objects.equals(expectedQuery, this.expectedQuery)) { + return this; + } + return new Dialect(name, code, sqlDialect, enabled, execute, + expectedQuery, expectedError); + } + + public Dialect withExpectedError(String expectedError) { + if (Objects.equals(expectedError, this.expectedError)) { + return this; + } + return new Dialect(name, code, sqlDialect, enabled, execute, + expectedQuery, expectedError); + } + + /** Performs an action with the dialect's connection. */ + public void withConnection(CalciteAssert.SchemaSpec schemaSpec, + Consumer<Connection> consumer) { + switch (code) { + case CALCITE: + final CalciteJdbc41Factory factory = new CalciteJdbc41Factory(); + final Driver driver = new Driver(); + final String url = "jdbc:calcite:"; + final CalciteSchema rootSchema = CalciteSchema.createRootSchema(false); + CalciteAssert.addSchema(rootSchema.plus(), + CalciteAssert.SchemaSpec.BOOKSTORE, + CalciteAssert.SchemaSpec.JDBC_FOODMART, + CalciteAssert.SchemaSpec.POST, + CalciteAssert.SchemaSpec.SCOTT, + CalciteAssert.SchemaSpec.SCOTT_WITH_TEMPORAL, + CalciteAssert.SchemaSpec.TPCH); + final Properties info = new Properties(); + // Hive for RLIKE, Postgres for ILIKE, Spark for EXISTS, etc. + info.put(CalciteConnectionProperty.FUN.name(), + "standard,postgresql,bigquery,hive,spark"); Review Comment: One small question: why those libraries are chosen? ########## core/src/test/java/org/apache/calcite/rel/rel2sql/DialectTestConfig.java: ########## @@ -0,0 +1,302 @@ +/* + * 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.rel.rel2sql; + +import org.apache.calcite.avatica.AvaticaConnection; +import org.apache.calcite.config.CalciteConnectionProperty; +import org.apache.calcite.jdbc.CalciteJdbc41Factory; +import org.apache.calcite.jdbc.CalciteSchema; +import org.apache.calcite.jdbc.Driver; +import org.apache.calcite.sql.SqlDialect; +import org.apache.calcite.sql.validate.SqlConformanceEnum; +import org.apache.calcite.test.CalciteAssert; + +import com.google.common.collect.ImmutableMap; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import static java.util.Objects.requireNonNull; + +/** Description of the dialects that are enabled for a particular test. + * + * <p>Each dialect has a name, optionally a connection factory, + * and a state (enabled, recording, replaying). + * + * <p>It is immutable. + */ +class DialectTestConfig { + final ImmutableMap<String, Dialect> dialectMap; + + /** The code of the reference dialect. If not null, the queries from this + * dialect as used as exemplars for other dialects: the other dialects are + * expected to return the same set of rows as the reference. */ + final @Nullable DialectCode refDialectCode; + + /** The name of the class relative to which the resource file containing + * query responses is located. */ + @SuppressWarnings("rawtypes") + private final Class testClass; + + /** A function that maps a dialect name to the name of the file containing + * its query responses. */ + private final Function<String, String> function; + + private DialectTestConfig(Map<String, Dialect> dialectMap, + @Nullable DialectCode refDialectCode, + @SuppressWarnings("rawtypes") Class testClass, + Function<String, String> function) { + this.dialectMap = ImmutableMap.copyOf(dialectMap); + this.refDialectCode = refDialectCode; + this.testClass = requireNonNull(testClass, "testClass"); + this.function = requireNonNull(function, "function"); + } + + /** Creates a DialectTestConfig. */ + static DialectTestConfig of(Iterable<Dialect> dialects) { + final ImmutableMap.Builder<String, Dialect> map = ImmutableMap.builder(); + dialects.forEach(dialect -> map.put(dialect.name, dialect)); + return new DialectTestConfig(map.build(), null, RelToSqlConverterTest.class, + UnaryOperator.identity()); + } + + /** Applies a transform to the dialect with a given code. + * + * <p>Throws if there is no such dialect. */ + public DialectTestConfig withDialect(DialectCode code, + UnaryOperator<Dialect> dialectTransform) { + return withDialect(code.name(), dialectTransform); + } + + /** Applies a transform to each dialect. */ + public DialectTestConfig withDialects( + UnaryOperator<Dialect> dialectTransform) { + final ImmutableMap.Builder<String, Dialect> b = + ImmutableMap.builder(); + dialectMap.forEach((name, dialect) -> + b.put(dialect.name, dialectTransform.apply(dialect))); + final ImmutableMap<String, Dialect> dialectMap2 = b.build(); + if (dialectMap2.equals(dialectMap)) { + return this; + } + return new DialectTestConfig(dialectMap2, refDialectCode, testClass, + function); + } + + /** Applies a transform to the dialect with a given name. + * + * <p>Throws if there is no such dialect. */ + public DialectTestConfig withDialect(String name, + UnaryOperator<Dialect> dialectTransform) { + final Dialect dialect = dialectMap.get(name); + final Dialect dialect2 = dialectTransform.apply(dialect); + if (dialect == dialect2) { + return this; + } + final Map<String, Dialect> dialectMap2 = new LinkedHashMap<>(dialectMap); + dialectMap2.put(name, dialect2); + return new DialectTestConfig(dialectMap2, refDialectCode, testClass, + function); + } + + /** Sets the name of the reference dialect. */ + public DialectTestConfig withReference(DialectCode refDialectCode) { + if (refDialectCode == this.refDialectCode) { + return this; + } + return new DialectTestConfig(dialectMap, refDialectCode, testClass, + function); + } + + /** Sets the path for any given dialect's corpus. */ + public DialectTestConfig withPath( + @SuppressWarnings("rawtypes") Class testClass, + Function<String, String> function) { + if (testClass == this.testClass && function == this.function) { + return this; + } + return new DialectTestConfig(dialectMap, refDialectCode, testClass, + function); + } + + /** Returns the dialect with the given code. */ + public Dialect get(DialectCode dialectCode) { + return requireNonNull(dialectMap.get(dialectCode.name()), + () -> "dialect " + dialectCode); + } + + /** Which phase of query execution. */ + public enum Phase { + /** Parses the query but does not validate. */ + PARSE, + PREPARE, + EXECUTE, + } + + /** Definition of a dialect. */ + static class Dialect { + /** The name of this dialect. */ + final String name; + + /** The code of this dialect. + * Having a code isn't strictly necessary, but it makes tests more concise. */ + final DialectCode code; + + /** The dialect object. */ + final SqlDialect sqlDialect; + + /** Whether the dialect is enabled in the test. */ + final boolean enabled; + + /** Whether the test should execute queries in this dialect. If there is a + * reference, compares the results to the reference. */ + final boolean execute; + + /** The query that we expect to be generated for this dialect in this test + * run. Is only set during a test run, and is always null in the base + * configuration. */ + final @Nullable String expectedQuery; + + /** The error that we expect to be thrown for this dialect in this test + * run. Is only set during a test run, and is always null in the base + * configuration. */ + final @Nullable String expectedError; + + Dialect(String name, DialectCode code, SqlDialect sqlDialect, + boolean enabled, boolean execute, @Nullable String expectedQuery, + @Nullable String expectedError) { + this.name = requireNonNull(name, "name"); + this.code = requireNonNull(code, "code"); + this.sqlDialect = requireNonNull(sqlDialect, "sqlDialect"); + this.enabled = enabled; + this.execute = execute; + this.expectedQuery = expectedQuery; + this.expectedError = expectedError; + } + + /** Creates a Dialect based on a + * {@link org.apache.calcite.sql.SqlDialect.DatabaseProduct}. */ + public static Dialect of(DialectCode dialectCode, + SqlDialect.DatabaseProduct databaseProduct) { + return of(dialectCode, databaseProduct.getDialect()); + } + + /** Creates a Dialect. */ + public static Dialect of(DialectCode dialectCode, SqlDialect dialect) { + return new Dialect(dialectCode.name(), dialectCode, dialect, true, false, + null, null); + } + + @Override public String toString() { + return name + + (enabled ? " (enabled)" : " (disabled)"); + } + + public Dialect withEnabled(boolean enabled) { + if (enabled == this.enabled) { + return this; + } + return new Dialect(name, code, sqlDialect, enabled, execute, + expectedQuery, expectedError); + } + + public Dialect withExecute(boolean execute) { + if (execute == this.execute) { + return this; + } + return new Dialect(name, code, sqlDialect, enabled, execute, + expectedQuery, expectedError); + } + + public Dialect withExpectedQuery(String expectedQuery) { + if (Objects.equals(expectedQuery, this.expectedQuery)) { + return this; + } + return new Dialect(name, code, sqlDialect, enabled, execute, + expectedQuery, expectedError); + } + + public Dialect withExpectedError(String expectedError) { + if (Objects.equals(expectedError, this.expectedError)) { + return this; + } + return new Dialect(name, code, sqlDialect, enabled, execute, + expectedQuery, expectedError); + } + + /** Performs an action with the dialect's connection. */ + public void withConnection(CalciteAssert.SchemaSpec schemaSpec, + Consumer<Connection> consumer) { + switch (code) { + case CALCITE: + final CalciteJdbc41Factory factory = new CalciteJdbc41Factory(); + final Driver driver = new Driver(); + final String url = "jdbc:calcite:"; + final CalciteSchema rootSchema = CalciteSchema.createRootSchema(false); + CalciteAssert.addSchema(rootSchema.plus(), + CalciteAssert.SchemaSpec.BOOKSTORE, + CalciteAssert.SchemaSpec.JDBC_FOODMART, + CalciteAssert.SchemaSpec.POST, + CalciteAssert.SchemaSpec.SCOTT, + CalciteAssert.SchemaSpec.SCOTT_WITH_TEMPORAL, + CalciteAssert.SchemaSpec.TPCH); + final Properties info = new Properties(); + // Hive for RLIKE, Postgres for ILIKE, Spark for EXISTS, etc. + info.put(CalciteConnectionProperty.FUN.name(), + "standard,postgresql,bigquery,hive,spark"); + info.put(CalciteConnectionProperty.SCHEMA.name(), + schemaSpec.schemaName); + info.put(CalciteConnectionProperty.CONFORMANCE.name(), + SqlConformanceEnum.LENIENT.name()); Review Comment: Why do we use LENIENT rather than DEFAULT? ########## core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlFixture.java: ########## @@ -0,0 +1,591 @@ +/* + * 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.rel.rel2sql; + +import org.apache.calcite.plan.RelOptPlanner; +import org.apache.calcite.plan.RelOptRule; +import org.apache.calcite.plan.RelTraitDef; +import org.apache.calcite.plan.hep.HepPlanner; +import org.apache.calcite.plan.hep.HepProgramBuilder; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.rules.CoreRules; +import org.apache.calcite.rel.rules.ProjectOverSumToSum0Rule; +import org.apache.calcite.rel.rules.ProjectToWindowRule; +import org.apache.calcite.rel.type.RelDataTypeSystem; +import org.apache.calcite.runtime.FlatLists; +import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.sql.SqlDialect; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlWriterConfig; +import org.apache.calcite.sql.fun.SqlLibrary; +import org.apache.calcite.sql.parser.SqlParser; +import org.apache.calcite.sql2rel.SqlToRelConverter; +import org.apache.calcite.test.CalciteAssert; +import org.apache.calcite.test.MockSqlOperatorTable; +import org.apache.calcite.test.RelBuilderTest; +import org.apache.calcite.tools.FrameworkConfig; +import org.apache.calcite.tools.Frameworks; +import org.apache.calcite.tools.Planner; +import org.apache.calcite.tools.Program; +import org.apache.calcite.tools.Programs; +import org.apache.calcite.tools.RelBuilder; +import org.apache.calcite.tools.RuleSet; +import org.apache.calcite.tools.RuleSets; +import org.apache.calcite.util.TestUtil; +import org.apache.calcite.util.Token; +import org.apache.calcite.util.Util; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +import static org.apache.calcite.rel.rel2sql.DialectCode.BIG_QUERY; +import static org.apache.calcite.rel.rel2sql.DialectCode.CALCITE; +import static org.apache.calcite.rel.rel2sql.DialectCode.CLICKHOUSE; +import static org.apache.calcite.rel.rel2sql.DialectCode.DB2; +import static org.apache.calcite.rel.rel2sql.DialectCode.EXASOL; +import static org.apache.calcite.rel.rel2sql.DialectCode.FIREBOLT; +import static org.apache.calcite.rel.rel2sql.DialectCode.HIVE; +import static org.apache.calcite.rel.rel2sql.DialectCode.HSQLDB; +import static org.apache.calcite.rel.rel2sql.DialectCode.INFORMIX; +import static org.apache.calcite.rel.rel2sql.DialectCode.JETHRO; +import static org.apache.calcite.rel.rel2sql.DialectCode.MSSQL_2017; +import static org.apache.calcite.rel.rel2sql.DialectCode.MYSQL; +import static org.apache.calcite.rel.rel2sql.DialectCode.MYSQL_8; +import static org.apache.calcite.rel.rel2sql.DialectCode.MYSQL_FIRST; +import static org.apache.calcite.rel.rel2sql.DialectCode.MYSQL_HIGH; +import static org.apache.calcite.rel.rel2sql.DialectCode.MYSQL_LAST; +import static org.apache.calcite.rel.rel2sql.DialectCode.ORACLE_11; +import static org.apache.calcite.rel.rel2sql.DialectCode.ORACLE_12; +import static org.apache.calcite.rel.rel2sql.DialectCode.ORACLE_19; +import static org.apache.calcite.rel.rel2sql.DialectCode.ORACLE_23; +import static org.apache.calcite.rel.rel2sql.DialectCode.ORACLE_MODIFIED; +import static org.apache.calcite.rel.rel2sql.DialectCode.POSTGRESQL; +import static org.apache.calcite.rel.rel2sql.DialectCode.POSTGRESQL_MODIFIED; +import static org.apache.calcite.rel.rel2sql.DialectCode.POSTGRESQL_MODIFIED_DECIMAL; +import static org.apache.calcite.rel.rel2sql.DialectCode.PRESTO; +import static org.apache.calcite.rel.rel2sql.DialectCode.REDSHIFT; +import static org.apache.calcite.rel.rel2sql.DialectCode.SNOWFLAKE; +import static org.apache.calcite.rel.rel2sql.DialectCode.SPARK; +import static org.apache.calcite.rel.rel2sql.DialectCode.STARROCKS; +import static org.apache.calcite.rel.rel2sql.DialectCode.SYBASE; +import static org.apache.calcite.rel.rel2sql.DialectCode.VERTICA; +import static org.apache.calcite.test.Matchers.isLinux; +import static org.apache.calcite.test.Matchers.returnsUnordered; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import static java.util.Objects.requireNonNull; + +/** Fluid interface to run tests. */ +class RelToSqlFixture { + /** A pool of tokens, used to identify fixtures that forgot to call + * {@link #done()}. */ + static final Token.Pool POOL = Token.pool(); + + private final Token token; + private final CalciteAssert.SchemaSpec schemaSpec; + private final String sql; + private final DialectTestConfig.Dialect dialect; + private final DialectTestConfig.Phase phase; + private final Set<SqlLibrary> librarySet; + private final @Nullable Function<RelBuilder, RelNode> relFn; + private final List<Function<RelNode, RelNode>> relTransforms; + private final SqlParser.Config parserConfig; + private final UnaryOperator<SqlToRelConverter.Config> configTransform; + private final DialectTestConfig testConfig; + private final UnaryOperator<SqlWriterConfig> writerTransform; + + RelToSqlFixture(Token token, CalciteAssert.SchemaSpec schemaSpec, String sql, + DialectTestConfig.Dialect dialect, DialectTestConfig.Phase phase, + SqlParser.Config parserConfig, Set<SqlLibrary> librarySet, + UnaryOperator<SqlToRelConverter.Config> configTransform, + @Nullable Function<RelBuilder, RelNode> relFn, + List<Function<RelNode, RelNode>> relTransforms, + DialectTestConfig testConfig, + UnaryOperator<SqlWriterConfig> writerTransform) { + this.token = requireNonNull(token, "token"); + this.schemaSpec = schemaSpec; + this.sql = sql; + this.dialect = dialect; + this.phase = requireNonNull(phase, "phase"); + this.librarySet = librarySet; + this.relFn = relFn; + this.relTransforms = ImmutableList.copyOf(relTransforms); + this.parserConfig = parserConfig; + this.configTransform = configTransform; + this.testConfig = requireNonNull(testConfig, "testConfig"); + this.writerTransform = requireNonNull(writerTransform, "writerTransform"); + } + + /** Default writer configuration. */ + static SqlWriterConfig transformWriter(SqlWriterConfig c) { + return c.withAlwaysUseParentheses(false) + .withSelectListItemsOnSeparateLines(false) + .withUpdateSetListNewline(false) + .withIndentation(0); + } + + public RelToSqlFixture schema(CalciteAssert.SchemaSpec schemaSpec) { + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public RelToSqlFixture withSql(String sql) { + if (sql.equals(this.sql)) { + return this; + } + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public RelToSqlFixture dialect(DialectCode dialectCode) { + DialectTestConfig.Dialect dialect = testConfig.get(dialectCode); + return withDialect(dialect); + } + + public RelToSqlFixture withDialect(DialectTestConfig.Dialect dialect) { + if (dialect.equals(this.dialect)) { + return this; + } + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public RelToSqlFixture parserConfig(SqlParser.Config parserConfig) { + if (parserConfig.equals(this.parserConfig)) { + return this; + } + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public final RelToSqlFixture withLibrary(SqlLibrary library) { + return withLibrarySet(ImmutableSet.of(library)); + } + + public RelToSqlFixture withLibrarySet( + Iterable<? extends SqlLibrary> librarySet) { + final ImmutableSet<SqlLibrary> librarySet1 = + ImmutableSet.copyOf(librarySet); + if (librarySet1.equals(this.librarySet)) { + return this; + } + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet1, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public RelToSqlFixture withPhase(DialectTestConfig.Phase phase) { + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public RelToSqlFixture withConfig( + UnaryOperator<SqlToRelConverter.Config> configTransform) { + if (configTransform.equals(this.configTransform)) { + return this; + } + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public RelToSqlFixture relFn(Function<RelBuilder, RelNode> relFn) { + if (relFn.equals(this.relFn)) { + return this; + } + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public RelToSqlFixture withExtraTransform( + Function<RelNode, RelNode> relTransform) { + final List<Function<RelNode, RelNode>> relTransforms2 = + FlatLists.append(relTransforms, relTransform); + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms2, + testConfig, writerTransform); + } + + public RelToSqlFixture withTestConfig( + UnaryOperator<DialectTestConfig> transform) { + DialectTestConfig testConfig = transform.apply(this.testConfig); + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + public RelToSqlFixture withWriterConfig( + UnaryOperator<SqlWriterConfig> writerTransform) { + if (writerTransform.equals(this.writerTransform)) { + return this; + } + return new RelToSqlFixture(token, schemaSpec, sql, dialect, phase, + parserConfig, librarySet, configTransform, relFn, relTransforms, + testConfig, writerTransform); + } + + RelToSqlFixture withBigQuery() { + return dialect(BIG_QUERY); + } + + RelToSqlFixture withCalcite() { + return dialect(CALCITE); + } + + RelToSqlFixture withClickHouse() { + return dialect(CLICKHOUSE); + } + + RelToSqlFixture withDb2() { + return dialect(DB2); + } + + RelToSqlFixture withExasol() { + return dialect(EXASOL); + } + + RelToSqlFixture withFirebolt() { + return dialect(FIREBOLT); + } + + RelToSqlFixture withHive() { + return dialect(HIVE); + } + + RelToSqlFixture withHsqldb() { + return dialect(HSQLDB); + } + + RelToSqlFixture withInformix() { + return dialect(INFORMIX); + } + + RelToSqlFixture withJethro() { + return dialect(JETHRO); + } + + RelToSqlFixture withMssql() { Review Comment: Do we need other withMssql for different versions? ########## core/src/test/java/org/apache/calcite/rel/rel2sql/DialectTestConfigs.java: ########## @@ -0,0 +1,204 @@ +/* + * 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.rel.rel2sql; + +import org.apache.calcite.config.NullCollation; +import org.apache.calcite.rel.type.RelDataTypeSystemImpl; +import org.apache.calcite.sql.SqlDialect; +import org.apache.calcite.sql.dialect.HiveSqlDialect; +import org.apache.calcite.sql.dialect.JethroDataSqlDialect; +import org.apache.calcite.sql.dialect.MssqlSqlDialect; +import org.apache.calcite.sql.dialect.MysqlSqlDialect; +import org.apache.calcite.sql.dialect.OracleSqlDialect; +import org.apache.calcite.sql.dialect.PostgresqlSqlDialect; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.validate.SqlConformance; +import org.apache.calcite.sql.validate.SqlConformanceEnum; +import org.apache.calcite.util.Util; + +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Ordering; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.function.Supplier; + +import static org.apache.calcite.util.Util.first; + +/** Utilities for {@link DialectTestConfig}. */ +class DialectTestConfigs { + private DialectTestConfigs() { + } + + static final Supplier<DialectTestConfig> INSTANCE_SUPPLIER = + Suppliers.memoize(() -> { + final ImmutableList.Builder<DialectTestConfig.Dialect> b = + ImmutableList.builder(); + for (DialectCode dialectCode : DialectCode.values()) { + b.add(dialectCode.toDialect()); + } + final ImmutableList<DialectTestConfig.Dialect> list = b.build(); + final Iterable<String> dialectNames = + Util.transform(list, dialect -> dialect.name); + if (!Ordering.natural().isOrdered(dialectNames)) { + throw new AssertionError("not ordered: " + dialectNames); + } + return DialectTestConfig.of(list); + })::get; + + + @SuppressWarnings("SameParameterValue") + static HiveSqlDialect hiveDialect(int majorVersion, int minorVersion) { + return new HiveSqlDialect(HiveSqlDialect.DEFAULT_CONTEXT + .withDatabaseMajorVersion(majorVersion) + .withDatabaseMinorVersion(minorVersion) + .withNullCollation(NullCollation.LOW)); Review Comment: DEFAULT_CONTEXT in HiveSqlDialect already has the `NullCollation.LOW`. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
