diff --git a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ReductionOperatorConversionHelper.java b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ReductionOperatorConversionHelper.java index 4a457b7ad7e..a747e9d27da 100644 --- a/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ReductionOperatorConversionHelper.java +++ b/sql/src/main/java/org/apache/druid/sql/calcite/expression/builtin/ReductionOperatorConversionHelper.java @@ -23,6 +23,7 @@ import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.sql.type.SqlReturnTypeInference; import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.druid.common.config.NullHandling; import org.apache.druid.java.util.common.IAE; import org.apache.druid.math.expr.ExpressionTypeConversion; import org.apache.druid.segment.column.ColumnType; @@ -53,7 +54,7 @@ class ReductionOperatorConversionHelper SqlTypeName returnSqlTypeName = SqlTypeName.NULL; boolean hasDouble = false; - + boolean isString = false; for (int i = 0; i < n; i++) { RelDataType type = opBinding.getOperandType(i); SqlTypeName sqlTypeName = type.getSqlTypeName(); @@ -63,6 +64,7 @@ class ReductionOperatorConversionHelper if (valueType != null) { if (valueType.is(ValueType.STRING)) { returnSqlTypeName = sqlTypeName; + isString = true; break; } else if (valueType.anyOf(ValueType.DOUBLE, ValueType.FLOAT)) { returnSqlTypeName = SqlTypeName.DOUBLE; @@ -75,6 +77,12 @@ class ReductionOperatorConversionHelper } } - return typeFactory.createSqlType(returnSqlTypeName); + if (isString || NullHandling.sqlCompatible()) { + // String can be null in both modes + return typeFactory.createTypeWithNullability(typeFactory.createSqlType(returnSqlTypeName), true); + } else { + return typeFactory.createSqlType(returnSqlTypeName); + } }; } + diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java index 77e6ca22d23..666967c410e 100644 --- a/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java +++ b/sql/src/test/java/org/apache/druid/sql/calcite/CalciteQueryTest.java @@ -39,6 +39,7 @@ import org.apache.druid.query.Druids; import org.apache.druid.query.InlineDataSource; import org.apache.druid.query.JoinDataSource; import org.apache.druid.query.LookupDataSource; +import org.apache.druid.query.Query; import org.apache.druid.query.QueryContext; import org.apache.druid.query.QueryContexts; import org.apache.druid.query.QueryDataSource; @@ -13902,4 +13903,113 @@ public class CalciteQueryTest extends BaseCalciteQueryTest null ); } + + @Test + public void testGreatestFunctionForNumberWithIsNull() throws Exception + { + String query = "SELECT dim1, MAX(GREATEST(l1, l2)) IS NULL FROM druid.numfoo GROUP BY dim1"; + + List expectedResult; + List expectedQueries; + + if (NullHandling.replaceWithDefault()) { + expectedResult = ImmutableList.of( + new Object[]{"", false}, + new Object[]{"1", false}, + new Object[]{"10.1", false}, + new Object[]{"2", false}, + new Object[]{"abc", false}, + new Object[]{"def", false} + ); + expectedQueries = ImmutableList.of( + GroupByQuery.builder() + .setDataSource(CalciteTests.DATASOURCE3) + .setInterval(querySegmentSpec(Intervals.ETERNITY)) + .setGranularity(Granularities.ALL) + .addDimension(new DefaultDimensionSpec("dim1", "_d0")) + .setPostAggregatorSpecs(ImmutableList.of( + expressionPostAgg("p0", "0") + )) + .build() + ); + } else { + cannotVectorize(); + expectedResult = ImmutableList.of( + new Object[]{"", false}, + new Object[]{"1", true}, + new Object[]{"10.1", false}, + new Object[]{"2", false}, + new Object[]{"abc", true}, + new Object[]{"def", true} + ); + expectedQueries = ImmutableList.of( + GroupByQuery.builder() + .setDataSource(CalciteTests.DATASOURCE3) + .setInterval(querySegmentSpec(Intervals.ETERNITY)) + .setVirtualColumns( + expressionVirtualColumn( + "v0", + "greatest(\"l1\",\"l2\")", + ColumnType.LONG + ) + ) + .setGranularity(Granularities.ALL) + .addDimension(new DefaultDimensionSpec("dim1", "_d0")) + .addAggregator(new LongMaxAggregatorFactory("a0", "v0")) + .setPostAggregatorSpecs(ImmutableList.of( + expressionPostAgg("p0", "isnull(\"a0\")") + )) + .build() + ); + } + + testQuery( + query, + expectedQueries, + expectedResult + ); + } + + @Test + public void testGreatestFunctionForStringWithIsNull() throws Exception + { + cannotVectorize(); + + String query = "SELECT l1, LATEST(GREATEST(dim1, dim2)) IS NULL FROM druid.numfoo GROUP BY l1"; + + testQuery( + query, + ImmutableList.of( + GroupByQuery.builder() + .setDataSource(CalciteTests.DATASOURCE3) + .setInterval(querySegmentSpec(Intervals.ETERNITY)) + .setVirtualColumns( + expressionVirtualColumn( + "v0", + "CAST(greatest(\"dim1\",\"dim2\"), 'DOUBLE')", + ColumnType.DOUBLE + ) + ) + .setGranularity(Granularities.ALL) + .addDimension(new DefaultDimensionSpec("l1", "_d0", ColumnType.LONG)) + .addAggregator(new DoubleLastAggregatorFactory("a0", "v0", null)) + .setPostAggregatorSpecs(ImmutableList.of( + expressionPostAgg("p0", "isnull(\"a0\")") + )) + .build() + ), + NullHandling.replaceWithDefault() ? + ImmutableList.of( + new Object[]{0L, false}, + new Object[]{7L, false}, + new Object[]{325323L, false} + ) : + ImmutableList.of( + new Object[]{null, true}, + new Object[]{0L, false}, + new Object[]{7L, true}, + new Object[]{325323L, false} + ) + ); + } }