This is an automated email from the ASF dual-hosted git repository.
xiong pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 12e7d621bb [CALCITE-7044] Add internal operator CAST NOT NULL to
enhance rewrite COALESCE operator
12e7d621bb is described below
commit 12e7d621bbb19aa32cb45329cb84532115037b7a
Author: Xiong Duan <[email protected]>
AuthorDate: Wed May 28 17:20:14 2025 +0800
[CALCITE-7044] Add internal operator CAST NOT NULL to enhance rewrite
COALESCE operator
---
.../main/java/org/apache/calcite/sql/SqlKind.java | 3 ++
.../calcite/sql/fun/SqlCoalesceFunction.java | 2 +-
.../calcite/sql/fun/SqlInternalOperators.java | 34 ++++++++++++++++++++++
.../calcite/sql2rel/StandardConvertletTable.java | 8 +++++
.../apache/calcite/test/SqlToRelConverterTest.java | 12 ++++++++
.../org/apache/calcite/test/RelOptRulesTest.xml | 2 +-
.../apache/calcite/test/SqlToRelConverterTest.xml | 25 +++++++++++++++-
core/src/test/resources/sql/sub-query.iq | 4 +--
8 files changed, 85 insertions(+), 5 deletions(-)
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index 1063b9df90..d2c57864b8 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -662,6 +662,9 @@ public enum SqlKind {
/** {@code IS NOT NULL} operator. */
IS_NOT_NULL,
+ /** {@code CAST NOT NULL} operator. */
+ CAST_NOT_NULL,
+
/** {@code PRECEDING} qualifier of an interval end-point in a window
* specification. */
PRECEDING,
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlCoalesceFunction.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlCoalesceFunction.java
index cd3c46040c..b4af37b32d 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCoalesceFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCoalesceFunction.java
@@ -87,7 +87,7 @@ public SqlCoalesceFunction() {
for (SqlNode operand : Util.skipLast(operands)) {
whenList.add(
SqlStdOperatorTable.IS_NOT_NULL.createCall(pos, operand));
- thenList.add(SqlNode.clone(operand));
+ thenList.add(SqlInternalOperators.CAST_NOT_NULL.createCall(pos,
SqlNode.clone(operand)));
}
SqlNode elseExpr = Util.last(operands);
assert call.getFunctionQuantifier() == null;
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
index 139f91068b..dd1098fe4f 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlInternalOperators.java
@@ -224,6 +224,40 @@ private SqlInternalOperators() {
public static final SqlAggFunction LITERAL_AGG =
SqlLiteralAggFunction.INSTANCE;
+ /** CAST NOT NULL operator used for cast expression to make it non-nullable
in SqlNode.
+ *
+ * <p>For example:
+ * <pre>{@code COALESCE(a,b)
+ * }</pre>
+ *
+ * <p>is converted to
+ *
+ * <pre>{@code CASE WHEN a is not null THEN a ELSE b
+ * }</pre>
+ * by {@code SqlCoalesceFunction#rewriteCall}.
+ *
+ * <p>When a is nullable and b is non-nullable, the {@code COALESCE(a,b)}
data type will be
+ * non-nullable and {@code CASE WHEN a is not null THEN a ELSE b} data type
will be nullable.
+ * The validator will throw an exception about failing to preserve the data
type.
+ *
+ * <p>Add CAST NOT NULL operator to {@code CASE WHEN a is not null THEN a
ELSE b},
+ * making it becomes
+ * <pre>{@code CASE WHEN a is not null THEN CAST NOT NULL(a) ELSE b}
+ * </pre>
+ *
+ * <p>Then the validator knows this data type is non-nullable.
+ * So we can keep the types consistent before and after conversion.
+ * */
+ public static final SqlOperator CAST_NOT_NULL =
+ new SqlInternalOperator("CAST NOT NULL", SqlKind.CAST_NOT_NULL, 2, true,
+ ReturnTypes.ARG0.andThen(SqlTypeTransforms.TO_NOT_NULLABLE), null,
+ OperandTypes.ANY) {
+ // This is an internal operator, which should not be unparsed to sql.
+ @Override public void unparse(SqlWriter writer, SqlCall call, int
leftPrec, int rightPrec) {
+ call.operand(0).unparse(writer, leftPrec, rightPrec);
+ }
+ };
+
/** Subject to change. */
private static class SqlBasicOperator extends SqlOperator {
@Override public SqlSyntax getSyntax() {
diff --git
a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index 78f8865915..fc8bde9a84 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -295,6 +295,14 @@ private StandardConvertletTable() {
registerOp(SqlInternalOperators.MEASURE,
(cx, call) -> cx.convertExpression(call.operand(0)));
+ // Expand "CAST NOT NULL(x)" into "CAST(x AS INTEGER NOT NULL)"
+ registerOp(SqlInternalOperators.CAST_NOT_NULL,
+ (cx, call) ->
+ cx.getRexBuilder().makeCast(
+ cx.getTypeFactory().createTypeWithNullability(
+ cx.getValidator().getValidatedNodeType(call), false),
+ cx.convertExpression(call.operand(0))));
+
registerOp(SqlStdOperatorTable.CONVERT, this::convertCharset);
registerOp(SqlLibraryOperators.CONVERT_ORACLE, this::convertCharset);
registerOp(SqlStdOperatorTable.TRANSLATE, this::translateCharset);
diff --git
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 50e5eec2ab..cc6a80488e 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -5227,6 +5227,18 @@ void checkUserDefinedOrderByOver(NullCollation
nullCollation) {
sql(sql).ok();
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7044">[CALCITE-7044]
+ * Add internal operator CAST NOT NULL to enhance rewrite COALESCE
operator</a>. */
+ @Test void testCoalesceSubquery() {
+ final String sql = "SELECT"
+ + " deptno, "
+ + " coalesce((select sum(empno) from emp "
+ + " where deptno = emp.deptno limit 1), 0) as w "
+ + "FROM dept";
+ sql(sql).ok();
+ }
+
/**
* Test case for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-4145">[CALCITE-4145]
diff --git
a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index e63683dd23..3de3b1a495 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -7225,7 +7225,7 @@ empnullables t2 on coalesce(t1.ename, t2.ename) = 'abc']]>
<Resource name="planBefore">
<![CDATA[
LogicalProject(DEPTNO=[$7])
- LogicalJoin(condition=[=(CASE(IS NOT NULL($1), $1, $10), 'abc')],
joinType=[inner])
+ LogicalJoin(condition=[=(CASE(IS NOT NULL($1), CAST($1):VARCHAR(20) NOT
NULL, $10), 'abc')], joinType=[inner])
LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
]]>
diff --git
a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
index 2dcbe1f413..0c7463f7b9 100644
--- a/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/SqlToRelConverterTest.xml
@@ -651,6 +651,29 @@ LogicalProject(EXPR$0=[CHAR_LENGTH('foo')])
<![CDATA[
LogicalProject(EXPR$0=[CASE(IS NOT NULL($3), CAST($3):INTEGER NOT NULL, 0)])
LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testCoalesceSubquery">
+ <Resource name="sql">
+ <![CDATA[SELECT deptno, coalesce((select sum(empno) from emp where
deptno = emp.deptno limit 1), 0) as w FROM dept]]>
+ </Resource>
+ <Resource name="plan">
+ <![CDATA[
+LogicalProject(DEPTNO=[$0], W=[CASE(IS NOT NULL($2), CAST($3):INTEGER NOT
NULL, 0)])
+ LogicalJoin(condition=[true], joinType=[left])
+ LogicalJoin(condition=[true], joinType=[left])
+ LogicalTableScan(table=[[CATALOG, SALES, DEPT]])
+ LogicalSort(fetch=[1])
+ LogicalAggregate(group=[{}], EXPR$0=[SUM($0)])
+ LogicalProject(EMPNO=[$0])
+ LogicalFilter(condition=[=($7, $7)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+ LogicalSort(fetch=[1])
+ LogicalAggregate(group=[{}], EXPR$0=[SUM($0)])
+ LogicalProject(EMPNO=[$0])
+ LogicalFilter(condition=[=($7, $7)])
+ LogicalTableScan(table=[[CATALOG, SALES, EMP]])
]]>
</Resource>
</TestCase>
@@ -2098,7 +2121,7 @@ LogicalProject(EMPNO=[$0])
LogicalFilter(condition=[IS NOT NULL($9)])
LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4],
SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f0=[$11])
LogicalJoin(condition=[=($9, $10)], joinType=[left])
- LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f9=[CASE(IS NOT
NULL($1), $1, 'M':VARCHAR(20))])
+ LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3],
HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8], $f9=[CASE(IS NOT
NULL($1), CAST($1):VARCHAR(20) NOT NULL, 'M':VARCHAR(20))])
LogicalTableScan(table=[[CATALOG, SALES, EMPNULLABLES]])
LogicalAggregate(group=[{0}], agg#0=[MIN($1)])
LogicalProject($f9=[CASE(IS NOT NULL($1), CAST($1):VARCHAR(20) NOT
NULL, 'M':VARCHAR(20))], $f0=[true])
diff --git a/core/src/test/resources/sql/sub-query.iq
b/core/src/test/resources/sql/sub-query.iq
index 8a3787d07f..0646f2c3b5 100644
--- a/core/src/test/resources/sql/sub-query.iq
+++ b/core/src/test/resources/sql/sub-query.iq
@@ -1134,7 +1134,7 @@ EnumerableCalc(expr#0..3=[{inputs}], ENAME=[$t1],
DEEP2SAL=[$t3])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableSort(sort0=[$0], dir0=[ASC])
EnumerableAggregate(group=[{0}], EXPR$0=[SUM($1)])
- EnumerableCalc(expr#0..4=[{inputs}], expr#5=[IS NOT NULL($t4)],
expr#6=[0.00:DECIMAL(19, 2)], expr#7=[CASE($t5, $t4, $t6)], expr#8=[+($t2,
$t7)], MGR9=[$t1], $f0=[$t8])
+ EnumerableCalc(expr#0..4=[{inputs}], expr#5=[IS NOT NULL($t4)],
expr#6=[CAST($t4):DECIMAL(19, 2) NOT NULL], expr#7=[0.00:DECIMAL(19, 2)],
expr#8=[CASE($t5, $t6, $t7)], expr#9=[+($t2, $t8)], MGR9=[$t1], $f0=[$t9])
EnumerableMergeJoin(condition=[=($0, $3)], joinType=[left])
EnumerableCalc(expr#0..7=[{inputs}], expr#8=[IS NOT NULL($t3)],
EMPNO=[$t0], MGR=[$t3], SAL=[$t5], $condition=[$t8])
EnumerableTableScan(table=[[scott, EMP]])
@@ -2714,7 +2714,7 @@ where exists
# following plan (two scans of EMP table instead of three).
EnumerableCalc(expr#0..2=[{inputs}], ENAME=[$t1])
EnumerableHashJoin(condition=[=($2, $3)], joinType=[semi])
- EnumerableCalc(expr#0..7=[{inputs}], expr#8=[IS NOT NULL($t3)],
expr#9=[CAST($t3):INTEGER], expr#10=[0], expr#11=[CASE($t8, $t9, $t10)],
proj#0..1=[{exprs}], $f3=[$t11])
+ EnumerableCalc(expr#0..7=[{inputs}], expr#8=[IS NOT NULL($t3)],
expr#9=[CAST($t3):INTEGER NOT NULL], expr#10=[0], expr#11=[CASE($t8, $t9,
$t10)], proj#0..1=[{exprs}], $f3=[$t11])
EnumerableTableScan(table=[[scott, EMP]])
EnumerableCalc(expr#0..7=[{inputs}], expr#8=[IS NOT NULL($t3)],
expr#9=[CAST($t3):INTEGER NOT NULL], expr#10=[0], expr#11=[CASE($t8, $t9,
$t10)], $f8=[$t11])
EnumerableTableScan(table=[[scott, EMP]])