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 0ec16e07fc [CALCITE-5085] Firebolt dialect implementation
0ec16e07fc is described below
commit 0ec16e07fca341e5fefa5c6898fae68569c1271f
Author: raghavsharma <[email protected]>
AuthorDate: Fri Feb 11 13:57:50 2022 +0530
[CALCITE-5085] Firebolt dialect implementation
Close apache/calcite#2764
---
.../java/org/apache/calcite/sql/SqlDialect.java | 3 +
.../apache/calcite/sql/SqlDialectFactoryImpl.java | 7 +
.../calcite/sql/dialect/FireboltSqlDialect.java | 199 +++++++++++++++++++++
.../calcite/rel/rel2sql/RelToSqlConverterTest.java | 14 ++
4 files changed, 223 insertions(+)
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 8770f81e0d..5172831e42 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlDialect.java
@@ -274,6 +274,8 @@ public class SqlDialect {
return DatabaseProduct.DERBY;
case "EXASOL":
return DatabaseProduct.EXASOL;
+ case "FIREBOLT":
+ return DatabaseProduct.FIREBOLT;
case "HIVE":
return DatabaseProduct.HIVE;
case "INGRES":
@@ -1306,6 +1308,7 @@ public class SqlDialect {
DB2("IBM DB2", null, NullCollation.HIGH),
EXASOL("Exasol", "\"", NullCollation.LOW),
FIREBIRD("Firebird", null, NullCollation.HIGH),
+ FIREBOLT("Firebolt", "\"", NullCollation.LOW),
H2("H2", "\"", NullCollation.HIGH),
HIVE("Apache Hive", null, NullCollation.LOW),
INFORMIX("Informix", null, NullCollation.HIGH),
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 890a0b8807..1454620bb5 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.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.FireboltSqlDialect;
import org.apache.calcite.sql.dialect.H2SqlDialect;
import org.apache.calcite.sql.dialect.HiveSqlDialect;
import org.apache.calcite.sql.dialect.HsqldbSqlDialect;
@@ -89,6 +90,8 @@ public class SqlDialectFactoryImpl implements
SqlDialectFactory {
return new DerbySqlDialect(c);
case "EXASOL":
return new ExasolSqlDialect(c);
+ case "FIREBOLT":
+ return new FireboltSqlDialect(c);
case "HIVE":
return new HiveSqlDialect(c);
case "INGRES":
@@ -124,6 +127,8 @@ public class SqlDialectFactoryImpl implements
SqlDialectFactory {
return new Db2SqlDialect(c);
} else if (upperProductName.contains("FIREBIRD")) {
return new FirebirdSqlDialect(c);
+ } else if (upperProductName.contains("FIREBOLT")) {
+ return new FireboltSqlDialect(c);
} else if (upperProductName.contains("GOOGLE BIGQUERY")
|| upperProductName.contains("GOOGLE BIG QUERY")) {
return new BigQuerySqlDialect(c);
@@ -178,6 +183,8 @@ public class SqlDialectFactoryImpl implements
SqlDialectFactory {
return ExasolSqlDialect.DEFAULT;
case FIREBIRD:
return FirebirdSqlDialect.DEFAULT;
+ case FIREBOLT:
+ return FireboltSqlDialect.DEFAULT;
case H2:
return H2SqlDialect.DEFAULT;
case HIVE:
diff --git
a/core/src/main/java/org/apache/calcite/sql/dialect/FireboltSqlDialect.java
b/core/src/main/java/org/apache/calcite/sql/dialect/FireboltSqlDialect.java
new file mode 100644
index 0000000000..71ca131875
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/dialect/FireboltSqlDialect.java
@@ -0,0 +1,199 @@
+/*
+ * 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.avatica.util.TimeUnitRange;
+import org.apache.calcite.config.NullCollation;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.SqlAlienSystemTypeNameSpec;
+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.SqlLiteral;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.fun.SqlFloorFunction;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.RelToSqlConverterUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+/**
+ * A SqlDialect implementation for the Firebolt database.
+ */
+public class FireboltSqlDialect extends SqlDialect {
+ public static final SqlDialect.Context DEFAULT_CONTEXT =
SqlDialect.EMPTY_CONTEXT
+ .withDatabaseProduct(DatabaseProduct.FIREBOLT)
+ .withIdentifierQuoteString("\"")
+ .withNullCollation(NullCollation.LOW);
+
+ public static final SqlDialect DEFAULT =
+ new FireboltSqlDialect(DEFAULT_CONTEXT);
+
+ /** Creates a FireboltSqlDialect. */
+ public FireboltSqlDialect(Context context) {
+ super(context);
+ }
+
+ /** Reserved Keywords for Firebolt. */
+ private static final List<String> RESERVED_KEYWORDS =
+ ImmutableList.copyOf(
+ Arrays.asList("ALL", "ALTER", "AND", "ARRAY", "BETWEEN",
+ "BIGINT", "BOOL", "BOOLEAN", "BOTH", "CASE",
+ "CAST", "CHAR", "CONCAT", "COPY", "CREATE", "CROSS",
+ "CURRENT_DATE", "CURRENT_TIMESTAMP", "DATABASE",
+ "DATE", "DATETIME", "DECIMAL", "DELETE", "DESCRIBE",
+ "DISTINCT", "DOUBLE", "DOUBLECOLON", "DOW", "DOY",
+ "DROP", "EMPTY_IDENTIFIER", "EPOCH", "EXCEPT", "EXECUTE",
+ "EXISTS", "EXPLAIN", "EXTRACT", "FALSE",
+ "FETCH", "FIRST", "FLOAT", "FROM", "FULL", "GENERATE",
+ "GROUP", "HAVING", "IF", "ILIKE", "IN", "INNER",
+ "INSERT", "INT", "INTEGER", "INTERSECT", "INTERVAL", "IS",
+ "ISNULL", "JOIN", "JOIN_TYPE", "LEADING",
+ "LEFT", "LIKE", "LIMIT", "LIMIT_DISTINCT", "LOCALTIMESTAMP",
+ "LONG", "NATURAL", "NEXT", "NOT", "NULL",
+ "NUMERIC", "OFFSET", "ON", "ONLY", "OR", "ORDER", "OUTER",
+ "OVER", "PARTITION", "PRECISION", "PREPARE",
+ "PRIMARY", "QUARTER", "RIGHT", "ROW", "ROWS", "SAMPLE",
+ "SELECT", "SET", "SHOW", "TEXT", "TIME", "TIMESTAMP",
+ "TOP", "TRAILING", "TRIM", "TRUE", "TRUNCATE", "UNION",
+ "UNKNOWN_CHAR", "UNNEST", "UNTERMINATED_STRING",
+ "UPDATE", "USING", "VARCHAR", "WEEK", "WHEN", "WHERE",
+ "WITH"));
+
+ /** An unquoted Firebolt 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_]*");
+
+ @Override public boolean supportsCharSet() {
+ return false;
+ }
+
+ @Override public void unparseOffsetFetch(SqlWriter writer, @Nullable SqlNode
offset,
+ @Nullable SqlNode fetch) {
+ unparseFetchUsingLimit(writer, offset, fetch);
+ }
+
+ @Override public boolean supportsAggregateFunction(SqlKind kind) {
+ switch (kind) {
+ case ANY_VALUE:
+ case AVG:
+ case COUNT:
+ case MAX:
+ case MIN:
+ case STDDEV_SAMP:
+ case SUM:
+ return true;
+ default:
+ break;
+ }
+ 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) {
+ String castSpec;
+ switch (type.getSqlTypeName()) {
+ case TINYINT:
+ case SMALLINT:
+ // Firebolt has no tinyint or smallint, so instead cast to INT
+ // fall through
+ castSpec = "INT";
+ break;
+ case TIME:
+ case TIME_WITH_LOCAL_TIME_ZONE:
+ case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+ // Firebolt has no TIME, TimeWithLocalTimezone and
TimestampWithLocalTimezone
+ // so instead cast all those to TIMESTAMP
+ // fall through
+ castSpec = "TIMESTAMP";
+ break;
+ case CHAR:
+ // Firebolt has no CHAR, so instead cast to VARCHAR
+ castSpec = "VARCHAR";
+ break;
+ case DECIMAL:
+ // Firebolt has no DECIMAL, so instead cast to FLOAT
+ castSpec = "FLOAT";
+ break;
+ case REAL:
+ // Firebolt has no REAL, so instead cast to DOUBLE
+ castSpec = "DOUBLE";
+ break;
+ default:
+ return super.getCastSpec(type);
+ }
+ // returning a type specification representing a type.
+ return new SqlDataTypeSpec(
+ new SqlAlienSystemTypeNameSpec(castSpec, type.getSqlTypeName(),
SqlParserPos.ZERO),
+ SqlParserPos.ZERO);
+ }
+
+ @Override public boolean supportsFunction(SqlOperator operator,
+ RelDataType type, final List<RelDataType> paramTypes) {
+ switch (operator.kind) {
+ case LIKE:
+ // introduces support for ILIKE as well
+ case EXISTS:
+ // introduces support for EXISTS as well
+ return true;
+ default:
+ return super.supportsFunction(operator, type, paramTypes);
+ }
+ }
+
+ @Override public void unparseCall(SqlWriter writer, SqlCall call, int
leftPrec, int rightPrec) {
+ if (call.getOperator() == SqlStdOperatorTable.SUBSTRING) {
+ RelToSqlConverterUtil.specialOperatorByName("SUBSTR")
+ .unparse(writer, call, 0, 0);
+ } else {
+ switch (call.getKind()) {
+ case FLOOR:
+ if (call.operandCount() != 2) {
+ super.unparseCall(writer, call, leftPrec, rightPrec);
+ return;
+ }
+
+ final SqlLiteral timeUnitNode = call.operand(1);
+ final TimeUnitRange timeUnit =
timeUnitNode.getValueAs(TimeUnitRange.class);
+
+ SqlCall call2 = SqlFloorFunction.replaceTimeUnitOperand(call,
timeUnit.name(),
+ timeUnitNode.getParserPosition());
+ SqlFloorFunction.unparseDatetimeFunction(writer, call2, "DATE_TRUNC",
false);
+ break;
+
+ default:
+ super.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 58601616e8..c2d235c132 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
@@ -3669,8 +3669,10 @@ class RelToSqlConverterTest {
+ "FROM \"foodmart\".\"employee\"";
String expectedPresto = "SELECT DATE_TRUNC('MINUTE', \"hire_date\")\n"
+ "FROM \"foodmart\".\"employee\"";
+ String expectedFirebolt = expectedPostgresql;
sql(query)
.withClickHouse().ok(expectedClickHouse)
+ .withFirebolt().ok(expectedFirebolt)
.withHsqldb().ok(expectedHsqldb)
.withOracle().ok(expectedOracle)
.withPostgresql().ok(expectedPostgresql)
@@ -3914,8 +3916,10 @@ class RelToSqlConverterTest {
+ " DATE_FORMAT(`hire_date`, '%Y-%m-%d %H:%i:00')\n"
+ "FROM `foodmart`.`employee`\n"
+ "GROUP BY DATE_FORMAT(`hire_date`, '%Y-%m-%d %H:%i:00')";
+ final String expectedFirebolt = expectedPostgresql;
sql(query)
.withClickHouse().ok(expectedClickHouse)
+ .withFirebolt().ok(expectedFirebolt)
.withHsqldb().ok(expected)
.withMysql().ok(expectedMysql)
.withOracle().ok(expectedOracle)
@@ -3935,10 +3939,12 @@ class RelToSqlConverterTest {
+ "FROM \"foodmart\".\"product\"";
final String expectedSnowflake = expectedPostgresql;
final String expectedRedshift = expectedPostgresql;
+ final String expectedFirebolt = expectedPresto;
final String expectedMysql = "SELECT SUBSTRING(`brand_name` FROM 2)\n"
+ "FROM `foodmart`.`product`";
sql(query)
.withClickHouse().ok(expectedClickHouse)
+ .withFirebolt().ok(expectedFirebolt)
.withMssql()
// mssql does not support this syntax and so should fail
.throws_("MSSQL SUBSTRING requires FROM and FOR arguments")
@@ -3963,12 +3969,14 @@ class RelToSqlConverterTest {
+ "FROM \"foodmart\".\"product\"";
final String expectedSnowflake = expectedPostgresql;
final String expectedRedshift = expectedPostgresql;
+ final String expectedFirebolt = expectedPresto;
final String expectedMysql = "SELECT SUBSTRING(`brand_name` FROM 2 FOR
3)\n"
+ "FROM `foodmart`.`product`";
final String expectedMssql = "SELECT SUBSTRING([brand_name], 2, 3)\n"
+ "FROM [foodmart].[product]";
sql(query)
.withClickHouse().ok(expectedClickHouse)
+ .withFirebolt().ok(expectedFirebolt)
.withMysql().ok(expectedMysql)
.withMssql().ok(expectedMssql)
.withOracle().ok(expectedOracle)
@@ -5161,12 +5169,14 @@ class RelToSqlConverterTest {
+ "FROM (SELECT 1 AS a, 'x ' AS b\n"
+ "UNION ALL\n"
+ "SELECT 2 AS a, 'yy' AS b)";
+ final String expectedFirebolt = expectedPostgresql;
final String expectedSnowflake = expectedPostgresql;
final String expectedRedshift = "SELECT \"a\"\n"
+ "FROM (SELECT 1 AS \"a\", 'x ' AS \"b\"\n"
+ "UNION ALL\nSELECT 2 AS \"a\", 'yy' AS \"b\")";
sql(sql)
.withClickHouse().ok(expectedClickHouse)
+ .withFirebolt().ok(expectedFirebolt)
.withBigQuery().ok(expectedBigQuery)
.withHive().ok(expectedHive)
.withHsqldb().ok(expectedHsqldb)
@@ -6406,6 +6416,10 @@ class RelToSqlConverterTest {
return dialect(DatabaseProduct.EXASOL.getDialect());
}
+ Sql withFirebolt() {
+ return dialect(DatabaseProduct.FIREBOLT.getDialect());
+ }
+
Sql withHive() {
return dialect(DatabaseProduct.HIVE.getDialect());
}