[CALCITE-6290] Incorrect return type for BigQuery TRUNC

Adjust nullability for BigQuery's CEIL and FLOOR to correct [CALCITE-5747]
diff --git a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index bc76a59..0b9deb1 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -278,7 +278,7 @@
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TO_CODE_POINTS;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TO_HEX;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TRANSLATE3;
-import static org.apache.calcite.sql.fun.SqlLibraryOperators.TRUNC;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.TRUNC_BIG_QUERY;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.TRY_CAST;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.UNIX_DATE;
 import static org.apache.calcite.sql.fun.SqlLibraryOperators.UNIX_MICROS;
@@ -677,7 +677,7 @@
       defineMethod(SINH, BuiltInMethod.SINH.method, NullPolicy.STRICT);
       defineMethod(TAN, BuiltInMethod.TAN.method, NullPolicy.STRICT);
       defineMethod(TANH, BuiltInMethod.TANH.method, NullPolicy.STRICT);
-      defineMethod(TRUNC, BuiltInMethod.STRUNCATE.method, NullPolicy.STRICT);
+      defineMethod(TRUNC_BIG_QUERY, BuiltInMethod.STRUNCATE.method, NullPolicy.STRICT);
       defineMethod(TRUNCATE, BuiltInMethod.STRUNCATE.method, NullPolicy.STRICT);
 
       map.put(SAFE_ADD,
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index 86a72d2..f1166fd 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -456,7 +456,7 @@
       .withName("CEIL_BIG_QUERY")
       .withReturnTypeInference(ReturnTypes.ARG0_EXCEPT_INTEGER_NULLABLE);
 
-  /** The "FLOOR(value)" function. Identical to the stadnard <code>FLOOR</code> function
+  /** The "FLOOR(value)" function. Identical to the standard <code>FLOOR</code> function
    * except the return type should be a double if the operand is an integer. */
   @LibraryOperator(libraries = {BIG_QUERY})
   public static final SqlFunction FLOOR_BIG_QUERY = new SqlFloorFunction(SqlKind.FLOOR)
@@ -2161,9 +2161,12 @@
   public static final SqlFunction POW =
       SqlStdOperatorTable.POWER.withName("POW");
 
+  /** The "TRUNC(numeric1 [, integer2])" function. Identical to the standard <code>TRUNCATE</code>
+  * function except the return type should be a double if numeric1 is an integer. */
   @LibraryOperator(libraries = {BIG_QUERY})
-  public static final SqlFunction TRUNC =
-      SqlStdOperatorTable.TRUNCATE.withName("TRUNC");
+  public static final SqlFunction TRUNC_BIG_QUERY = SqlStdOperatorTable.TRUNCATE
+          .withName("TRUNC")
+          .withReturnTypeInference(ReturnTypes.ARG0_EXCEPT_INTEGER_NULLABLE);
 
   /** Infix "::" cast operator used by PostgreSQL, for example
    * {@code '100'::INTEGER}. */
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
index 13e6807..342284d 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
@@ -550,12 +550,12 @@
    */
   public static final SqlReturnTypeInference ARG0_EXCEPT_INTEGER = opBinding ->  {
     RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
-    SqlTypeName op = opBinding.getOperandType(0).getSqlTypeName();
-    if (SqlTypeName.INT_TYPES.contains(op)) {
+    RelDataType opType = opBinding.getOperandType(0);
+    if (SqlTypeName.INT_TYPES.contains(opType.getSqlTypeName())) {
       return typeFactory.createTypeWithNullability(
-          typeFactory.createSqlType(SqlTypeName.DOUBLE), true);
+          typeFactory.createSqlType(SqlTypeName.DOUBLE), false);
     } else {
-      return typeFactory.createTypeWithNullability(typeFactory.createSqlType(op), true);
+      return opType;
     }
   };
 
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 1de16cb..461ce04 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
@@ -592,6 +592,20 @@
         + "created_thing\nFROM foodmart.product");
   }
 
+    /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6290">[CALCITE-6290]
+   * Incorrect return type for BigQuery TRUNC</a>. */
+  @Test void testBigQueryTruncPreservesCast() {
+    final String query = "SELECT CAST(TRUNC(3) AS BIGINT) as created_thing\n"
+        + " FROM `foodmart`.`product`";
+    final SqlParser.Config parserConfig =
+        BigQuerySqlDialect.DEFAULT.configureParser(SqlParser.config());
+    final Sql sql = fixture()
+        .withBigQuery().withLibrary(SqlLibrary.BIG_QUERY).parserConfig(parserConfig);
+    sql.withSql(query).ok("SELECT CAST(TRUNC(3) AS INT64) AS created_thing\n"
+        + "FROM foodmart.product");
+  }
+
   @Test void testSelectLiteralAgg() {
     final Function<RelBuilder, RelNode> relFn = b -> b
         .scan("EMP")
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index 0ca4b26..1e81c68 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -8051,7 +8051,7 @@
   @Test void testTruncFail() {
     SqlOperatorFixture f = fixture();
     f = f.setFor(SqlStdOperatorTable.TRUNCATE, VmName.EXPAND)
-        .setFor(SqlLibraryOperators.TRUNC)
+        .setFor(SqlLibraryOperators.TRUNC_BIG_QUERY)
         .withLibrary(SqlLibrary.BIG_QUERY);
     f.checkFails("^truncate(42, CAST(2 as BIGINT))^",
         "Cannot apply 'TRUNCATE' to arguments of type 'TRUNCATE\\(<INTEGER>, <BIGINT>\\)'\\. "
@@ -8190,12 +8190,12 @@
 
   @Test void testTruncFunc() {
     final SqlOperatorFixture f = fixture()
-        .setFor(SqlLibraryOperators.TRUNC)
+        .setFor(SqlLibraryOperators.TRUNC_BIG_QUERY)
         .withLibrary(SqlLibrary.BIG_QUERY);
-    f.checkType("trunc(42, -1)", "INTEGER NOT NULL");
+    f.checkType("trunc(42, -1)", "DOUBLE NOT NULL");
     f.checkType("trunc(cast(42 as float), 1)", "FLOAT NOT NULL");
     f.checkType("trunc(case when false then 42 else null end, -1)",
-        "INTEGER");
+        "DOUBLE");
     f.enableTypeCoercion(false)
         .checkFails("^trunc('abc', 'def')^",
             "Cannot apply 'TRUNC' to arguments of type 'TRUNC\\(<CHAR\\(3\\)>, <CHAR\\(3\\)>\\)'\\."
@@ -8203,7 +8203,7 @@
                 + "TRUNC\\(<NUMERIC>, <INTEGER>\\)",
             false);
     f.checkType("trunc('abc', 'def')", "DECIMAL(19, 9) NOT NULL");
-    f.checkScalar("trunc(42, -1)", 40, "INTEGER NOT NULL");
+    f.checkScalar("trunc(42, -1)", 40.0, "DOUBLE NOT NULL");
     f.checkScalar("trunc(cast(42.345 as decimal(2, 3)), 2)",
         BigDecimal.valueOf(4234, 2), "DECIMAL(2, 3) NOT NULL");
     f.checkScalar("trunc(cast(-42.345 as decimal(2, 3)), 2)",
@@ -8212,7 +8212,7 @@
     f.checkNull("trunc(cast(null as double), 1)");
     f.checkNull("trunc(43.21, cast(null as integer))");
 
-    f.checkScalar("trunc(42)", 42, "INTEGER NOT NULL");
+    f.checkScalar("trunc(42)", 42.0, "DOUBLE NOT NULL");
     f.checkScalar("trunc(42.324)",
         BigDecimal.valueOf(42, 0), "DECIMAL(5, 3) NOT NULL");
     f.checkScalar("trunc(cast(42.324 as float))", 42F,
@@ -11307,14 +11307,14 @@
     f0.checkType("ceil(cast(3 as tinyint))", "TINYINT NOT NULL");
     final SqlOperatorFixture f = f0.setFor(SqlLibraryOperators.FLOOR_BIG_QUERY)
         .withLibrary(SqlLibrary.BIG_QUERY).withConformance(SqlConformanceEnum.BIG_QUERY);
-    f.checkScalarExact("ceil(cast(3 as tinyint))", "DOUBLE", "3.0");
-    f.checkScalarExact("ceil(cast(3 as smallint))", "DOUBLE", "3.0");
-    f.checkScalarExact("ceil(cast(3 as integer))", "DOUBLE", "3.0");
-    f.checkScalarExact("ceil(cast(3 as bigint))", "DOUBLE", "3.0");
-    f.checkScalarExact("ceil(cast(3.5 as double))", "DOUBLE", "4.0");
+    f.checkScalarExact("ceil(cast(3 as tinyint))", "DOUBLE NOT NULL", "3.0");
+    f.checkScalarExact("ceil(cast(3 as smallint))", "DOUBLE NOT NULL", "3.0");
+    f.checkScalarExact("ceil(cast(3 as integer))", "DOUBLE NOT NULL", "3.0");
+    f.checkScalarExact("ceil(cast(3 as bigint))", "DOUBLE NOT NULL", "3.0");
+    f.checkScalarExact("ceil(cast(3.5 as double))", "DOUBLE NOT NULL", "4.0");
     f.checkScalarExact("ceil(cast(3.45 as decimal))",
-        "DECIMAL(19, 0)", "4");
-    f.checkScalarExact("ceil(cast(3.45 as float))", "FLOAT", "4.0");
+        "DECIMAL(19, 0) NOT NULL", "4");
+    f.checkScalarExact("ceil(cast(3.45 as float))", "FLOAT NOT NULL", "4.0");
     f.checkNull("ceil(cast(null as tinyint))");
   }
 
@@ -11323,14 +11323,14 @@
     f0.checkType("floor(cast(3 as tinyint))", "TINYINT NOT NULL");
     final SqlOperatorFixture f = f0.setFor(SqlLibraryOperators.FLOOR_BIG_QUERY)
         .withLibrary(SqlLibrary.BIG_QUERY).withConformance(SqlConformanceEnum.BIG_QUERY);
-    f.checkScalarExact("floor(cast(3 as tinyint))", "DOUBLE", "3.0");
-    f.checkScalarExact("floor(cast(3 as smallint))", "DOUBLE", "3.0");
-    f.checkScalarExact("floor(cast(3 as integer))", "DOUBLE", "3.0");
-    f.checkScalarExact("floor(cast(3 as bigint))", "DOUBLE", "3.0");
-    f.checkScalarExact("floor(cast(3.5 as double))", "DOUBLE", "3.0");
+    f.checkScalarExact("floor(cast(3 as tinyint))", "DOUBLE NOT NULL", "3.0");
+    f.checkScalarExact("floor(cast(3 as smallint))", "DOUBLE NOT NULL", "3.0");
+    f.checkScalarExact("floor(cast(3 as integer))", "DOUBLE NOT NULL", "3.0");
+    f.checkScalarExact("floor(cast(3 as bigint))", "DOUBLE NOT NULL", "3.0");
+    f.checkScalarExact("floor(cast(3.5 as double))", "DOUBLE NOT NULL", "3.0");
     f.checkScalarExact("floor(cast(3.45 as decimal))",
-        "DECIMAL(19, 0)", "3");
-    f.checkScalarExact("floor(cast(3.45 as float))", "FLOAT", "3.0");
+        "DECIMAL(19, 0) NOT NULL", "3");
+    f.checkScalarExact("floor(cast(3.45 as float))", "FLOAT NOT NULL", "3.0");
     f.checkNull("floor(cast(null as tinyint))");
   }