You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2021/07/01 20:34:47 UTC

[calcite] branch master updated: [CALCITE-4614] Exasol dialect implementation (TJ Banghart)

This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/master by this push:
     new f928e07  [CALCITE-4614] Exasol dialect implementation (TJ Banghart)
f928e07 is described below

commit f928e073c384010c294370b63ffb748c15caab8a
Author: TJ Banghart <tj...@google.com>
AuthorDate: Wed Jun 9 12:34:45 2021 -0700

    [CALCITE-4614] Exasol dialect implementation (TJ Banghart)
    
    Formatting; import SqlDialect.DatabaseProduct to make code more concise.
    
    Close apache/calcite#2431
---
 .../java/org/apache/calcite/sql/SqlDialect.java    |   3 +
 .../apache/calcite/sql/SqlDialectFactoryImpl.java  |   5 +
 .../calcite/sql/dialect/ExasolSqlDialect.java      | 183 +++++++++++++++++++++
 .../calcite/rel/rel2sql/RelToSqlConverterTest.java | 102 +++++++-----
 4 files changed, 252 insertions(+), 41 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
index fdb69fd..0f6f3fe 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
@@ -270,6 +270,8 @@ public class SqlDialect {
       return DatabaseProduct.CLICKHOUSE;
     case "DBMS:CLOUDSCAPE":
       return DatabaseProduct.DERBY;
+    case "EXASOL":
+      return DatabaseProduct.EXASOL;
     case "HIVE":
       return DatabaseProduct.HIVE;
     case "INGRES":
@@ -1263,6 +1265,7 @@ public class SqlDialect {
     ORACLE("Oracle", "\"", NullCollation.HIGH),
     DERBY("Apache Derby", null, NullCollation.HIGH),
     DB2("IBM DB2", null, NullCollation.HIGH),
+    EXASOL("Exasol", "\"", NullCollation.LOW),
     FIREBIRD("Firebird", null, NullCollation.HIGH),
     H2("H2", "\"", NullCollation.HIGH),
     HIVE("Apache Hive", null, NullCollation.LOW),
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlDialectFactoryImpl.java b/core/src/main/java/org/apache/calcite/sql/SqlDialectFactoryImpl.java
index 3a5aaeb..91f0f31 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDialectFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDialectFactoryImpl.java
@@ -25,6 +25,7 @@ import org.apache.calcite.sql.dialect.CalciteSqlDialect;
 import org.apache.calcite.sql.dialect.ClickHouseSqlDialect;
 import org.apache.calcite.sql.dialect.Db2SqlDialect;
 import org.apache.calcite.sql.dialect.DerbySqlDialect;
+import org.apache.calcite.sql.dialect.ExasolSqlDialect;
 import org.apache.calcite.sql.dialect.FirebirdSqlDialect;
 import org.apache.calcite.sql.dialect.H2SqlDialect;
 import org.apache.calcite.sql.dialect.HiveSqlDialect;
@@ -105,6 +106,8 @@ public class SqlDialectFactoryImpl implements SqlDialectFactory {
       return new ClickHouseSqlDialect(c);
     case "DBMS:CLOUDSCAPE":
       return new DerbySqlDialect(c);
+    case "EXASOL":
+      return new ExasolSqlDialect(c);
     case "HIVE":
       return new HiveSqlDialect(c);
     case "INGRES":
@@ -253,6 +256,8 @@ public class SqlDialectFactoryImpl implements SqlDialectFactory {
       return Db2SqlDialect.DEFAULT;
     case DERBY:
       return DerbySqlDialect.DEFAULT;
+    case EXASOL:
+      return ExasolSqlDialect.DEFAULT;
     case FIREBIRD:
       return FirebirdSqlDialect.DEFAULT;
     case H2:
diff --git a/core/src/main/java/org/apache/calcite/sql/dialect/ExasolSqlDialect.java b/core/src/main/java/org/apache/calcite/sql/dialect/ExasolSqlDialect.java
new file mode 100644
index 0000000..6e127cf
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/dialect/ExasolSqlDialect.java
@@ -0,0 +1,183 @@
+/*
+ * 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.dialect;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SqlBasicTypeNameSpec;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlDataTypeSpec;
+import org.apache.calcite.sql.SqlDialect;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.parser.SqlParserPos;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * A <code>SqlDialect</code> implementation for the Exasol database.
+ */
+public class ExasolSqlDialect extends SqlDialect {
+  public static final SqlDialect.Context DEFAULT_CONTEXT =
+      SqlDialect.EMPTY_CONTEXT
+          .withDatabaseProduct(DatabaseProduct.EXASOL)
+          .withIdentifierQuoteString("\"");
+
+  public static final SqlDialect DEFAULT = new ExasolSqlDialect(DEFAULT_CONTEXT);
+
+  /** An unquoted Exasol identifier must start with a letter and be followed
+   * by zero or more letters, digits or _. */
+  private static final Pattern IDENTIFIER_REGEX =
+      Pattern.compile("[A-Za-z][A-Za-z0-9_]*");
+
+  private static final List<String> RESERVED_KEYWORDS =
+      ImmutableList.of("ABS", "ACCESS", "ACOS", "ADAPTER", "ADD_DAYS",
+          "ADD_HOURS", "ADD_MINUTES", "ADD_MONTHS", "ADD_SECONDS", "ADD_WEEKS",
+          "ADD_YEARS", "ADMIN", "ALIGN", "ALWAYS", "ANALYZE", "ANSI",
+          "APPROXIMATE_COUNT_DISTINCT", "ASCII", "ASIN", "ASSIGNMENT",
+          "ASYMMETRIC", "ATAN", "ATAN2", "ATOMIC", "ATTEMPTS", "AUDIT",
+          "AUTHENTICATED", "AUTO", "AVG", "BACKUP", "BERNOULLI", "BIT_AND",
+          "BIT_CHECK", "BIT_LENGTH", "BIT_LROTATE", "BIT_LSHIFT", "BIT_NOT",
+          "BIT_OR", "BIT_RROTATE", "BIT_RSHIFT", "BIT_SET", "BIT_TO_NUM",
+          "BIT_XOR", "BREADTH", "CEIL", "CEILING", "CHANGE", "CHARACTERS",
+          "CHARACTER_LENGTH", "CHR", "CLEAR", "COBOL", "COLOGNE_PHONETIC",
+          "COMMENT", "COMMENTS", "COMMITTED", "CONCAT", "CONNECT", "CONVERT_TZ",
+          "CORR", "COS", "COSH", "COT", "COUNT", "COVAR_POP", "COVAR_SAMP",
+          "CREATED", "CROSS", "CURDATE", "DATABASE", "DATE_TRUNC",
+          "DAYS_BETWEEN", "DECODE", "DEFAULTS", "DEFAULT_PRIORITY_GROUP",
+          "DEGREES", "DELIMIT", "DELIMITER", "DENSE_RANK", "DEPTH",
+          "DIAGNOSTICS", "DICTIONARY", "DISTRIBUTE", "DISTRIBUTION", "DIV",
+          "DOWN", "DUMP", "EDIT_DISTANCE", "ENCODING", "ESTIMATE", "EVALUATE",
+          "EVERY", "EXA", "EXCLUDE", "EXCLUDING", "EXP", "EXPIRE", "EXPLAIN",
+          "EXPRESSION", "FAILED", "FIRST_VALUE", "FLOOR", "FLUSH", "FOREIGN",
+          "FORTRAN", "FROM_POSIX_TIME", "GREATEST", "GROUPING_ID", "HANDLER",
+          "HAS", "HASH", "HASH_MD5", "HASH_SHA", "HASH_SHA1", "HASH_SHA256",
+          "HASH_SHA512", "HASH_TIGER", "HIERARCHY", "HOURS_BETWEEN",
+          "IDENTIFIED", "IGNORE", "IMPERSONATION", "INCLUDING", "INITCAP",
+          "INITIALLY", "INSTR", "INVALID", "IPROC", "ISOLATION", "IS_BOOLEAN",
+          "IS_DATE", "IS_DSINTERVAL", "IS_NUMBER", "IS_TIMESTAMP",
+          "IS_YMINTERVAL", "JAVA", "JAVASCRIPT", "KEEP", "KEY", "KEYS", "KILL",
+          "LAG", "LANGUAGE", "LAST_VALUE", "LCASE", "LEAD", "LEAST", "LENGTH",
+          "LINK", "LN", "LOCATE", "LOCK", "LOG10", "LOG2", "LOGIN", "LOGS",
+          "LONG", "LOWER", "LPAD", "LTRIM", "LUA", "MANAGE", "MAX", "MAXIMAL",
+          "MEDIAN", "MID", "MIN", "MINUTES_BETWEEN", "MONTHS_BETWEEN", "MUMPS",
+          "NEVER", "NICE", "NORMALIZED", "NOW", "NPROC", "NULLIFZERO", "NULLS",
+          "NUMTODSINTERVAL", "NUMTOYMINTERVAL", "NVL", "NVL2", "OCTETS",
+          "OCTET_LENGTH", "OFFSET", "OPTIMIZE", "ORA", "OWNER", "PADDING",
+          "PARTITION", "PASCAL", "PASSWORD", "PASSWORD_EXPIRY_POLICY",
+          "PASSWORD_SECURITY_POLICY", "PERCENTILE_CONT", "PERCENTILE_DISC",
+          "PI", "PLI", "POSIX_TIME", "POWER", "PRECISION", "PRELOAD", "PRIMARY",
+          "PRIORITY", "PRIVILEGE", "PYTHON", "QUERY_CACHE", "QUERY_TIMEOUT",
+          "R", "RADIANS", "RAND", "RANK", "RATIO_TO_REPORT", "RAW_SIZE_LIMIT",
+          "RECOMPRESS", "RECORD", "REGEXP_INSTR", "REGEXP_REPLACE",
+          "REGEXP_SUBSTR", "REGR_AVGX", "REGR_AVGY", "REGR_COUNT",
+          "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY",
+          "REGR_SYY", "REJECT", "REORGANIZE", "REPEATABLE", "RESET", "REVERSE",
+          "ROLE", "ROUND", "ROWID", "ROW_NUMBER", "RPAD", "RTRIM", "SCALAR",
+          "SCHEMAS", "SCHEME", "SCRIPT_LANGUAGES", "SCRIPT_OUTPUT_ADDRESS",
+          "SECONDS_BETWEEN", "SECURE", "SERIALIZABLE", "SHUT", "SIGN", "SIMPLE",
+          "SIN", "SINH", "SIZE", "SKIP", "SOUNDEX", "SQRT", "STATISTICS",
+          "STDDEV", "STDDEV_POP", "STDDEV_SAMP", "ST_AREA", "ST_BOUNDARY",
+          "ST_BUFFER", "ST_CENTROID", "ST_CONTAINS", "ST_CONVEXHULL",
+          "ST_CROSSES", "ST_DIFFERENCE", "ST_DIMENSION", "ST_DISJOINT",
+          "ST_DISTANCE", "ST_ENDPOINT", "ST_ENVELOPE", "ST_EQUALS",
+          "ST_EXTERIORRING", "ST_FORCE2D", "ST_GEOMETRYN", "ST_GEOMETRYTYPE",
+          "ST_INTERIORRINGN", "ST_INTERSECTION", "ST_INTERSECTS", "ST_ISCLOSED",
+          "ST_ISEMPTY", "ST_ISRING", "ST_ISSIMPLE", "ST_LENGTH",
+          "ST_NUMGEOMETRIES", "ST_NUMINTERIORRINGS", "ST_NUMPOINTS",
+          "ST_OVERLAPS", "ST_POINTN", "ST_SETSRID", "ST_STARTPOINT",
+          "ST_SYMDIFFERENCE", "ST_TOUCHES", "ST_TRANSFORM", "ST_UNION",
+          "ST_WITHIN", "ST_X", "ST_Y", "SUBSTR", "SUM", "SYMMETRIC",
+          "SYS_CONNECT_BY_PATH", "SYS_GUID", "TABLES", "TABLESAMPLE", "TAN",
+          "TANH", "TASKS", "TIES", "TIMESTAMP_ARITHMETIC_BEHAVIOR", "TIME_ZONE",
+          "TIME_ZONE_BEHAVIOR", "TO_CHAR", "TO_DATE", "TO_DSINTERVAL",
+          "TO_NUMBER", "TO_TIMESTAMP", "TO_YMINTERVAL", "TRANSLATE", "TRUNC",
+          "TYPE", "UCASE", "UNBOUNDED", "UNCOMMITTED", "UNDO", "UNICODE",
+          "UNICODECHR", "UNLIMITED", "UPPER", "UTF8", "VALUE2PROC", "VARIANCE",
+          "VARYING", "VAR_POP", "VAR_SAMP", "VIRTUAL", "WEEK", "WEIGHT",
+          "WRITE", "YEARS_BETWEEN", "ZEROIFNULL");
+
+  /** Creates a ExasolSqlDialect. */
+  public ExasolSqlDialect(Context context) {
+    super(context);
+  }
+
+  @Override public boolean supportsCharSet() {
+    return false;
+  }
+
+  @Override public boolean supportsNestedAggregations() {
+    return false;
+  }
+
+  @Override public boolean supportsAggregateFunctionFilter() {
+    return false;
+  }
+
+  @Override public boolean supportsAggregateFunction(SqlKind kind) {
+    switch (kind) {
+    case AVG:
+    case COUNT:
+    case COVAR_POP:
+    case COVAR_SAMP:
+    case MAX:
+    case MIN:
+    case STDDEV_POP:
+    case STDDEV_SAMP:
+    case SUM:
+    case VAR_POP:
+    case VAR_SAMP:
+      return true;
+    default:
+      return false;
+    }
+  }
+
+  @Override protected boolean identifierNeedsQuote(String val) {
+    return !IDENTIFIER_REGEX.matcher(val).matches()
+        || RESERVED_KEYWORDS.contains(val.toUpperCase(Locale.ROOT));
+  }
+
+  @Override public @Nullable SqlNode getCastSpec(RelDataType type) {
+    switch (type.getSqlTypeName()) {
+    case TIMESTAMP:
+      // Exasol does not support TIMESTAMP with precision.
+      return new SqlDataTypeSpec(
+        new SqlBasicTypeNameSpec(type.getSqlTypeName(), SqlParserPos.ZERO),
+        SqlParserPos.ZERO);
+    default:
+      return super.getCastSpec(type);
+    }
+  }
+
+  @Override public void unparseOffsetFetch(SqlWriter writer, @Nullable SqlNode offset,
+    @Nullable SqlNode fetch) {
+    unparseFetchUsingLimit(writer, offset, fetch);
+  }
+
+  @Override public void unparseCall(SqlWriter writer, SqlCall call,
+    int leftPrec, int rightPrec) {
+    // Same as PostgreSQL implementation
+    PostgresqlSqlDialect.DEFAULT.unparseCall(writer, call, leftPrec, rightPrec);
+  }
+}
diff --git a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index b276a15..6851e82 100644
--- a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -41,7 +41,6 @@ import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDialect;
-import org.apache.calcite.sql.SqlDialect.Context;
 import org.apache.calcite.sql.SqlDialect.DatabaseProduct;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlSelect;
@@ -141,8 +140,8 @@ class RelToSqlConverterTest {
   }
 
   private static JethroDataSqlDialect jethroDataSqlDialect() {
-    Context dummyContext = SqlDialect.EMPTY_CONTEXT
-        .withDatabaseProduct(SqlDialect.DatabaseProduct.JETHRO)
+    SqlDialect.Context dummyContext = SqlDialect.EMPTY_CONTEXT
+        .withDatabaseProduct(DatabaseProduct.JETHRO)
         .withDatabaseMajorVersion(1)
         .withDatabaseMinorVersion(0)
         .withDatabaseVersion("1.0")
@@ -161,28 +160,18 @@ class RelToSqlConverterTest {
    * represent. */
   private static Map<SqlDialect, DatabaseProduct> dialects() {
     return ImmutableMap.<SqlDialect, DatabaseProduct>builder()
-        .put(SqlDialect.DatabaseProduct.BIG_QUERY.getDialect(),
-            SqlDialect.DatabaseProduct.BIG_QUERY)
-        .put(SqlDialect.DatabaseProduct.CALCITE.getDialect(),
-            SqlDialect.DatabaseProduct.CALCITE)
-        .put(SqlDialect.DatabaseProduct.DB2.getDialect(),
-            SqlDialect.DatabaseProduct.DB2)
-        .put(SqlDialect.DatabaseProduct.HIVE.getDialect(),
-            SqlDialect.DatabaseProduct.HIVE)
-        .put(jethroDataSqlDialect(),
-            SqlDialect.DatabaseProduct.JETHRO)
-        .put(SqlDialect.DatabaseProduct.MSSQL.getDialect(),
-            SqlDialect.DatabaseProduct.MSSQL)
-        .put(SqlDialect.DatabaseProduct.MYSQL.getDialect(),
-            SqlDialect.DatabaseProduct.MYSQL)
-        .put(mySqlDialect(NullCollation.HIGH),
-            SqlDialect.DatabaseProduct.MYSQL)
-        .put(SqlDialect.DatabaseProduct.ORACLE.getDialect(),
-            SqlDialect.DatabaseProduct.ORACLE)
-        .put(SqlDialect.DatabaseProduct.POSTGRESQL.getDialect(),
-            SqlDialect.DatabaseProduct.POSTGRESQL)
-        .put(DatabaseProduct.PRESTO.getDialect(),
-            DatabaseProduct.PRESTO)
+        .put(DatabaseProduct.BIG_QUERY.getDialect(), DatabaseProduct.BIG_QUERY)
+        .put(DatabaseProduct.CALCITE.getDialect(), DatabaseProduct.CALCITE)
+        .put(DatabaseProduct.DB2.getDialect(), DatabaseProduct.DB2)
+        .put(DatabaseProduct.EXASOL.getDialect(), DatabaseProduct.EXASOL)
+        .put(DatabaseProduct.HIVE.getDialect(), DatabaseProduct.HIVE)
+        .put(jethroDataSqlDialect(), DatabaseProduct.JETHRO)
+        .put(DatabaseProduct.MSSQL.getDialect(), DatabaseProduct.MSSQL)
+        .put(DatabaseProduct.MYSQL.getDialect(), DatabaseProduct.MYSQL)
+        .put(mySqlDialect(NullCollation.HIGH), DatabaseProduct.MYSQL)
+        .put(DatabaseProduct.ORACLE.getDialect(), DatabaseProduct.ORACLE)
+        .put(DatabaseProduct.POSTGRESQL.getDialect(), DatabaseProduct.POSTGRESQL)
+        .put(DatabaseProduct.PRESTO.getDialect(), DatabaseProduct.PRESTO)
         .build();
   }
 
@@ -193,7 +182,7 @@ class RelToSqlConverterTest {
 
   /** Converts a relational expression to SQL. */
   private String toSql(RelNode root) {
-    return toSql(root, SqlDialect.DatabaseProduct.CALCITE.getDialect());
+    return toSql(root, DatabaseProduct.CALCITE.getDialect());
   }
 
   /** Converts a relational expression to SQL in a given dialect. */
@@ -805,7 +794,7 @@ class RelToSqlConverterTest {
                     .mapToObj(i -> b.equals(b.field("EMPNO"), b.literal(i)))
                     .collect(Collectors.toList())))
         .build();
-    final SqlDialect dialect = SqlDialect.DatabaseProduct.CALCITE.getDialect();
+    final SqlDialect dialect = DatabaseProduct.CALCITE.getDialect();
     final RelNode root = relFn.apply(relBuilder());
     final RelToSqlConverter converter = new RelToSqlConverter(dialect);
     final SqlNode sqlNode = converter.visitRoot(root).asStatement();
@@ -1025,6 +1014,7 @@ class RelToSqlConverterTest {
         + "FROM foodmart.product\n"
         + "GROUP BY product_id) t1";
     final String expectedSpark = expectedHive;
+    final String expectedExasol = expectedBigQuery;
     sql(query)
         .withOracle()
         .ok(expectedOracle)
@@ -1039,7 +1029,9 @@ class RelToSqlConverterTest {
         .withHive()
         .ok(expectedHive)
         .withSpark()
-        .ok(expectedSpark);
+        .ok(expectedSpark)
+        .withExasol()
+        .ok(expectedExasol);
   }
 
   /** Test case for
@@ -1603,6 +1595,24 @@ class RelToSqlConverterTest {
     sql(query).withMssql().ok(expected);
   }
 
+  @Test void testExasolCharacterSet() {
+    String query = "select \"hire_date\", cast(\"hire_date\" as varchar(10))\n"
+        + "from \"foodmart\".\"reserve_employee\"";
+    final String expected = "SELECT hire_date, CAST(hire_date AS VARCHAR(10))\n"
+        + "FROM foodmart.reserve_employee";
+    sql(query).withExasol().ok(expected);
+  }
+
+  @Test void testExasolCastToTimestamp() {
+    final String query = "select  * from \"employee\" where  \"hire_date\" - "
+        + "INTERVAL '19800' SECOND(5) > cast(\"hire_date\" as TIMESTAMP(0))";
+    final String expected = "SELECT *\n"
+        + "FROM foodmart.employee\n"
+        + "WHERE (hire_date - INTERVAL '19800' SECOND(5))"
+        + " > CAST(hire_date AS TIMESTAMP)";
+    sql(query).withExasol().ok(expected);
+  }
+
   /**
    * Tests that IN can be un-parsed.
    *
@@ -1724,11 +1734,17 @@ class RelToSqlConverterTest {
         + "FROM \"foodmart\".\"days\") AS \"t\"\n"
         + "WHERE \"one\" < \"tWo\" AND \"THREE\" < \"fo$ur\"";
     final String expectedOracle = expectedPostgresql.replace(" AS ", " ");
+    final String expectedExasol = "SELECT *\n"
+        + "FROM (SELECT 1 AS one, 2 AS tWo, 3 AS THREE,"
+        + " 4 AS \"fo$ur\", 5 AS \"ignore\"\n"
+        + "FROM foodmart.days) AS t\n"
+        + "WHERE one < tWo AND THREE < \"fo$ur\"";
     sql(query)
         .withBigQuery().ok(expectedBigQuery)
         .withMysql().ok(expectedMysql)
         .withOracle().ok(expectedOracle)
-        .withPostgresql().ok(expectedPostgresql);
+        .withPostgresql().ok(expectedPostgresql)
+        .withExasol().ok(expectedExasol);
   }
 
   @Test void testModFunctionForHive() {
@@ -5390,10 +5406,10 @@ class RelToSqlConverterTest {
         new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
     final RelDataType booleanDataType = typeFactory.createSqlType(SqlTypeName.BOOLEAN);
     final RelDataType integerDataType = typeFactory.createSqlType(SqlTypeName.INTEGER);
-    final SqlDialect oracleDialect = SqlDialect.DatabaseProduct.ORACLE.getDialect();
+    final SqlDialect oracleDialect = DatabaseProduct.ORACLE.getDialect();
     assertFalse(oracleDialect.supportsDataType(booleanDataType));
     assertTrue(oracleDialect.supportsDataType(integerDataType));
-    final SqlDialect postgresqlDialect = SqlDialect.DatabaseProduct.POSTGRESQL.getDialect();
+    final SqlDialect postgresqlDialect = DatabaseProduct.POSTGRESQL.getDialect();
     assertTrue(postgresqlDialect.supportsDataType(booleanDataType));
     assertTrue(postgresqlDialect.supportsDataType(integerDataType));
   }
@@ -5803,23 +5819,27 @@ class RelToSqlConverterTest {
     }
 
     Sql withCalcite() {
-      return dialect(SqlDialect.DatabaseProduct.CALCITE.getDialect());
+      return dialect(DatabaseProduct.CALCITE.getDialect());
     }
 
     Sql withClickHouse() {
-      return dialect(SqlDialect.DatabaseProduct.CLICKHOUSE.getDialect());
+      return dialect(DatabaseProduct.CLICKHOUSE.getDialect());
     }
 
     Sql withDb2() {
-      return dialect(SqlDialect.DatabaseProduct.DB2.getDialect());
+      return dialect(DatabaseProduct.DB2.getDialect());
+    }
+
+    Sql withExasol() {
+      return dialect(DatabaseProduct.EXASOL.getDialect());
     }
 
     Sql withHive() {
-      return dialect(SqlDialect.DatabaseProduct.HIVE.getDialect());
+      return dialect(DatabaseProduct.HIVE.getDialect());
     }
 
     Sql withHsqldb() {
-      return dialect(SqlDialect.DatabaseProduct.HSQLDB.getDialect());
+      return dialect(DatabaseProduct.HSQLDB.getDialect());
     }
 
     Sql withMssql() {
@@ -5837,7 +5857,7 @@ class RelToSqlConverterTest {
     }
 
     Sql withMysql() {
-      return dialect(SqlDialect.DatabaseProduct.MYSQL.getDialect());
+      return dialect(DatabaseProduct.MYSQL.getDialect());
     }
 
     Sql withMysql8() {
@@ -5851,11 +5871,11 @@ class RelToSqlConverterTest {
     }
 
     Sql withOracle() {
-      return dialect(SqlDialect.DatabaseProduct.ORACLE.getDialect());
+      return dialect(DatabaseProduct.ORACLE.getDialect());
     }
 
     Sql withPostgresql() {
-      return dialect(SqlDialect.DatabaseProduct.POSTGRESQL.getDialect());
+      return dialect(DatabaseProduct.POSTGRESQL.getDialect());
     }
 
     Sql withPresto() {
@@ -5875,11 +5895,11 @@ class RelToSqlConverterTest {
     }
 
     Sql withVertica() {
-      return dialect(SqlDialect.DatabaseProduct.VERTICA.getDialect());
+      return dialect(DatabaseProduct.VERTICA.getDialect());
     }
 
     Sql withBigQuery() {
-      return dialect(SqlDialect.DatabaseProduct.BIG_QUERY.getDialect());
+      return dialect(DatabaseProduct.BIG_QUERY.getDialect());
     }
 
     Sql withSpark() {