fix bug with aggregator expressions on realtime index with string columns always producing 0 values (#11185)
* fix bug with aggregator expressions on realtime index with string columns always producing 0 values
* more test
* rework some stuff
* javadocs
diff --git a/core/src/main/java/org/apache/druid/math/expr/ExprEval.java b/core/src/main/java/org/apache/druid/math/expr/ExprEval.java
index c72be2e..38bb045 100644
--- a/core/src/main/java/org/apache/druid/math/expr/ExprEval.java
+++ b/core/src/main/java/org/apache/druid/math/expr/ExprEval.java
@@ -627,7 +627,18 @@
}
/**
- * returns true if numeric primitive value for this ExprEval is null, otherwise false.
+ * The method returns true if numeric primitive value for this {@link ExprEval} is null, otherwise false.
+ *
+ * If this method returns false, then the values returned by {@link #asLong()}, {@link #asDouble()},
+ * and {@link #asInt()} are "valid", since this method is primarily used during {@link Expr} evaluation to decide
+ * if primitive numbers can be fetched to use.
+ *
+ * If a type cannot sanely convert into a primitive numeric value, then this method should always return true so that
+ * these primitive numeric getters are not called, since returning false is assumed to mean these values are valid.
+ *
+ * Note that all types must still return values for {@link #asInt()}, {@link #asLong()}}, and {@link #asDouble()},
+ * since this can still happen if {@link NullHandling#sqlCompatible()} is false, but it should be assumed that this
+ * can only happen in that mode and 0s are typical and expected for values that would otherwise be null.
*/
public abstract boolean isNumericNull();
@@ -636,10 +647,25 @@
return false;
}
+ /**
+ * Get the primtive integer value. Callers should check {@link #isNumericNull()} prior to calling this method,
+ * otherwise it may improperly return placeholder a value (typically zero, which is expected if
+ * {@link NullHandling#sqlCompatible()} is false)
+ */
public abstract int asInt();
+ /**
+ * Get the primtive long value. Callers should check {@link #isNumericNull()} prior to calling this method,
+ * otherwise it may improperly return a placeholder value (typically zero, which is expected if
+ * {@link NullHandling#sqlCompatible()} is false)
+ */
public abstract long asLong();
+ /**
+ * Get the primtive double value. Callers should check {@link #isNumericNull()} prior to calling this method,
+ * otherwise it may improperly return a placeholder value (typically zero, which is expected if
+ * {@link NullHandling#sqlCompatible()} is false)
+ */
public abstract double asDouble();
public abstract boolean asBoolean();
@@ -1042,6 +1068,12 @@
if (!isStringValueCached()) {
if (value == null) {
cacheStringValue(null);
+ } else if (value.length == 1) {
+ if (value[0] == null) {
+ cacheStringValue(null);
+ } else {
+ cacheStringValue(String.valueOf(value[0]));
+ }
} else {
cacheStringValue(Arrays.toString(value));
}
@@ -1053,7 +1085,11 @@
@Override
public boolean isNumericNull()
{
- return false;
+ if (isScalar()) {
+ return getScalarValue() == null;
+ }
+
+ return true;
}
@Override
@@ -1098,6 +1134,18 @@
{
return value == null ? null : value[index];
}
+
+ protected boolean isScalar()
+ {
+ return value != null && value.length == 1;
+ }
+
+ @Nullable
+ protected T getScalarValue()
+ {
+ assert value != null && value.length == 1;
+ return value[0];
+ }
}
private static class LongArrayExprEval extends ArrayExprEval<Long>
@@ -1115,6 +1163,62 @@
return ExprType.LONG_ARRAY;
}
+ @Override
+ public int asInt()
+ {
+ if (isScalar()) {
+ Number scalar = getScalarValue();
+ if (scalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0;
+ }
+ return scalar.intValue();
+ }
+ return super.asInt();
+ }
+
+ @Override
+ public long asLong()
+ {
+ if (isScalar()) {
+ Number scalar = getScalarValue();
+ if (scalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0;
+ }
+ return scalar.longValue();
+ }
+ return super.asLong();
+ }
+
+ @Override
+ public double asDouble()
+ {
+ if (isScalar()) {
+ Number scalar = getScalarValue();
+ if (scalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0;
+ }
+ return scalar.doubleValue();
+ }
+ return super.asDouble();
+ }
+
+ @Override
+ public boolean asBoolean()
+ {
+ if (isScalar()) {
+ Number scalarValue = getScalarValue();
+ if (scalarValue == null) {
+ assert NullHandling.replaceWithDefault();
+ return false;
+ }
+ return Evals.asBoolean(scalarValue.longValue());
+ }
+ return super.asBoolean();
+ }
+
@Nullable
@Override
public String[] asStringArray()
@@ -1143,6 +1247,21 @@
return StringExprEval.OF_NULL;
}
switch (castTo) {
+ case STRING:
+ if (value.length == 1) {
+ return ExprEval.of(asString());
+ }
+ break;
+ case LONG:
+ if (value.length == 1) {
+ return isNumericNull() ? ExprEval.ofLong(null) : ExprEval.ofLong(asLong());
+ }
+ break;
+ case DOUBLE:
+ if (value.length == 1) {
+ return isNumericNull() ? ExprEval.ofDouble(null) : ExprEval.ofDouble(asDouble());
+ }
+ break;
case LONG_ARRAY:
return this;
case DOUBLE_ARRAY:
@@ -1176,6 +1295,62 @@
return ExprType.DOUBLE_ARRAY;
}
+ @Override
+ public int asInt()
+ {
+ if (isScalar()) {
+ Number scalar = getScalarValue();
+ if (scalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0;
+ }
+ return scalar.intValue();
+ }
+ return super.asInt();
+ }
+
+ @Override
+ public long asLong()
+ {
+ if (isScalar()) {
+ Number scalar = getScalarValue();
+ if (scalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0;
+ }
+ return scalar.longValue();
+ }
+ return super.asLong();
+ }
+
+ @Override
+ public double asDouble()
+ {
+ if (isScalar()) {
+ Number scalar = getScalarValue();
+ if (scalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0;
+ }
+ return scalar.doubleValue();
+ }
+ return super.asDouble();
+ }
+
+ @Override
+ public boolean asBoolean()
+ {
+ if (isScalar()) {
+ Number scalarValue = getScalarValue();
+ if (scalarValue == null) {
+ assert NullHandling.replaceWithDefault();
+ return false;
+ }
+ return Evals.asBoolean(scalarValue.longValue());
+ }
+ return super.asBoolean();
+ }
+
@Nullable
@Override
public String[] asStringArray()
@@ -1206,6 +1381,21 @@
return StringExprEval.OF_NULL;
}
switch (castTo) {
+ case STRING:
+ if (value.length == 1) {
+ return ExprEval.of(asString());
+ }
+ break;
+ case LONG:
+ if (value.length == 1) {
+ return isNumericNull() ? ExprEval.ofLong(null) : ExprEval.ofLong(asLong());
+ }
+ break;
+ case DOUBLE:
+ if (value.length == 1) {
+ return isNumericNull() ? ExprEval.ofDouble(null) : ExprEval.ofDouble(asDouble());
+ }
+ break;
case LONG_ARRAY:
return ExprEval.ofLongArray(asLongArray());
case DOUBLE_ARRAY:
@@ -1230,8 +1420,13 @@
private boolean longValueValid = false;
private boolean doubleValueValid = false;
+ @Nullable
private Long[] longValues;
+ @Nullable
private Double[] doubleValues;
+ @Nullable
+ private Number computedNumericScalar;
+ private boolean isScalarNumberValid;
private StringArrayExprEval(@Nullable String[] value)
{
@@ -1244,6 +1439,67 @@
return ExprType.STRING_ARRAY;
}
+ @Override
+ public boolean isNumericNull()
+ {
+ if (isScalar()) {
+ computeScalarNumericIfNeeded();
+ return computedNumericScalar == null;
+ }
+ return true;
+ }
+
+ @Override
+ public int asInt()
+ {
+ if (isScalar()) {
+ computeScalarNumericIfNeeded();
+ if (computedNumericScalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0;
+ }
+ return computedNumericScalar.intValue();
+ }
+ return super.asInt();
+ }
+
+ @Override
+ public long asLong()
+ {
+ if (isScalar()) {
+ computeScalarNumericIfNeeded();
+ if (computedNumericScalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0L;
+ }
+ return computedNumericScalar.longValue();
+ }
+ return super.asLong();
+ }
+
+ @Override
+ public double asDouble()
+ {
+ if (isScalar()) {
+ computeScalarNumericIfNeeded();
+ if (computedNumericScalar == null) {
+ assert NullHandling.replaceWithDefault();
+ return 0.0;
+ }
+ return computedNumericScalar.doubleValue();
+ }
+ return super.asDouble();
+ }
+
+ @Override
+ public boolean asBoolean()
+ {
+ if (isScalar()) {
+ return Evals.asBoolean(getScalarValue());
+ }
+ return super.asBoolean();
+ }
+
@Nullable
@Override
public String[] asStringArray()
@@ -1280,6 +1536,21 @@
return StringExprEval.OF_NULL;
}
switch (castTo) {
+ case STRING:
+ if (value.length == 1) {
+ return ExprEval.of(asString());
+ }
+ break;
+ case LONG:
+ if (value.length == 1) {
+ return isNumericNull() ? ExprEval.ofLong(null) : ExprEval.ofLong(asLong());
+ }
+ break;
+ case DOUBLE:
+ if (value.length == 1) {
+ return isNumericNull() ? ExprEval.ofDouble(null) : ExprEval.ofDouble(asDouble());
+ }
+ break;
case STRING_ARRAY:
return this;
case LONG_ARRAY:
@@ -1330,5 +1601,17 @@
return Doubles.tryParse(val);
}).toArray(Double[]::new);
}
+
+
+ /**
+ * must not be called unless array has a single element
+ */
+ private void computeScalarNumericIfNeeded()
+ {
+ if (!isScalarNumberValid) {
+ computedNumericScalar = computeNumber(getScalarValue());
+ isScalarNumberValid = true;
+ }
+ }
}
}
diff --git a/core/src/main/java/org/apache/druid/math/expr/Function.java b/core/src/main/java/org/apache/druid/math/expr/Function.java
index 7b9fe527..711acad 100644
--- a/core/src/main/java/org/apache/druid/math/expr/Function.java
+++ b/core/src/main/java/org/apache/druid/math/expr/Function.java
@@ -1437,9 +1437,11 @@
public ExprEval apply(List<Expr> args, Expr.ObjectBinding bindings)
{
ExprEval value1 = args.get(0).eval(bindings);
+
if (NullHandling.sqlCompatible() && value1.isNumericNull()) {
return ExprEval.of(null);
}
+
if (value1.type() != ExprType.LONG && value1.type() != ExprType.DOUBLE) {
throw new IAE(
"The first argument to the function[%s] should be integer or double type but got the type: %s",
diff --git a/core/src/test/java/org/apache/druid/math/expr/EvalTest.java b/core/src/test/java/org/apache/druid/math/expr/EvalTest.java
index a80edba..82624b9 100644
--- a/core/src/test/java/org/apache/druid/math/expr/EvalTest.java
+++ b/core/src/test/java/org/apache/druid/math/expr/EvalTest.java
@@ -21,14 +21,20 @@
import com.google.common.collect.ImmutableMap;
import org.apache.druid.common.config.NullHandling;
+import org.apache.druid.java.util.common.IAE;
import org.apache.druid.testing.InitializedNullHandlingTest;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
/**
*/
public class EvalTest extends InitializedNullHandlingTest
{
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
private long evalLong(String x, Expr.ObjectBinding bindings)
{
ExprEval ret = eval(x, bindings);
@@ -145,6 +151,198 @@
}
@Test
+ public void testArrayToScalar()
+ {
+ Assert.assertEquals(1L, ExprEval.ofLongArray(new Long[]{1L}).asLong());
+ Assert.assertEquals(1.0, ExprEval.ofLongArray(new Long[]{1L}).asDouble(), 0.0);
+ Assert.assertEquals(1, ExprEval.ofLongArray(new Long[]{1L}).asInt());
+ Assert.assertEquals(true, ExprEval.ofLongArray(new Long[]{1L}).asBoolean());
+ Assert.assertEquals("1", ExprEval.ofLongArray(new Long[]{1L}).asString());
+
+
+ Assert.assertEquals(null, ExprEval.ofLongArray(new Long[]{null}).asString());
+
+ Assert.assertEquals(0L, ExprEval.ofLongArray(new Long[]{1L, 2L}).asLong());
+ Assert.assertEquals(0.0, ExprEval.ofLongArray(new Long[]{1L, 2L}).asDouble(), 0.0);
+ Assert.assertEquals("[1, 2]", ExprEval.ofLongArray(new Long[]{1L, 2L}).asString());
+ Assert.assertEquals(0, ExprEval.ofLongArray(new Long[]{1L, 2L}).asInt());
+ Assert.assertEquals(false, ExprEval.ofLongArray(new Long[]{1L, 2L}).asBoolean());
+
+ Assert.assertEquals(1.1, ExprEval.ofDoubleArray(new Double[]{1.1}).asDouble(), 0.0);
+ Assert.assertEquals(1L, ExprEval.ofDoubleArray(new Double[]{1.1}).asLong());
+ Assert.assertEquals("1.1", ExprEval.ofDoubleArray(new Double[]{1.1}).asString());
+ Assert.assertEquals(1, ExprEval.ofDoubleArray(new Double[]{1.1}).asInt());
+ Assert.assertEquals(true, ExprEval.ofDoubleArray(new Double[]{1.1}).asBoolean());
+
+ Assert.assertEquals(null, ExprEval.ofDoubleArray(new Double[]{null}).asString());
+
+ Assert.assertEquals(0.0, ExprEval.ofDoubleArray(new Double[]{1.1, 2.2}).asDouble(), 0.0);
+ Assert.assertEquals(0L, ExprEval.ofDoubleArray(new Double[]{1.1, 2.2}).asLong());
+ Assert.assertEquals("[1.1, 2.2]", ExprEval.ofDoubleArray(new Double[]{1.1, 2.2}).asString());
+ Assert.assertEquals(0, ExprEval.ofDoubleArray(new Double[]{1.1, 2.2}).asInt());
+ Assert.assertEquals(false, ExprEval.ofDoubleArray(new Double[]{1.1, 2.2}).asBoolean());
+
+ Assert.assertEquals("foo", ExprEval.ofStringArray(new String[]{"foo"}).asString());
+
+ Assert.assertEquals("1", ExprEval.ofStringArray(new String[]{"1"}).asString());
+ Assert.assertEquals(1L, ExprEval.ofStringArray(new String[]{"1"}).asLong());
+ Assert.assertEquals(1.0, ExprEval.ofStringArray(new String[]{"1"}).asDouble(), 0.0);
+ Assert.assertEquals(1, ExprEval.ofStringArray(new String[]{"1"}).asInt());
+ Assert.assertEquals(false, ExprEval.ofStringArray(new String[]{"1"}).asBoolean());
+ Assert.assertEquals(true, ExprEval.ofStringArray(new String[]{"true"}).asBoolean());
+
+ Assert.assertEquals("[1, 2.2]", ExprEval.ofStringArray(new String[]{"1", "2.2"}).asString());
+ Assert.assertEquals(0L, ExprEval.ofStringArray(new String[]{"1", "2.2"}).asLong());
+ Assert.assertEquals(0.0, ExprEval.ofStringArray(new String[]{"1", "2.2"}).asDouble(), 0.0);
+ Assert.assertEquals(0, ExprEval.ofStringArray(new String[]{"1", "2.2"}).asInt());
+ Assert.assertEquals(false, ExprEval.ofStringArray(new String[]{"1", "2.2"}).asBoolean());
+
+ // test casting arrays to scalars
+ Assert.assertEquals(1L, ExprEval.ofLongArray(new Long[]{1L}).castTo(ExprType.LONG).value());
+ Assert.assertEquals(NullHandling.defaultLongValue(), ExprEval.ofLongArray(new Long[]{null}).castTo(ExprType.LONG).value());
+ Assert.assertEquals(1.0, ExprEval.ofLongArray(new Long[]{1L}).castTo(ExprType.DOUBLE).asDouble(), 0.0);
+ Assert.assertEquals("1", ExprEval.ofLongArray(new Long[]{1L}).castTo(ExprType.STRING).value());
+
+ Assert.assertEquals(1.1, ExprEval.ofDoubleArray(new Double[]{1.1}).castTo(ExprType.DOUBLE).asDouble(), 0.0);
+ Assert.assertEquals(NullHandling.defaultDoubleValue(), ExprEval.ofDoubleArray(new Double[]{null}).castTo(ExprType.DOUBLE).value());
+ Assert.assertEquals(1L, ExprEval.ofDoubleArray(new Double[]{1.1}).castTo(ExprType.LONG).value());
+ Assert.assertEquals("1.1", ExprEval.ofDoubleArray(new Double[]{1.1}).castTo(ExprType.STRING).value());
+
+ Assert.assertEquals("foo", ExprEval.ofStringArray(new String[]{"foo"}).castTo(ExprType.STRING).value());
+ Assert.assertEquals(NullHandling.defaultLongValue(), ExprEval.ofStringArray(new String[]{"foo"}).castTo(ExprType.LONG).value());
+ Assert.assertEquals(NullHandling.defaultDoubleValue(), ExprEval.ofStringArray(new String[]{"foo"}).castTo(ExprType.DOUBLE).value());
+ Assert.assertEquals("1", ExprEval.ofStringArray(new String[]{"1"}).castTo(ExprType.STRING).value());
+ Assert.assertEquals(1L, ExprEval.ofStringArray(new String[]{"1"}).castTo(ExprType.LONG).value());
+ Assert.assertEquals(1.0, ExprEval.ofStringArray(new String[]{"1"}).castTo(ExprType.DOUBLE).value());
+ }
+
+ @Test
+ public void testStringArrayToScalarStringBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type STRING");
+ ExprEval.ofStringArray(new String[]{"foo", "bar"}).castTo(ExprType.STRING);
+ }
+
+ @Test
+ public void testStringArrayToScalarLongBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type LONG");
+ ExprEval.ofStringArray(new String[]{"foo", "bar"}).castTo(ExprType.LONG);
+ }
+
+ @Test
+ public void testStringArrayToScalarDoubleBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type DOUBLE");
+ ExprEval.ofStringArray(new String[]{"foo", "bar"}).castTo(ExprType.DOUBLE);
+ }
+
+ @Test
+ public void testLongArrayToScalarStringBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type STRING");
+ ExprEval.ofLongArray(new Long[]{1L, 2L}).castTo(ExprType.STRING);
+ }
+
+ @Test
+ public void testLongArrayToScalarLongBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type LONG");
+ ExprEval.ofLongArray(new Long[]{1L, 2L}).castTo(ExprType.LONG);
+ }
+
+ @Test
+ public void testLongArrayToScalarDoubleBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type DOUBLE");
+ ExprEval.ofLongArray(new Long[]{1L, 2L}).castTo(ExprType.DOUBLE);
+ }
+
+ @Test
+ public void testDoubleArrayToScalarStringBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type STRING");
+ ExprEval.ofDoubleArray(new Double[]{1.1, 2.2}).castTo(ExprType.STRING);
+ }
+
+ @Test
+ public void testDoubleArrayToScalarLongBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type LONG");
+ ExprEval.ofDoubleArray(new Double[]{1.1, 2.2}).castTo(ExprType.LONG);
+ }
+
+ @Test
+ public void testDoubleArrayToScalarDoubleBadCast()
+ {
+ expectedException.expect(IAE.class);
+ expectedException.expectMessage("invalid type DOUBLE");
+ ExprEval.ofDoubleArray(new Double[]{1.1, 2.2}).castTo(ExprType.DOUBLE);
+ }
+
+ @Test
+ public void testIsNumericNull()
+ {
+ if (NullHandling.sqlCompatible()) {
+ Assert.assertFalse(ExprEval.ofLong(1L).isNumericNull());
+ Assert.assertTrue(ExprEval.ofLong(null).isNumericNull());
+
+ Assert.assertFalse(ExprEval.ofDouble(1.0).isNumericNull());
+ Assert.assertTrue(ExprEval.ofDouble(null).isNumericNull());
+
+ Assert.assertTrue(ExprEval.of(null).isNumericNull());
+ Assert.assertTrue(ExprEval.of("one").isNumericNull());
+ Assert.assertFalse(ExprEval.of("1").isNumericNull());
+
+ Assert.assertFalse(ExprEval.ofLongArray(new Long[]{1L}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofLongArray(new Long[]{null, 2L, 3L}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofLongArray(new Long[]{null}).isNumericNull());
+
+ Assert.assertFalse(ExprEval.ofDoubleArray(new Double[]{1.1}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofDoubleArray(new Double[]{null, 1.1, 2.2}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofDoubleArray(new Double[]{null}).isNumericNull());
+
+ Assert.assertFalse(ExprEval.ofStringArray(new String[]{"1"}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofStringArray(new String[]{null, "1", "2"}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofStringArray(new String[]{"one"}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofStringArray(new String[]{null}).isNumericNull());
+ } else {
+ Assert.assertFalse(ExprEval.ofLong(1L).isNumericNull());
+ Assert.assertFalse(ExprEval.ofLong(null).isNumericNull());
+
+ Assert.assertFalse(ExprEval.ofDouble(1.0).isNumericNull());
+ Assert.assertFalse(ExprEval.ofDouble(null).isNumericNull());
+
+ // strings are still null
+ Assert.assertTrue(ExprEval.of(null).isNumericNull());
+ Assert.assertTrue(ExprEval.of("one").isNumericNull());
+ Assert.assertFalse(ExprEval.of("1").isNumericNull());
+
+ // arrays can still have nulls
+ Assert.assertFalse(ExprEval.ofLongArray(new Long[]{1L}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofLongArray(new Long[]{null, 2L, 3L}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofLongArray(new Long[]{null}).isNumericNull());
+
+ Assert.assertFalse(ExprEval.ofDoubleArray(new Double[]{1.1}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofDoubleArray(new Double[]{null, 1.1, 2.2}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofDoubleArray(new Double[]{null}).isNumericNull());
+
+ Assert.assertFalse(ExprEval.ofStringArray(new String[]{"1"}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofStringArray(new String[]{null, "1", "2"}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofStringArray(new String[]{"one"}).isNumericNull());
+ Assert.assertTrue(ExprEval.ofStringArray(new String[]{null}).isNumericNull());
+ }
+ }
+
+ @Test
public void testBooleanReturn()
{
Expr.ObjectBinding bindings = InputBindings.withMap(
diff --git a/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java b/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
index bc283d3..1557749 100644
--- a/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
+++ b/core/src/test/java/org/apache/druid/math/expr/FunctionTest.java
@@ -430,11 +430,14 @@
}
@Test
- public void testRoundWithNullValue()
+ public void testRoundWithNullValueOrInvalid()
{
Set<Pair<String, String>> invalidArguments = ImmutableSet.of(
Pair.of("null", "STRING"),
- Pair.of("x", "STRING")
+ Pair.of("x", "STRING"),
+ Pair.of("b", "LONG_ARRAY"),
+ Pair.of("c", "DOUBLE_ARRAY"),
+ Pair.of("a", "STRING_ARRAY")
);
for (Pair<String, String> argAndType : invalidArguments) {
if (NullHandling.sqlCompatible()) {
@@ -446,8 +449,7 @@
}
catch (IllegalArgumentException e) {
Assert.assertEquals(
- String.format(
- Locale.ENGLISH,
+ StringUtils.format(
"The first argument to the function[round] should be integer or double type but got the type: %s",
argAndType.rhs
),
@@ -459,33 +461,6 @@
}
@Test
- public void testRoundWithInvalidFirstArgument()
- {
- Set<Pair<String, String>> invalidArguments = ImmutableSet.of(
- Pair.of("b", "LONG_ARRAY"),
- Pair.of("c", "DOUBLE_ARRAY"),
- Pair.of("a", "STRING_ARRAY")
-
- );
- for (Pair<String, String> argAndType : invalidArguments) {
- try {
- assertExpr(String.format(Locale.ENGLISH, "round(%s)", argAndType.lhs), null);
- Assert.fail("Did not throw IllegalArgumentException");
- }
- catch (IllegalArgumentException e) {
- Assert.assertEquals(
- String.format(
- Locale.ENGLISH,
- "The first argument to the function[round] should be integer or double type but got the type: %s",
- argAndType.rhs
- ),
- e.getMessage()
- );
- }
- }
- }
-
- @Test
public void testRoundWithInvalidSecondArgument()
{
Set<Pair<String, String>> invalidArguments = ImmutableSet.of(
@@ -502,8 +477,7 @@
}
catch (IllegalArgumentException e) {
Assert.assertEquals(
- String.format(
- Locale.ENGLISH,
+ StringUtils.format(
"The second argument to the function[round] should be integer type but got the type: %s",
argAndType.rhs
),
diff --git a/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryRunnerTest.java b/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryRunnerTest.java
index a861803..b20fe00 100644
--- a/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryRunnerTest.java
+++ b/processing/src/test/java/org/apache/druid/query/groupby/GroupByQueryRunnerTest.java
@@ -49,7 +49,6 @@
import org.apache.druid.java.util.common.guava.Sequences;
import org.apache.druid.java.util.common.io.Closer;
import org.apache.druid.js.JavaScriptConfig;
-import org.apache.druid.math.expr.ExprMacroTable;
import org.apache.druid.query.BySegmentResultValue;
import org.apache.druid.query.BySegmentResultValueClass;
import org.apache.druid.query.ChainedExecutionQueryRunner;
@@ -11183,29 +11182,36 @@
"v",
"qualityDouble * qualityLong",
ValueType.LONG,
- ExprMacroTable.nil()
+ TestExprMacroTable.INSTANCE
+ ),
+ new ExpressionVirtualColumn(
+ "two",
+ "2",
+ null,
+ TestExprMacroTable.INSTANCE
)
)
.setDimensions(
new DefaultDimensionSpec("v", "v", ValueType.LONG)
)
- .setAggregatorSpecs(QueryRunnerTestHelper.ROWS_COUNT)
+ .setAggregatorSpecs(QueryRunnerTestHelper.ROWS_COUNT, new LongSumAggregatorFactory("twosum", null, "1 + two", TestExprMacroTable.INSTANCE))
.setGranularity(QueryRunnerTestHelper.ALL_GRAN)
.setLimit(5)
.build();
List<ResultRow> expectedResults = Arrays.asList(
- makeRow(query, "2011-04-01", "v", 10000000L, "rows", 2L),
- makeRow(query, "2011-04-01", "v", 12100000L, "rows", 2L),
- makeRow(query, "2011-04-01", "v", 14400000L, "rows", 2L),
- makeRow(query, "2011-04-01", "v", 16900000L, "rows", 2L),
- makeRow(query, "2011-04-01", "v", 19600000L, "rows", 6L)
+ makeRow(query, "2011-04-01", "v", 10000000L, "rows", 2L, "twosum", 6L),
+ makeRow(query, "2011-04-01", "v", 12100000L, "rows", 2L, "twosum", 6L),
+ makeRow(query, "2011-04-01", "v", 14400000L, "rows", 2L, "twosum", 6L),
+ makeRow(query, "2011-04-01", "v", 16900000L, "rows", 2L, "twosum", 6L),
+ makeRow(query, "2011-04-01", "v", 19600000L, "rows", 6L, "twosum", 18L)
);
Iterable<ResultRow> results = GroupByQueryRunnerTestHelper.runQuery(factory, runner, query);
TestHelper.assertExpectedObjects(expectedResults, results, "groupBy");
}
+
@Test
public void testGroupByWithExpressionAggregator()
{
diff --git a/processing/src/test/java/org/apache/druid/query/timeseries/TimeseriesQueryRunnerTest.java b/processing/src/test/java/org/apache/druid/query/timeseries/TimeseriesQueryRunnerTest.java
index 806d5d8..c1de43c 100644
--- a/processing/src/test/java/org/apache/druid/query/timeseries/TimeseriesQueryRunnerTest.java
+++ b/processing/src/test/java/org/apache/druid/query/timeseries/TimeseriesQueryRunnerTest.java
@@ -46,8 +46,10 @@
import org.apache.druid.query.aggregation.CountAggregatorFactory;
import org.apache.druid.query.aggregation.DoubleMaxAggregatorFactory;
import org.apache.druid.query.aggregation.DoubleMinAggregatorFactory;
+import org.apache.druid.query.aggregation.DoubleSumAggregatorFactory;
import org.apache.druid.query.aggregation.ExpressionLambdaAggregatorFactory;
import org.apache.druid.query.aggregation.FilteredAggregatorFactory;
+import org.apache.druid.query.aggregation.FloatSumAggregatorFactory;
import org.apache.druid.query.aggregation.LongSumAggregatorFactory;
import org.apache.druid.query.aggregation.cardinality.CardinalityAggregatorFactory;
import org.apache.druid.query.aggregation.first.DoubleFirstAggregatorFactory;
@@ -2205,10 +2207,12 @@
Lists.newArrayList(
Iterables.concat(
aggregatorFactoryList,
- Collections.singletonList(new FilteredAggregatorFactory(
- new CountAggregatorFactory("filteredAgg"),
- new SelectorDimFilter(QueryRunnerTestHelper.MARKET_DIMENSION, "spot", null)
- ))
+ ImmutableList.of(
+ new FilteredAggregatorFactory(
+ new CountAggregatorFactory("filteredAgg"),
+ new SelectorDimFilter(QueryRunnerTestHelper.MARKET_DIMENSION, "spot", null)
+ )
+ )
)
)
)
@@ -2222,15 +2226,83 @@
new Result<>(
DateTimes.of("2011-04-01"),
new TimeseriesResultValue(
- ImmutableMap.of(
- "filteredAgg", 18L,
- "addRowsIndexConstant", 12486.361190795898d,
- "index", 12459.361190795898d,
- "uniques", 9.019833517963864d,
- "rows", 26L
+ ImmutableMap.<String, Object>builder()
+ .put("filteredAgg", 18L)
+ .put("addRowsIndexConstant", 12486.361190795898d)
+ .put("index", 12459.361190795898d)
+ .put("uniques", 9.019833517963864d)
+ .put("rows", 26L)
+ .build()
+ )
+ )
+ );
+
+ assertExpectedResults(expectedResults, actualResults);
+ }
+
+ @Test
+ public void testTimeSeriesWithFilteredAggAndExpressionFilteredAgg()
+ {
+ // can't vectorize if expression
+ cannotVectorize();
+ TimeseriesQuery query = Druids
+ .newTimeseriesQueryBuilder()
+ .dataSource(QueryRunnerTestHelper.DATA_SOURCE)
+ .granularity(QueryRunnerTestHelper.ALL_GRAN)
+ .intervals(QueryRunnerTestHelper.FIRST_TO_THIRD)
+ .aggregators(
+ Lists.newArrayList(
+ Iterables.concat(
+ aggregatorFactoryList,
+ ImmutableList.of(
+ new FilteredAggregatorFactory(
+ new CountAggregatorFactory("filteredAgg"),
+ new SelectorDimFilter(QueryRunnerTestHelper.MARKET_DIMENSION, "spot", null)
+ ),
+ new LongSumAggregatorFactory(
+ "altLongCount",
+ null,
+ "if (market == 'spot', 1, 0)",
+ TestExprMacroTable.INSTANCE
+ ),
+ new DoubleSumAggregatorFactory(
+ "altDoubleCount",
+ null,
+ "if (market == 'spot', 1, 0)",
+ TestExprMacroTable.INSTANCE
+ ),
+ new FloatSumAggregatorFactory(
+ "altFloatCount",
+ null,
+ "if (market == 'spot', 1, 0)",
+ TestExprMacroTable.INSTANCE
+ )
+ )
)
)
)
+ .postAggregators(QueryRunnerTestHelper.ADD_ROWS_INDEX_CONSTANT)
+ .descending(descending)
+ .context(makeContext())
+ .build();
+
+ Iterable<Result<TimeseriesResultValue>> actualResults = runner.run(QueryPlus.wrap(query)).toList();
+ List<Result<TimeseriesResultValue>> expectedResults = Collections.singletonList(
+ new Result<>(
+ DateTimes.of("2011-04-01"),
+ new TimeseriesResultValue(
+ ImmutableMap.<String, Object>builder()
+ .put("filteredAgg", 18L)
+ .put("addRowsIndexConstant", 12486.361190795898d)
+ .put("index", 12459.361190795898d)
+ .put("uniques", 9.019833517963864d)
+ .put("rows", 26L)
+ .put("altLongCount", 18L)
+ .put("altDoubleCount", 18.0)
+ .put("altFloatCount", 18.0f)
+ .build()
+ )
+ )
);
assertExpectedResults(expectedResults, actualResults);