From 0bcce380d224c537fa96f39ea93e2b15a9ee5eca Mon Sep 17 00:00:00 2001 From: Wang Weidong Date: Tue, 20 Aug 2019 21:05:29 +0800 Subject: [PATCH 1/4] [CALCITE-3210] JDBC adapter should generate "CAST(NULL AS type)" rather than "NULL" conditionally (Wang Weidong) --- .../rel/rel2sql/RelToSqlConverter.java | 16 ++++++ .../rel/rel2sql/RelToSqlConverterTest.java | 55 +++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java index 0899ea46b7fa..23e57108d0e0 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java @@ -41,6 +41,7 @@ import org.apache.calcite.rel.logical.LogicalProject; import org.apache.calcite.rel.logical.LogicalSort; import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeField; import org.apache.calcite.rex.RexBuilder; import org.apache.calcite.rex.RexCall; import org.apache.calcite.rex.RexInputRef; @@ -64,6 +65,7 @@ import org.apache.calcite.sql.SqlNodeList; import org.apache.calcite.sql.SqlSelect; import org.apache.calcite.sql.SqlUpdate; +import org.apache.calcite.sql.SqlUtil; import org.apache.calcite.sql.fun.SqlRowOperator; import org.apache.calcite.sql.fun.SqlSingleValueAggFunction; import org.apache.calcite.sql.fun.SqlStdOperatorTable; @@ -199,6 +201,9 @@ public Result visit(Project e) { final List selectList = new ArrayList<>(); for (RexNode ref : e.getChildExps()) { SqlNode sqlExpr = builder.context.toSql(null, ref); + if (SqlUtil.isNullLiteral(sqlExpr, false)) { + sqlExpr = castNullType(sqlExpr, e.getRowType().getFieldList().get(selectList.size())); + } addSelect(selectList, sqlExpr, e.getRowType()); } @@ -206,6 +211,17 @@ public Result visit(Project e) { return builder.result(); } + /** + * Wrap the {@code sqlNodeNull} in a CAST operator with target type as {@code field}. + * @param sqlNodeNull null literal + * @param field field description of sqlNodeNull + * @return null literal wrapped in CAST call. + */ + private SqlNode castNullType(SqlNode sqlNodeNull, RelDataTypeField field) { + return SqlStdOperatorTable.CAST.createCall(POS, + sqlNodeNull, dialect.getCastSpec(field.getType())); + } + /** @see #dispatch */ public Result visit(Aggregate e) { return visitAggregate(e, e.getGroupSet().toList()); 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 f013e1f68a2a..22818a68aa58 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 @@ -3694,6 +3694,61 @@ private void checkLiteral2(String expression, String expected) { assertTrue(postgresqlDialect.supportsDataType(integerDataType)); } + @Test public void testSelectNull() { + String query = "SELECT CAST(NULL AS INT)"; + final String expected = "SELECT CAST(NULL AS INTEGER)\n" + + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")"; + sql(query).ok(expected); + // validate + sql(expected).exec(); + } + + @Test public void testSelectNullWithCount() { + String query = "SELECT COUNT(CAST(NULL AS INT))"; + final String expected = "SELECT COUNT(CAST(NULL AS INTEGER))\n" + + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")"; + sql(query).ok(expected); + // validate + sql(expected).exec(); + } + + @Test public void testSelectNullWithGroupByNull() { + String query = "SELECT COUNT(CAST(NULL AS INT)) FROM (VALUES (0)) " + + "AS \"t\" GROUP BY CAST(NULL AS VARCHAR CHARACTER SET \"ISO-8859-1\")"; + final String expected = "SELECT COUNT(CAST(NULL AS INTEGER))\n" + + "FROM (VALUES (0)) AS \"t\" (\"EXPR$0\")\nGROUP BY CAST(NULL " + + "AS VARCHAR CHARACTER SET \"ISO-8859-1\")"; + sql(query).ok(expected); + // validate + sql(expected).exec(); + } + + @Test public void testSelectNullWithGroupByVar() { + String query = "SELECT COUNT(CAST(NULL AS INT)) FROM \"account\" " + + "AS \"t\" GROUP BY \"account_type\""; + final String expected = "SELECT COUNT(CAST(NULL AS INTEGER))\n" + + "FROM \"foodmart\".\"account\"\n" + + "GROUP BY \"account_type\""; + sql(query).ok(expected); + // validate + sql(expected).exec(); + } + + @Test public void testSelectNullWithInsert() { + String query = "insert into " + + "\"account\"(\"account_id\",\"account_parent\",\"account_type\",\"account_rollup\")" + + " select 1, cast(NULL AS INT), cast(123 as varchar), cast(123 as varchar)"; + final String expected = "INSERT INTO \"foodmart\".\"account\" (\"account_id\", \"account_parent\", \"account_description\", " + + "\"account_type\", \"account_rollup\", \"Custom_Members\")\n" + + "(SELECT 1 AS \"account_id\", CAST(NULL AS INTEGER) AS \"account_parent\", CAST(NULL AS VARCHAR(30) CHARACTER SET " + + "\"ISO-8859-1\") AS \"account_description\", '123' AS \"account_type\", '123' AS \"account_rollup\", CAST(NULL AS VARCHAR" + + "(255) CHARACTER SET \"ISO-8859-1\") AS \"Custom_Members\"\n" + + "FROM (VALUES (0)) AS \"t\" (\"ZERO\"))"; + sql(query).ok(expected); + // validate + sql(expected).exec(); + } + @Test public void testDialectQuoteStringLiteral() { dialects().forEach((dialect, databaseProduct) -> { assertThat(dialect.quoteStringLiteral(""), is("''")); From f96b1877fdf312ca9fe518d93a801534d95d8ed8 Mon Sep 17 00:00:00 2001 From: Wang Weidong Date: Wed, 21 Aug 2019 11:04:36 +0800 Subject: [PATCH 2/4] [CALCITE-3210] JDBC adapter should generate "CAST(NULL AS type)" rather than "NULL" conditionally (Wang Weidong) Add test case of "testSelectNullWithInsertFromJoin". --- .../rel/rel2sql/RelToSqlConverterTest.java | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) 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 22818a68aa58..0946cc2946b1 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 @@ -3738,10 +3738,14 @@ private void checkLiteral2(String expression, String expected) { String query = "insert into " + "\"account\"(\"account_id\",\"account_parent\",\"account_type\",\"account_rollup\")" + " select 1, cast(NULL AS INT), cast(123 as varchar), cast(123 as varchar)"; - final String expected = "INSERT INTO \"foodmart\".\"account\" (\"account_id\", \"account_parent\", \"account_description\", " + final String expected = "INSERT INTO \"foodmart\".\"account\" (" + + "\"account_id\", \"account_parent\", \"account_description\", " + "\"account_type\", \"account_rollup\", \"Custom_Members\")\n" - + "(SELECT 1 AS \"account_id\", CAST(NULL AS INTEGER) AS \"account_parent\", CAST(NULL AS VARCHAR(30) CHARACTER SET " - + "\"ISO-8859-1\") AS \"account_description\", '123' AS \"account_type\", '123' AS \"account_rollup\", CAST(NULL AS VARCHAR" + + "(SELECT 1 AS \"account_id\", CAST(NULL AS INTEGER) AS \"account_parent\"," + + " CAST(NULL AS VARCHAR(30) CHARACTER SET " + + "\"ISO-8859-1\") AS \"account_description\", '123' AS \"account_type\", " + + "" + + "'123' AS \"account_rollup\", CAST(NULL AS VARCHAR" + "(255) CHARACTER SET \"ISO-8859-1\") AS \"Custom_Members\"\n" + "FROM (VALUES (0)) AS \"t\" (\"ZERO\"))"; sql(query).ok(expected); @@ -3749,6 +3753,36 @@ private void checkLiteral2(String expression, String expected) { sql(expected).exec(); } + @Test public void testSelectNullWithInsertFromJoin() { + String query = "insert into " + + "\"account\"(\"account_id\",\"account_parent\",\"account_type\",\"account_rollup\")" + + "\tselect \"product\".\"product_id\", \n" + + "\tcast(NULL AS INT), \n" + + "\tcast(\"product\".\"product_id\" as varchar), \n" + + "\tcast(\"sales_fact_1997\".\"store_id\" as varchar)\n" + + " from \"product\"\n" + + " inner join \"sales_fact_1997\"\n" + + " on \"product\".\"product_id\" = \"sales_fact_1997\".\"product_id\""; + final String expected = "INSERT INTO \"foodmart\".\"account\" " + + "(\"account_id\", \"account_parent\", \"account_description\", " + + "\"account_type\", \"account_rollup\", \"Custom_Members\")\n" + + "(SELECT \"product\".\"product_id\" AS \"account_id\", " + + "CAST(NULL AS INTEGER) AS \"account_parent\", CAST(NULL AS VARCHAR" + + "(30) CHARACTER SET \"ISO-8859-1\") AS \"account_description\", " + + "CAST(\"product\".\"product_id\" AS VARCHAR CHARACTER SET " + + "\"ISO-8859-1\") AS \"account_type\", " + + "CAST(\"sales_fact_1997\".\"store_id\" AS VARCHAR CHARACTER SET \"ISO-8859-1\") AS " + + "\"account_rollup\", " + + "CAST(NULL AS VARCHAR(255) CHARACTER SET \"ISO-8859-1\") AS \"Custom_Members\"\n" + + "FROM \"foodmart\".\"product\"\n" + + "INNER JOIN \"foodmart\".\"sales_fact_1997\" " + + "ON \"product\".\"product_id\" = \"sales_fact_1997\".\"product_id\")"; + sql(query).ok(expected); + // validate + sql(expected).exec(); + } + + @Test public void testDialectQuoteStringLiteral() { dialects().forEach((dialect, databaseProduct) -> { assertThat(dialect.quoteStringLiteral(""), is("''")); From dfdb5e03c2a666b611959707fdbf7b484e7ba26e Mon Sep 17 00:00:00 2001 From: Wang Weidong Date: Sun, 25 Aug 2019 11:39:01 +0800 Subject: [PATCH 3/4] [CALCITE-3210] JDBC adapter should generate "CAST(NULL AS type)" rather than "NULL" conditionally (Wang Weidong) Modify format of test case. --- .../calcite/rel/rel2sql/RelToSqlConverterTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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 0946cc2946b1..9c0efd353995 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 @@ -3754,12 +3754,13 @@ private void checkLiteral2(String expression, String expected) { } @Test public void testSelectNullWithInsertFromJoin() { - String query = "insert into " - + "\"account\"(\"account_id\",\"account_parent\",\"account_type\",\"account_rollup\")" - + "\tselect \"product\".\"product_id\", \n" - + "\tcast(NULL AS INT), \n" - + "\tcast(\"product\".\"product_id\" as varchar), \n" - + "\tcast(\"sales_fact_1997\".\"store_id\" as varchar)\n" + String query = "insert into \n" + + "\"account\"(\"account_id\",\"account_parent\",\n" + + "\"account_type\",\"account_rollup\")\n" + + "select \"product\".\"product_id\", \n" + + "cast(NULL AS INT), \n" + + "cast(\"product\".\"product_id\" as varchar), \n" + + "cast(\"sales_fact_1997\".\"store_id\" as varchar)\n" + " from \"product\"\n" + " inner join \"sales_fact_1997\"\n" + " on \"product\".\"product_id\" = \"sales_fact_1997\".\"product_id\""; From 82c118acfb557721f3721c21777de9d37dca9071 Mon Sep 17 00:00:00 2001 From: Wang Weidong Date: Sun, 25 Aug 2019 11:41:19 +0800 Subject: [PATCH 4/4] [CALCITE-3210] JDBC adapter should generate "CAST(NULL AS type)" rather than "NULL" conditionally (Wang Weidong) Modify format of test case. --- .../apache/calcite/rel/rel2sql/RelToSqlConverterTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 9c0efd353995..2091b540b25f 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 @@ -3761,9 +3761,9 @@ private void checkLiteral2(String expression, String expected) { + "cast(NULL AS INT), \n" + "cast(\"product\".\"product_id\" as varchar), \n" + "cast(\"sales_fact_1997\".\"store_id\" as varchar)\n" - + " from \"product\"\n" - + " inner join \"sales_fact_1997\"\n" - + " on \"product\".\"product_id\" = \"sales_fact_1997\".\"product_id\""; + + "from \"product\"\n" + + "inner join \"sales_fact_1997\"\n" + + "on \"product\".\"product_id\" = \"sales_fact_1997\".\"product_id\""; final String expected = "INSERT INTO \"foodmart\".\"account\" " + "(\"account_id\", \"account_parent\", \"account_description\", " + "\"account_type\", \"account_rollup\", \"Custom_Members\")\n"