Skip to content

Commit

Permalink
[CALCITE-5529] Improve dialect tests, part 1: check each query agains…
Browse files Browse the repository at this point in the history
…t a reference dialect

Create class RelToSqlFixture (was inner class
RelToSqlConverterTest.Sql). This fixture has a list of
dialects for which the test is enabled, and a reference
dialect (currently always Calcite). When the test calls
'done()' on the fixture, the fixture checks the query
against the reference dialect. In future, it will also
execute the query against each enabled dialect.

To ensure that each test remembers to call 'done'()', add a
'token pool' to the test framework. If a token has been opened
but not closed, the framework prints the call stack of the
'open' call and fails the test.

Add enum DialectCode, which defines the dialects (or dialect
configurations) that are possible to test. Adding a
DialectCode has a cost (e.g. a bunch of new Quidem recording
files) so we should not add one without a good reason.

After this change, all queries succeed against the reference
dialect (Calcite). A few cases are disabled due to bugs; we
should fix these bugs and enable the tests.

This is just part 1. Part 2 will be to get the tests to
succeed against another dialect (probably a local instance
of Postgres). Part 3 will be to use a Quidem recording rather
than a live Postgres instance. Part 4 will be to enable this
framework for other tests (e.g. SqlOperatorTest).
  • Loading branch information
julianhyde committed Feb 19, 2025
1 parent ce64da0 commit d12029c
Show file tree
Hide file tree
Showing 12 changed files with 3,647 additions and 2,029 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,16 @@ protected CalciteConnectionImpl(Driver driver, AvaticaFactory factory,
requireNonNull(rootSchema != null
? rootSchema
: CalciteSchema.createRootSchema(true));

final String schema = cfg.schema();
if (schema != null && !schema.isEmpty()) {
try {
setSchema(schema);
} catch (SQLException e) {
throw new AssertionError(e); // not possible
}
}

// Add dual table metadata when isSupportedDualTable return true
if (cfg.conformance().isSupportedDualTable()) {
SchemaPlus schemaPlus = this.rootSchema.plus();
Expand Down
98 changes: 98 additions & 0 deletions core/src/main/java/org/apache/calcite/util/Token.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* 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.util;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/** Hands out tokens, and throws if they are not all released.
*
* <p>Typical use:
*
* <blockquote><pre>{@code
* Token.Pool pool = Token.pool();
* Token token1 = pool.token();
* Token token2 = pool.token();
* token1.close();
* pool.assertEmpty(); // throws because token2 has not been closed
* }</pre></blockquote>
* */
public class Token implements AutoCloseable {
private final Pool pool;
private final int id;
private final StackTraceElement[] stackElements;

/** Creates a Token. Should only be called from {@link Pool#token()}. */
private Token(Pool pool, int id, StackTraceElement[] stackElements) {
this.pool = pool;
this.id = id;
this.stackElements = stackElements;
}

@Override public String toString() {
return Integer.toString(id);
}

/** Releases this Token. */
@Override public void close() {
if (!pool.release(id)) {
final RuntimeException x =
new RuntimeException("token " + id + " has already released");
x.setStackTrace(stackElements);
throw x;
}
}

/** Creates a pool. */
public static Pool pool() {
return new Pool();
}

/** A collection of tokens.
*
* <p>It is thread-safe. */
public static class Pool {
private final Map<Integer, Token> map = new ConcurrentHashMap<>();
private final AtomicInteger ordinal = new AtomicInteger();

/** Creates a token. */
public Token token() {
return map.computeIfAbsent(ordinal.getAndIncrement(),
id ->
new Token(Pool.this, id, Thread.currentThread().getStackTrace()));
}

/** Releases a token id. Should be called from {@link Token#close()}. */
@SuppressWarnings("resource")
private boolean release(int id) {
return map.remove(id) != null;
}

/** Throws if not all fixtures have been released. */
public void assertEmpty() {
int size = map.size();
if (!map.isEmpty()) {
final RuntimeException x =
new RuntimeException("map should be empty, but contains " + size
+ " tokens");
x.setStackTrace(map.values().iterator().next().stackElements);
throw x;
}
}
}
}
85 changes: 85 additions & 0 deletions core/src/test/java/org/apache/calcite/rel/rel2sql/DialectCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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.sql.SqlDialect;
import org.apache.calcite.sql.dialect.AnsiSqlDialect;

import static org.apache.calcite.rel.rel2sql.DialectTestConfigs.JETHRO_DIALECT_SUPPLIER;

/** Dialect code. */
enum DialectCode {
ANSI(new AnsiSqlDialect(SqlDialect.EMPTY_CONTEXT)),
BIG_QUERY(SqlDialect.DatabaseProduct.BIG_QUERY),
CALCITE(SqlDialect.DatabaseProduct.CALCITE),
CLICKHOUSE(SqlDialect.DatabaseProduct.CLICKHOUSE),
DB2(SqlDialect.DatabaseProduct.DB2),
EXASOL(SqlDialect.DatabaseProduct.EXASOL),
FIREBOLT(SqlDialect.DatabaseProduct.FIREBOLT),
HIVE(SqlDialect.DatabaseProduct.HIVE),
HIVE_2_0(DialectTestConfigs.hiveDialect(2, 0)),
HIVE_2_1(DialectTestConfigs.hiveDialect(2, 1)),
HIVE_2_2(DialectTestConfigs.hiveDialect(2, 2)),
HSQLDB(SqlDialect.DatabaseProduct.HSQLDB),
INFORMIX(SqlDialect.DatabaseProduct.INFORMIX),
JETHRO(JETHRO_DIALECT_SUPPLIER.get()),
MOCK(new MockSqlDialect()),
MSSQL_2008(DialectTestConfigs.mssqlDialect(10)),
MSSQL_2012(DialectTestConfigs.mssqlDialect(11)),
MSSQL_2017(DialectTestConfigs.mssqlDialect(14)),
MYSQL(SqlDialect.DatabaseProduct.MYSQL),
MYSQL_8(DialectTestConfigs.mysqlDialect(8, null)),
MYSQL_FIRST(DialectTestConfigs.mysqlDialect(8, NullCollation.FIRST)),
MYSQL_HIGH(DialectTestConfigs.mysqlDialect(8, NullCollation.HIGH)),
MYSQL_LAST(DialectTestConfigs.mysqlDialect(8, NullCollation.LAST)),
NON_ORDINAL(DialectTestConfigs.nonOrdinalDialect()),
ORACLE(SqlDialect.DatabaseProduct.ORACLE),
ORACLE_11(DialectTestConfigs.oracleDialect(11, null)),
ORACLE_12(DialectTestConfigs.oracleDialect(12, null)),
ORACLE_19(DialectTestConfigs.oracleDialect(19, null)),
ORACLE_23(DialectTestConfigs.oracleDialect(23, null)),
/** Oracle dialect with max length for varchar set to 512. */
ORACLE_MODIFIED(DialectTestConfigs.oracleDialect(12, 512)),
POSTGRESQL(SqlDialect.DatabaseProduct.POSTGRESQL),
/** Postgresql dialect with max length for varchar set to 256. */
POSTGRESQL_MODIFIED(DialectTestConfigs.postgresqlDialect(256, false)),
/** Postgresql dialect with modified decimal type. */
POSTGRESQL_MODIFIED_DECIMAL(
DialectTestConfigs.postgresqlDialect(null, true)),
PRESTO(SqlDialect.DatabaseProduct.PRESTO),
REDSHIFT(SqlDialect.DatabaseProduct.REDSHIFT),
SNOWFLAKE(SqlDialect.DatabaseProduct.SNOWFLAKE),
SPARK(SqlDialect.DatabaseProduct.SPARK),
STARROCKS(SqlDialect.DatabaseProduct.STARROCKS),
SYBASE(SqlDialect.DatabaseProduct.SYBASE),
VERTICA(SqlDialect.DatabaseProduct.VERTICA);

private final DialectTestConfig.Dialect dialect;

DialectCode(SqlDialect.DatabaseProduct databaseProduct) {
dialect = DialectTestConfig.Dialect.of(this, databaseProduct);
}

DialectCode(SqlDialect sqlDialect) {
dialect = DialectTestConfig.Dialect.of(this, sqlDialect);
}

DialectTestConfig.Dialect toDialect() {
return dialect;
}
}
Loading

0 comments on commit d12029c

Please sign in to comment.