GREATEST/LEAST function is incorrectly specifying that it cannot return null (#12804)

This commit is contained in:
Maytas Monsereenusorn 2022-07-20 02:11:24 -07:00 committed by GitHub
parent 809bf161ce
commit 3bf1e699ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 120 additions and 2 deletions

View File

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

View File

@ -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<Object[]> expectedResult;
List<Query> 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}
)
);
}
}