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());
     }

Reply via email to