Skip to content

Commit d12029c

Browse files
committed
[CALCITE-5529] Improve dialect tests, part 1: check each query against 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).
1 parent ce64da0 commit d12029c

File tree

12 files changed

+3647
-2029
lines changed

12 files changed

+3647
-2029
lines changed

core/src/main/java/org/apache/calcite/jdbc/CalciteConnectionImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ protected CalciteConnectionImpl(Driver driver, AvaticaFactory factory,
150150
requireNonNull(rootSchema != null
151151
? rootSchema
152152
: CalciteSchema.createRootSchema(true));
153+
154+
final String schema = cfg.schema();
155+
if (schema != null && !schema.isEmpty()) {
156+
try {
157+
setSchema(schema);
158+
} catch (SQLException e) {
159+
throw new AssertionError(e); // not possible
160+
}
161+
}
162+
153163
// Add dual table metadata when isSupportedDualTable return true
154164
if (cfg.conformance().isSupportedDualTable()) {
155165
SchemaPlus schemaPlus = this.rootSchema.plus();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.util;
18+
19+
import java.util.Map;
20+
import java.util.concurrent.ConcurrentHashMap;
21+
import java.util.concurrent.atomic.AtomicInteger;
22+
23+
/** Hands out tokens, and throws if they are not all released.
24+
*
25+
* <p>Typical use:
26+
*
27+
* <blockquote><pre>{@code
28+
* Token.Pool pool = Token.pool();
29+
* Token token1 = pool.token();
30+
* Token token2 = pool.token();
31+
* token1.close();
32+
* pool.assertEmpty(); // throws because token2 has not been closed
33+
* }</pre></blockquote>
34+
* */
35+
public class Token implements AutoCloseable {
36+
private final Pool pool;
37+
private final int id;
38+
private final StackTraceElement[] stackElements;
39+
40+
/** Creates a Token. Should only be called from {@link Pool#token()}. */
41+
private Token(Pool pool, int id, StackTraceElement[] stackElements) {
42+
this.pool = pool;
43+
this.id = id;
44+
this.stackElements = stackElements;
45+
}
46+
47+
@Override public String toString() {
48+
return Integer.toString(id);
49+
}
50+
51+
/** Releases this Token. */
52+
@Override public void close() {
53+
if (!pool.release(id)) {
54+
final RuntimeException x =
55+
new RuntimeException("token " + id + " has already released");
56+
x.setStackTrace(stackElements);
57+
throw x;
58+
}
59+
}
60+
61+
/** Creates a pool. */
62+
public static Pool pool() {
63+
return new Pool();
64+
}
65+
66+
/** A collection of tokens.
67+
*
68+
* <p>It is thread-safe. */
69+
public static class Pool {
70+
private final Map<Integer, Token> map = new ConcurrentHashMap<>();
71+
private final AtomicInteger ordinal = new AtomicInteger();
72+
73+
/** Creates a token. */
74+
public Token token() {
75+
return map.computeIfAbsent(ordinal.getAndIncrement(),
76+
id ->
77+
new Token(Pool.this, id, Thread.currentThread().getStackTrace()));
78+
}
79+
80+
/** Releases a token id. Should be called from {@link Token#close()}. */
81+
@SuppressWarnings("resource")
82+
private boolean release(int id) {
83+
return map.remove(id) != null;
84+
}
85+
86+
/** Throws if not all fixtures have been released. */
87+
public void assertEmpty() {
88+
int size = map.size();
89+
if (!map.isEmpty()) {
90+
final RuntimeException x =
91+
new RuntimeException("map should be empty, but contains " + size
92+
+ " tokens");
93+
x.setStackTrace(map.values().iterator().next().stackElements);
94+
throw x;
95+
}
96+
}
97+
}
98+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.rel.rel2sql;
18+
19+
import org.apache.calcite.config.NullCollation;
20+
import org.apache.calcite.sql.SqlDialect;
21+
import org.apache.calcite.sql.dialect.AnsiSqlDialect;
22+
23+
import static org.apache.calcite.rel.rel2sql.DialectTestConfigs.JETHRO_DIALECT_SUPPLIER;
24+
25+
/** Dialect code. */
26+
enum DialectCode {
27+
ANSI(new AnsiSqlDialect(SqlDialect.EMPTY_CONTEXT)),
28+
BIG_QUERY(SqlDialect.DatabaseProduct.BIG_QUERY),
29+
CALCITE(SqlDialect.DatabaseProduct.CALCITE),
30+
CLICKHOUSE(SqlDialect.DatabaseProduct.CLICKHOUSE),
31+
DB2(SqlDialect.DatabaseProduct.DB2),
32+
EXASOL(SqlDialect.DatabaseProduct.EXASOL),
33+
FIREBOLT(SqlDialect.DatabaseProduct.FIREBOLT),
34+
HIVE(SqlDialect.DatabaseProduct.HIVE),
35+
HIVE_2_0(DialectTestConfigs.hiveDialect(2, 0)),
36+
HIVE_2_1(DialectTestConfigs.hiveDialect(2, 1)),
37+
HIVE_2_2(DialectTestConfigs.hiveDialect(2, 2)),
38+
HSQLDB(SqlDialect.DatabaseProduct.HSQLDB),
39+
INFORMIX(SqlDialect.DatabaseProduct.INFORMIX),
40+
JETHRO(JETHRO_DIALECT_SUPPLIER.get()),
41+
MOCK(new MockSqlDialect()),
42+
MSSQL_2008(DialectTestConfigs.mssqlDialect(10)),
43+
MSSQL_2012(DialectTestConfigs.mssqlDialect(11)),
44+
MSSQL_2017(DialectTestConfigs.mssqlDialect(14)),
45+
MYSQL(SqlDialect.DatabaseProduct.MYSQL),
46+
MYSQL_8(DialectTestConfigs.mysqlDialect(8, null)),
47+
MYSQL_FIRST(DialectTestConfigs.mysqlDialect(8, NullCollation.FIRST)),
48+
MYSQL_HIGH(DialectTestConfigs.mysqlDialect(8, NullCollation.HIGH)),
49+
MYSQL_LAST(DialectTestConfigs.mysqlDialect(8, NullCollation.LAST)),
50+
NON_ORDINAL(DialectTestConfigs.nonOrdinalDialect()),
51+
ORACLE(SqlDialect.DatabaseProduct.ORACLE),
52+
ORACLE_11(DialectTestConfigs.oracleDialect(11, null)),
53+
ORACLE_12(DialectTestConfigs.oracleDialect(12, null)),
54+
ORACLE_19(DialectTestConfigs.oracleDialect(19, null)),
55+
ORACLE_23(DialectTestConfigs.oracleDialect(23, null)),
56+
/** Oracle dialect with max length for varchar set to 512. */
57+
ORACLE_MODIFIED(DialectTestConfigs.oracleDialect(12, 512)),
58+
POSTGRESQL(SqlDialect.DatabaseProduct.POSTGRESQL),
59+
/** Postgresql dialect with max length for varchar set to 256. */
60+
POSTGRESQL_MODIFIED(DialectTestConfigs.postgresqlDialect(256, false)),
61+
/** Postgresql dialect with modified decimal type. */
62+
POSTGRESQL_MODIFIED_DECIMAL(
63+
DialectTestConfigs.postgresqlDialect(null, true)),
64+
PRESTO(SqlDialect.DatabaseProduct.PRESTO),
65+
REDSHIFT(SqlDialect.DatabaseProduct.REDSHIFT),
66+
SNOWFLAKE(SqlDialect.DatabaseProduct.SNOWFLAKE),
67+
SPARK(SqlDialect.DatabaseProduct.SPARK),
68+
STARROCKS(SqlDialect.DatabaseProduct.STARROCKS),
69+
SYBASE(SqlDialect.DatabaseProduct.SYBASE),
70+
VERTICA(SqlDialect.DatabaseProduct.VERTICA);
71+
72+
private final DialectTestConfig.Dialect dialect;
73+
74+
DialectCode(SqlDialect.DatabaseProduct databaseProduct) {
75+
dialect = DialectTestConfig.Dialect.of(this, databaseProduct);
76+
}
77+
78+
DialectCode(SqlDialect sqlDialect) {
79+
dialect = DialectTestConfig.Dialect.of(this, sqlDialect);
80+
}
81+
82+
DialectTestConfig.Dialect toDialect() {
83+
return dialect;
84+
}
85+
}

0 commit comments

Comments
 (0)