mirror of https://github.com/apache/druid.git
Expressions work better with strings. (#4394)
* Expressions work better with strings. - ExpressionObjectSelector able to read from string columns, and able to return strings. - ExpressionVirtualColumn able to offer string (and long for that matter) as its native type. - ExpressionPostAggregator able to return strings. - groupBy, topN: Allow post-aggregators to accept dimensions as inputs, making ExpressionPostAggregator more useful. - topN: Use DimExtractionTopNAlgorithm for STRING columns that do not have dictionaries, allowing it to work with STRING-type expression virtual columns. - Adjusts null handling to better match the rest of Druid: null and empty string treated the same; nulls implicitly treated as zeroes in numeric context. * Code review comments. * More code review. * Fix test. * Adjust annotations.
This commit is contained in:
parent
976492c186
commit
6edee7f434
|
@ -19,14 +19,18 @@
|
|||
|
||||
package io.druid.math.expr;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.math.LongMath;
|
||||
import com.google.common.primitives.Ints;
|
||||
import io.druid.java.util.common.IAE;
|
||||
import io.druid.java.util.common.ISE;
|
||||
import io.druid.java.util.common.guava.Comparators;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -57,7 +61,8 @@ public interface Expr
|
|||
|
||||
interface ObjectBinding
|
||||
{
|
||||
Number get(String name);
|
||||
@Nullable
|
||||
Object get(String name);
|
||||
}
|
||||
|
||||
void visit(Visitor visitor);
|
||||
|
@ -89,10 +94,10 @@ class LongExpr extends ConstantExpr
|
|||
|
||||
public LongExpr(Long value)
|
||||
{
|
||||
this.value = value;
|
||||
this.value = Preconditions.checkNotNull(value, "value");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object getLiteralValue()
|
||||
{
|
||||
|
@ -119,7 +124,7 @@ class StringExpr extends ConstantExpr
|
|||
|
||||
public StringExpr(String value)
|
||||
{
|
||||
this.value = value;
|
||||
this.value = Strings.emptyToNull(value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -149,10 +154,10 @@ class DoubleExpr extends ConstantExpr
|
|||
|
||||
public DoubleExpr(Double value)
|
||||
{
|
||||
this.value = value;
|
||||
this.value = Preconditions.checkNotNull(value, "value");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Nonnull
|
||||
@Override
|
||||
public Object getLiteralValue()
|
||||
{
|
||||
|
@ -357,19 +362,16 @@ abstract class BinaryEvalOpExprBase extends BinaryOpExprBase
|
|||
{
|
||||
ExprEval leftVal = left.eval(bindings);
|
||||
ExprEval rightVal = right.eval(bindings);
|
||||
if (leftVal.isNull() || rightVal.isNull()) {
|
||||
return ExprEval.of(null);
|
||||
}
|
||||
if (leftVal.type() == ExprType.STRING || rightVal.type() == ExprType.STRING) {
|
||||
if (leftVal.type() == ExprType.STRING && rightVal.type() == ExprType.STRING) {
|
||||
return evalString(leftVal.asString(), rightVal.asString());
|
||||
}
|
||||
if (leftVal.type() == ExprType.LONG && rightVal.type() == ExprType.LONG) {
|
||||
} else if (leftVal.type() == ExprType.LONG && rightVal.type() == ExprType.LONG) {
|
||||
return ExprEval.of(evalLong(leftVal.asLong(), rightVal.asLong()));
|
||||
} else {
|
||||
return ExprEval.of(evalDouble(leftVal.asDouble(), rightVal.asDouble()));
|
||||
}
|
||||
return ExprEval.of(evalDouble(leftVal.asDouble(), rightVal.asDouble()));
|
||||
}
|
||||
|
||||
protected ExprEval evalString(String left, String right)
|
||||
protected ExprEval evalString(@Nullable String left, @Nullable String right)
|
||||
{
|
||||
throw new IllegalArgumentException("unsupported type " + ExprType.STRING);
|
||||
}
|
||||
|
@ -487,9 +489,9 @@ class BinPlusExpr extends BinaryEvalOpExprBase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ExprEval evalString(String left, String right)
|
||||
protected ExprEval evalString(@Nullable String left, @Nullable String right)
|
||||
{
|
||||
return ExprEval.of(left + right);
|
||||
return ExprEval.of(Strings.nullToEmpty(left) + Strings.nullToEmpty(right));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -513,9 +515,9 @@ class BinLtExpr extends BinaryEvalOpExprBase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ExprEval evalString(String left, String right)
|
||||
protected ExprEval evalString(@Nullable String left, @Nullable String right)
|
||||
{
|
||||
return ExprEval.of(left.compareTo(right) < 0, ExprType.LONG);
|
||||
return ExprEval.of(Comparators.<String>naturalNullsFirst().compare(left, right) < 0, ExprType.LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -527,7 +529,8 @@ class BinLtExpr extends BinaryEvalOpExprBase
|
|||
@Override
|
||||
protected final double evalDouble(double left, double right)
|
||||
{
|
||||
return Evals.asDouble(left < right);
|
||||
// Use Double.compare for more consistent NaN handling.
|
||||
return Evals.asDouble(Double.compare(left, right) < 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -539,9 +542,9 @@ class BinLeqExpr extends BinaryEvalOpExprBase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ExprEval evalString(String left, String right)
|
||||
protected ExprEval evalString(@Nullable String left, @Nullable String right)
|
||||
{
|
||||
return ExprEval.of(left.compareTo(right) <= 0, ExprType.LONG);
|
||||
return ExprEval.of(Comparators.<String>naturalNullsFirst().compare(left, right) <= 0, ExprType.LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -553,7 +556,8 @@ class BinLeqExpr extends BinaryEvalOpExprBase
|
|||
@Override
|
||||
protected final double evalDouble(double left, double right)
|
||||
{
|
||||
return Evals.asDouble(left <= right);
|
||||
// Use Double.compare for more consistent NaN handling.
|
||||
return Evals.asDouble(Double.compare(left, right) <= 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -565,9 +569,9 @@ class BinGtExpr extends BinaryEvalOpExprBase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ExprEval evalString(String left, String right)
|
||||
protected ExprEval evalString(@Nullable String left, @Nullable String right)
|
||||
{
|
||||
return ExprEval.of(left.compareTo(right) > 0, ExprType.LONG);
|
||||
return ExprEval.of(Comparators.<String>naturalNullsFirst().compare(left, right) > 0, ExprType.LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -579,7 +583,8 @@ class BinGtExpr extends BinaryEvalOpExprBase
|
|||
@Override
|
||||
protected final double evalDouble(double left, double right)
|
||||
{
|
||||
return Evals.asDouble(left > right);
|
||||
// Use Double.compare for more consistent NaN handling.
|
||||
return Evals.asDouble(Double.compare(left, right) > 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -591,9 +596,9 @@ class BinGeqExpr extends BinaryEvalOpExprBase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ExprEval evalString(String left, String right)
|
||||
protected ExprEval evalString(@Nullable String left, @Nullable String right)
|
||||
{
|
||||
return ExprEval.of(left.compareTo(right) >= 0, ExprType.LONG);
|
||||
return ExprEval.of(Comparators.<String>naturalNullsFirst().compare(left, right) >= 0, ExprType.LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -605,7 +610,8 @@ class BinGeqExpr extends BinaryEvalOpExprBase
|
|||
@Override
|
||||
protected final double evalDouble(double left, double right)
|
||||
{
|
||||
return Evals.asDouble(left >= right);
|
||||
// Use Double.compare for more consistent NaN handling.
|
||||
return Evals.asDouble(Double.compare(left, right) >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -617,9 +623,9 @@ class BinEqExpr extends BinaryEvalOpExprBase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ExprEval evalString(String left, String right)
|
||||
protected ExprEval evalString(@Nullable String left, @Nullable String right)
|
||||
{
|
||||
return ExprEval.of(left.equals(right), ExprType.LONG);
|
||||
return ExprEval.of(Objects.equals(left, right), ExprType.LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -643,9 +649,9 @@ class BinNeqExpr extends BinaryEvalOpExprBase
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ExprEval evalString(String left, String right)
|
||||
protected ExprEval evalString(@Nullable String left, @Nullable String right)
|
||||
{
|
||||
return ExprEval.of(!left.equals(right), ExprType.LONG);
|
||||
return ExprEval.of(!Objects.equals(left, right), ExprType.LONG);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -19,7 +19,11 @@
|
|||
|
||||
package io.druid.math.expr;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.primitives.Doubles;
|
||||
import com.google.common.primitives.Ints;
|
||||
import io.druid.common.guava.GuavaUtils;
|
||||
import io.druid.java.util.common.IAE;
|
||||
|
||||
/**
|
||||
|
@ -72,9 +76,9 @@ public abstract class ExprEval<T>
|
|||
}
|
||||
if (val instanceof Number) {
|
||||
if (val instanceof Float || val instanceof Double) {
|
||||
return new DoubleExprEval((Number)val);
|
||||
return new DoubleExprEval((Number) val);
|
||||
}
|
||||
return new LongExprEval((Number)val);
|
||||
return new LongExprEval((Number) val);
|
||||
}
|
||||
return new StringExprEval(val == null ? null : String.valueOf(val));
|
||||
}
|
||||
|
@ -98,11 +102,6 @@ public abstract class ExprEval<T>
|
|||
return value == null;
|
||||
}
|
||||
|
||||
public Number numericValue()
|
||||
{
|
||||
return (Number) value;
|
||||
}
|
||||
|
||||
public abstract int asInt();
|
||||
|
||||
public abstract long asLong();
|
||||
|
@ -120,7 +119,8 @@ public abstract class ExprEval<T>
|
|||
|
||||
public abstract Expr toExpr();
|
||||
|
||||
private static abstract class NumericExprEval extends ExprEval<Number> {
|
||||
private static abstract class NumericExprEval extends ExprEval<Number>
|
||||
{
|
||||
|
||||
private NumericExprEval(Number value)
|
||||
{
|
||||
|
@ -150,7 +150,7 @@ public abstract class ExprEval<T>
|
|||
{
|
||||
private DoubleExprEval(Number value)
|
||||
{
|
||||
super(value);
|
||||
super(Preconditions.checkNotNull(value, "value"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -182,7 +182,7 @@ public abstract class ExprEval<T>
|
|||
@Override
|
||||
public Expr toExpr()
|
||||
{
|
||||
return new DoubleExpr(value == null ? null : value.doubleValue());
|
||||
return new DoubleExpr(value.doubleValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -190,7 +190,7 @@ public abstract class ExprEval<T>
|
|||
{
|
||||
private LongExprEval(Number value)
|
||||
{
|
||||
super(value);
|
||||
super(Preconditions.checkNotNull(value, "value"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -222,7 +222,7 @@ public abstract class ExprEval<T>
|
|||
@Override
|
||||
public Expr toExpr()
|
||||
{
|
||||
return new LongExpr(value == null ? null : value.longValue());
|
||||
return new LongExpr(value.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +230,7 @@ public abstract class ExprEval<T>
|
|||
{
|
||||
private StringExprEval(String value)
|
||||
{
|
||||
super(value);
|
||||
super(Strings.emptyToNull(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -239,28 +239,34 @@ public abstract class ExprEval<T>
|
|||
return ExprType.STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isNull()
|
||||
{
|
||||
return Strings.isNullOrEmpty(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int asInt()
|
||||
{
|
||||
return Integer.parseInt(value);
|
||||
if (value == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final Integer theInt = Ints.tryParse(value);
|
||||
return theInt == null ? 0 : theInt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long asLong()
|
||||
{
|
||||
return Long.parseLong(value);
|
||||
// GuavaUtils.tryParseLong handles nulls, no need for special null handling here.
|
||||
final Long theLong = GuavaUtils.tryParseLong(value);
|
||||
return theLong == null ? 0L : theLong;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final double asDouble()
|
||||
{
|
||||
return Double.parseDouble(value);
|
||||
if (value == null) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
final Double theDouble = Doubles.tryParse(value);
|
||||
return theDouble == null ? 0.0 : theDouble;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -148,30 +148,14 @@ public class Parser
|
|||
|
||||
public static Expr.ObjectBinding withMap(final Map<String, ?> bindings)
|
||||
{
|
||||
return new Expr.ObjectBinding()
|
||||
{
|
||||
@Override
|
||||
public Number get(String name)
|
||||
{
|
||||
Number number = (Number)bindings.get(name);
|
||||
if (number == null && !bindings.containsKey(name)) {
|
||||
throw new RuntimeException("No binding found for " + name);
|
||||
}
|
||||
return number;
|
||||
}
|
||||
};
|
||||
return bindings::get;
|
||||
}
|
||||
|
||||
public static Expr.ObjectBinding withSuppliers(final Map<String, Supplier<Number>> bindings)
|
||||
public static Expr.ObjectBinding withSuppliers(final Map<String, Supplier<Object>> bindings)
|
||||
{
|
||||
return new Expr.ObjectBinding()
|
||||
{
|
||||
@Override
|
||||
public Number get(String name)
|
||||
{
|
||||
Supplier<Number> supplier = bindings.get(name);
|
||||
return supplier == null ? null : supplier.get();
|
||||
}
|
||||
return (String name) -> {
|
||||
Supplier<Object> supplier = bindings.get(name);
|
||||
return supplier == null ? null : supplier.get();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,12 @@ package io.druid.query;
|
|||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import io.druid.query.aggregation.AggregatorFactory;
|
||||
import io.druid.query.aggregation.PostAggregator;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -34,8 +35,10 @@ import java.util.Set;
|
|||
*/
|
||||
public class Queries
|
||||
{
|
||||
public static List<PostAggregator> decoratePostAggregators(List<PostAggregator> postAggs,
|
||||
Map<String, AggregatorFactory> aggFactories)
|
||||
public static List<PostAggregator> decoratePostAggregators(
|
||||
List<PostAggregator> postAggs,
|
||||
Map<String, AggregatorFactory> aggFactories
|
||||
)
|
||||
{
|
||||
List<PostAggregator> decorated = Lists.newArrayListWithExpectedSize(postAggs.size());
|
||||
for (PostAggregator aggregator : postAggs) {
|
||||
|
@ -44,34 +47,57 @@ public class Queries
|
|||
return decorated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns decorated post-aggregators, based on original un-decorated post-aggregators. In addition, this method
|
||||
* also verifies that there are no output name collisions, and that all of the post-aggregators' required input
|
||||
* fields are present.
|
||||
*
|
||||
* @param otherOutputNames names of fields that will appear in the same output namespace as aggregators and
|
||||
* post-aggregators. For most built-in query types, this is either empty, or the list of
|
||||
* dimension output names.
|
||||
* @param aggFactories aggregator factories for this query
|
||||
* @param postAggs post-aggregators for this query
|
||||
*
|
||||
* @return decorated post-aggregators
|
||||
*
|
||||
* @throws NullPointerException if otherOutputNames or aggFactories is null
|
||||
* @throws IllegalArgumentException if there are any output name collisions or missing post-aggregator inputs
|
||||
*/
|
||||
public static List<PostAggregator> prepareAggregations(
|
||||
List<String> otherOutputNames,
|
||||
List<AggregatorFactory> aggFactories,
|
||||
List<PostAggregator> postAggs
|
||||
)
|
||||
{
|
||||
Preconditions.checkNotNull(otherOutputNames, "otherOutputNames cannot be null");
|
||||
Preconditions.checkNotNull(aggFactories, "aggregations cannot be null");
|
||||
|
||||
final Map<String, AggregatorFactory> aggsFactoryMap = Maps.newHashMap();
|
||||
final Set<String> combinedOutputNames = new HashSet<>();
|
||||
combinedOutputNames.addAll(otherOutputNames);
|
||||
|
||||
final Map<String, AggregatorFactory> aggsFactoryMap = new HashMap<>();
|
||||
for (AggregatorFactory aggFactory : aggFactories) {
|
||||
Preconditions.checkArgument(!aggsFactoryMap.containsKey(aggFactory.getName()),
|
||||
"[%s] already defined", aggFactory.getName());
|
||||
Preconditions.checkArgument(
|
||||
combinedOutputNames.add(aggFactory.getName()),
|
||||
"[%s] already defined", aggFactory.getName()
|
||||
);
|
||||
aggsFactoryMap.put(aggFactory.getName(), aggFactory);
|
||||
}
|
||||
|
||||
if (postAggs != null && !postAggs.isEmpty()) {
|
||||
final Set<String> combinedAggNames = Sets.newHashSet(aggsFactoryMap.keySet());
|
||||
|
||||
List<PostAggregator> decorated = Lists.newArrayListWithExpectedSize(postAggs.size());
|
||||
for (final PostAggregator postAgg : postAggs) {
|
||||
final Set<String> dependencies = postAgg.getDependentFields();
|
||||
final Set<String> missing = Sets.difference(dependencies, combinedAggNames);
|
||||
final Set<String> missing = Sets.difference(dependencies, combinedOutputNames);
|
||||
|
||||
Preconditions.checkArgument(
|
||||
missing.isEmpty(),
|
||||
"Missing fields [%s] for postAggregator [%s]", missing, postAgg.getName()
|
||||
);
|
||||
Preconditions.checkArgument(combinedAggNames.add(postAgg.getName()),
|
||||
"[%s] already defined", postAgg.getName());
|
||||
Preconditions.checkArgument(
|
||||
combinedOutputNames.add(postAgg.getName()),
|
||||
"[%s] already defined", postAgg.getName()
|
||||
);
|
||||
|
||||
decorated.add(postAgg.decorate(aggsFactoryMap));
|
||||
}
|
||||
|
|
|
@ -271,6 +271,7 @@ public class ArithmeticPostAggregator implements PostAggregator
|
|||
|
||||
public static enum Ordering implements Comparator<Double> {
|
||||
// ensures the following order: numeric > NaN > Infinite
|
||||
// The name may be referenced via Ordering.valueOf(ordering) in the constructor.
|
||||
numericFirst {
|
||||
@Override
|
||||
public int compare(Double lhs, Double rhs) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
|||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.druid.java.util.common.guava.Comparators;
|
||||
import io.druid.math.expr.Expr;
|
||||
import io.druid.math.expr.ExprMacroTable;
|
||||
import io.druid.math.expr.Parser;
|
||||
|
@ -36,25 +37,22 @@ import java.util.Map;
|
|||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class ExpressionPostAggregator implements PostAggregator
|
||||
{
|
||||
private static final Comparator<Number> DEFAULT_COMPARATOR = new Comparator<Number>()
|
||||
{
|
||||
@Override
|
||||
public int compare(Number o1, Number o2)
|
||||
{
|
||||
if (o1 instanceof Long && o2 instanceof Long) {
|
||||
return Long.compare(o1.longValue(), o2.longValue());
|
||||
}
|
||||
return Double.compare(o1.doubleValue(), o2.doubleValue());
|
||||
}
|
||||
};
|
||||
private static final Comparator<Comparable> DEFAULT_COMPARATOR = Comparator.nullsFirst(
|
||||
(Comparable o1, Comparable o2) -> {
|
||||
if (o1 instanceof Long && o2 instanceof Long) {
|
||||
return Long.compare((long) o1, (long) o2);
|
||||
} else if (o1 instanceof Number && o2 instanceof Number) {
|
||||
return Double.compare(((Number) o1).doubleValue(), ((Number) o2).doubleValue());
|
||||
} else {
|
||||
return o1.compareTo(o2);
|
||||
}
|
||||
});
|
||||
|
||||
private final String name;
|
||||
private final String expression;
|
||||
private final Comparator comparator;
|
||||
private final Comparator<Comparable> comparator;
|
||||
private final String ordering;
|
||||
private final ExprMacroTable macroTable;
|
||||
|
||||
|
@ -150,31 +148,29 @@ public class ExpressionPostAggregator implements PostAggregator
|
|||
.build();
|
||||
}
|
||||
|
||||
public static enum Ordering implements Comparator<Number>
|
||||
public enum Ordering implements Comparator<Comparable>
|
||||
{
|
||||
// ensures the following order: numeric > NaN > Infinite
|
||||
// The name may be referenced via Ordering.valueOf(ordering) in the constructor.
|
||||
numericFirst {
|
||||
@Override
|
||||
public int compare(Number lhs, Number rhs)
|
||||
public int compare(Comparable lhs, Comparable rhs)
|
||||
{
|
||||
if (lhs instanceof Long && rhs instanceof Long) {
|
||||
return Long.compare(lhs.longValue(), rhs.longValue());
|
||||
return Long.compare(((Number) lhs).longValue(), ((Number) rhs).longValue());
|
||||
} else if (lhs instanceof Number && rhs instanceof Number) {
|
||||
double d1 = ((Number) lhs).doubleValue();
|
||||
double d2 = ((Number) rhs).doubleValue();
|
||||
if (Double.isFinite(d1) && !Double.isFinite(d2)) {
|
||||
return 1;
|
||||
}
|
||||
if (!Double.isFinite(d1) && Double.isFinite(d2)) {
|
||||
return -1;
|
||||
}
|
||||
return Double.compare(d1, d2);
|
||||
} else {
|
||||
return Comparators.<Comparable>naturalNullsFirst().compare(lhs, rhs);
|
||||
}
|
||||
double d1 = lhs.doubleValue();
|
||||
double d2 = rhs.doubleValue();
|
||||
if (isFinite(d1) && !isFinite(d2)) {
|
||||
return 1;
|
||||
}
|
||||
if (!isFinite(d1) && isFinite(d2)) {
|
||||
return -1;
|
||||
}
|
||||
return Double.compare(d1, d2);
|
||||
}
|
||||
|
||||
// Double.isFinite only exist in JDK8
|
||||
private boolean isFinite(double value)
|
||||
{
|
||||
return !Double.isInfinite(value) && !Double.isNaN(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*/
|
||||
|
@ -182,6 +183,7 @@ public class GroupByQuery extends BaseQuery<Row>
|
|||
}
|
||||
this.aggregatorSpecs = aggregatorSpecs == null ? ImmutableList.<AggregatorFactory>of() : aggregatorSpecs;
|
||||
this.postAggregatorSpecs = Queries.prepareAggregations(
|
||||
this.dimensions.stream().map(DimensionSpec::getOutputName).collect(Collectors.toList()),
|
||||
this.aggregatorSpecs,
|
||||
postAggregatorSpecs == null ? ImmutableList.<PostAggregator>of() : postAggregatorSpecs
|
||||
);
|
||||
|
|
|
@ -71,6 +71,7 @@ public class TimeseriesQuery extends BaseQuery<Result<TimeseriesResultValue>>
|
|||
this.granularity = granularity;
|
||||
this.aggregatorSpecs = aggregatorSpecs == null ? ImmutableList.of() : aggregatorSpecs;
|
||||
this.postAggregatorSpecs = Queries.prepareAggregations(
|
||||
ImmutableList.of(),
|
||||
this.aggregatorSpecs,
|
||||
postAggregatorSpecs == null ? ImmutableList.of() : postAggregatorSpecs
|
||||
);
|
||||
|
|
|
@ -55,7 +55,10 @@ public class NumericTopNMetricSpec implements TopNMetricSpec
|
|||
{
|
||||
Preconditions.checkNotNull(metric, "metric can't be null");
|
||||
Preconditions.checkNotNull(aggregatorSpecs, "aggregations cannot be null");
|
||||
Preconditions.checkArgument(aggregatorSpecs.size() > 0, "Must have at least one AggregatorFactory");
|
||||
Preconditions.checkArgument(
|
||||
aggregatorSpecs.size() > 0 || postAggregatorSpecs.size() > 0,
|
||||
"Must have at least one AggregatorFactory or PostAggregator"
|
||||
);
|
||||
|
||||
final AggregatorFactory aggregator = Iterables.tryFind(
|
||||
aggregatorSpecs,
|
||||
|
|
|
@ -81,6 +81,7 @@ public class TopNQuery extends BaseQuery<Result<TopNResultValue>>
|
|||
this.granularity = granularity;
|
||||
this.aggregatorSpecs = aggregatorSpecs == null ? ImmutableList.<AggregatorFactory>of() : aggregatorSpecs;
|
||||
this.postAggregatorSpecs = Queries.prepareAggregations(
|
||||
ImmutableList.of(dimensionSpec.getOutputName()),
|
||||
this.aggregatorSpecs,
|
||||
postAggregatorSpecs == null
|
||||
? ImmutableList.<PostAggregator>of()
|
||||
|
|
|
@ -143,8 +143,9 @@ public class TopNQueryEngine
|
|||
topNAlgorithm = new TimeExtractionTopNAlgorithm(capabilities, query);
|
||||
} else if (selector.isHasExtractionFn()) {
|
||||
topNAlgorithm = new DimExtractionTopNAlgorithm(capabilities, query);
|
||||
} else if (columnCapabilities != null && columnCapabilities.getType() != ValueType.STRING) {
|
||||
// force non-Strings to use DimExtraction for now, do a typed PooledTopN later
|
||||
} else if (columnCapabilities != null && !(columnCapabilities.getType() == ValueType.STRING
|
||||
&& columnCapabilities.isDictionaryEncoded())) {
|
||||
// Use DimExtraction for non-Strings and for non-dictionary-encoded Strings.
|
||||
topNAlgorithm = new DimExtractionTopNAlgorithm(capabilities, query);
|
||||
} else if (selector.isAggregateAllMetrics()) {
|
||||
topNAlgorithm = new PooledTopNAlgorithm(capabilities, query, bufferPool);
|
||||
|
|
|
@ -249,11 +249,16 @@ public class TopNQueryQueryToolChest extends QueryToolChest<Result<TopNResultVal
|
|||
+ 1
|
||||
);
|
||||
|
||||
// Put non-finalized aggregators before post-aggregators.
|
||||
for (int i = 0; i < aggFactoryNames.length; ++i) {
|
||||
final String name = aggFactoryNames[i];
|
||||
values.put(name, input.getMetric(name));
|
||||
}
|
||||
|
||||
// Put dimension, post-aggregators might depend on it.
|
||||
values.put(dimension, input.getDimensionValue(dimension));
|
||||
|
||||
// Put post-aggregators.
|
||||
for (PostAggregator postAgg : postAggregators) {
|
||||
Object calculatedPostAgg = input.getMetric(postAgg.getName());
|
||||
if (calculatedPostAgg != null) {
|
||||
|
@ -262,13 +267,13 @@ public class TopNQueryQueryToolChest extends QueryToolChest<Result<TopNResultVal
|
|||
values.put(postAgg.getName(), postAgg.compute(values));
|
||||
}
|
||||
}
|
||||
|
||||
// Put finalized aggregators now that post-aggregators are done.
|
||||
for (int i = 0; i < aggFactoryNames.length; ++i) {
|
||||
final String name = aggFactoryNames[i];
|
||||
values.put(name, fn.manipulate(aggregatorFactories[i], input.getMetric(name)));
|
||||
}
|
||||
|
||||
values.put(dimension, input.getDimensionValue(dimension));
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,22 +23,22 @@ import com.google.common.annotations.VisibleForTesting;
|
|||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.primitives.Doubles;
|
||||
import io.druid.common.guava.GuavaUtils;
|
||||
import io.druid.math.expr.Expr;
|
||||
import io.druid.math.expr.ExprEval;
|
||||
import io.druid.math.expr.Parser;
|
||||
import io.druid.query.dimension.DefaultDimensionSpec;
|
||||
import io.druid.segment.ColumnSelectorFactory;
|
||||
import io.druid.segment.FloatColumnSelector;
|
||||
import io.druid.segment.LongColumnSelector;
|
||||
import io.druid.segment.DimensionSelector;
|
||||
import io.druid.segment.ObjectColumnSelector;
|
||||
import io.druid.segment.column.ColumnCapabilities;
|
||||
import io.druid.segment.column.ValueType;
|
||||
import io.druid.segment.data.IndexedInts;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
public class ExpressionObjectSelector implements ObjectColumnSelector<Number>
|
||||
public class ExpressionObjectSelector implements ObjectColumnSelector<ExprEval>
|
||||
{
|
||||
private final Expr expression;
|
||||
private final Expr.ObjectBinding bindings;
|
||||
|
@ -49,28 +49,32 @@ public class ExpressionObjectSelector implements ObjectColumnSelector<Number>
|
|||
this.expression = Preconditions.checkNotNull(expression, "expression");
|
||||
}
|
||||
|
||||
public static ExpressionObjectSelector from(ColumnSelectorFactory columnSelectorFactory, Expr expression)
|
||||
static ExpressionObjectSelector from(ColumnSelectorFactory columnSelectorFactory, Expr expression)
|
||||
{
|
||||
return new ExpressionObjectSelector(createBindings(columnSelectorFactory, expression), expression);
|
||||
}
|
||||
|
||||
private static Expr.ObjectBinding createBindings(ColumnSelectorFactory columnSelectorFactory, Expr expression)
|
||||
{
|
||||
final Map<String, Supplier<Number>> suppliers = Maps.newHashMap();
|
||||
final Map<String, Supplier<Object>> suppliers = Maps.newHashMap();
|
||||
for (String columnName : Parser.findRequiredBindings(expression)) {
|
||||
final ColumnCapabilities columnCapabilities = columnSelectorFactory.getColumnCapabilities(columnName);
|
||||
final ValueType nativeType = columnCapabilities != null ? columnCapabilities.getType() : null;
|
||||
final Supplier<Number> supplier;
|
||||
final Supplier<Object> supplier;
|
||||
|
||||
if (nativeType == ValueType.FLOAT) {
|
||||
supplier = supplierFromFloatSelector(columnSelectorFactory.makeFloatColumnSelector(columnName));
|
||||
supplier = columnSelectorFactory.makeFloatColumnSelector(columnName)::get;
|
||||
} else if (nativeType == ValueType.LONG) {
|
||||
supplier = supplierFromLongSelector(columnSelectorFactory.makeLongColumnSelector(columnName));
|
||||
supplier = columnSelectorFactory.makeLongColumnSelector(columnName)::get;
|
||||
} else if (nativeType == ValueType.STRING) {
|
||||
supplier = supplierFromDimensionSelector(
|
||||
columnSelectorFactory.makeDimensionSelector(new DefaultDimensionSpec(columnName, columnName))
|
||||
);
|
||||
} else if (nativeType == null) {
|
||||
// Unknown ValueType. Try making an Object selector and see if that gives us anything useful.
|
||||
supplier = supplierFromObjectSelector(columnSelectorFactory.makeObjectColumnSelector(columnName));
|
||||
} else {
|
||||
// Unhandleable ValueType (possibly STRING or COMPLEX).
|
||||
// Unhandleable ValueType (COMPLEX).
|
||||
supplier = null;
|
||||
}
|
||||
|
||||
|
@ -84,91 +88,61 @@ public class ExpressionObjectSelector implements ObjectColumnSelector<Number>
|
|||
|
||||
@VisibleForTesting
|
||||
@Nonnull
|
||||
static Supplier<Number> supplierFromFloatSelector(final FloatColumnSelector selector)
|
||||
static Supplier<Object> supplierFromDimensionSelector(final DimensionSelector selector)
|
||||
{
|
||||
Preconditions.checkNotNull(selector, "selector");
|
||||
return new Supplier<Number>()
|
||||
{
|
||||
@Override
|
||||
public Number get()
|
||||
{
|
||||
return selector.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Nonnull
|
||||
static Supplier<Number> supplierFromLongSelector(final LongColumnSelector selector)
|
||||
{
|
||||
Preconditions.checkNotNull(selector, "selector");
|
||||
return new Supplier<Number>()
|
||||
{
|
||||
@Override
|
||||
public Number get()
|
||||
{
|
||||
return selector.get();
|
||||
return () -> {
|
||||
final IndexedInts row = selector.getRow();
|
||||
if (row.size() == 0) {
|
||||
// Treat empty multi-value rows as nulls.
|
||||
return null;
|
||||
} else if (row.size() == 1) {
|
||||
return selector.lookupName(row.get(0));
|
||||
} else {
|
||||
// Can't handle multi-value rows in expressions.
|
||||
// Treat them as nulls until we think of something better to do.
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
static Supplier<Number> supplierFromObjectSelector(final ObjectColumnSelector selector)
|
||||
static Supplier<Object> supplierFromObjectSelector(final ObjectColumnSelector selector)
|
||||
{
|
||||
final Class<?> clazz = selector == null ? null : selector.classOfObject();
|
||||
if (selector != null && (clazz.isAssignableFrom(Number.class)
|
||||
|| clazz.isAssignableFrom(String.class)
|
||||
|| Number.class.isAssignableFrom(clazz))) {
|
||||
// There may be numbers here.
|
||||
return new Supplier<Number>()
|
||||
{
|
||||
@Override
|
||||
public Number get()
|
||||
{
|
||||
return tryParse(selector.get());
|
||||
if (selector == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Class<?> clazz = selector.classOfObject();
|
||||
if (Number.class.isAssignableFrom(clazz) || String.class.isAssignableFrom(clazz)) {
|
||||
// Number, String supported as-is.
|
||||
return selector::get;
|
||||
} else if (clazz.isAssignableFrom(Number.class) || clazz.isAssignableFrom(String.class)) {
|
||||
// Might be Numbers and Strings. Use a selector that double-checks.
|
||||
return () -> {
|
||||
final Object val = selector.get();
|
||||
if (val instanceof Number || val instanceof String) {
|
||||
return val;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// We know there are no numbers here. Use a null supplier.
|
||||
// No numbers or strings.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Number tryParse(final Object value)
|
||||
@Override
|
||||
public Class<ExprEval> classOfObject()
|
||||
{
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Number) {
|
||||
return (Number) value;
|
||||
}
|
||||
|
||||
final String stringValue = String.valueOf(value);
|
||||
final Long longValue = GuavaUtils.tryParseLong(stringValue);
|
||||
if (longValue != null) {
|
||||
return longValue;
|
||||
}
|
||||
|
||||
final Double doubleValue = Doubles.tryParse(stringValue);
|
||||
if (doubleValue != null) {
|
||||
return doubleValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
return ExprEval.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Number> classOfObject()
|
||||
public ExprEval get()
|
||||
{
|
||||
return Number.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number get()
|
||||
{
|
||||
return expression.eval(bindings).numericValue();
|
||||
return expression.eval(bindings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
package io.druid.segment.virtual;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import io.druid.math.expr.Expr;
|
||||
import io.druid.math.expr.ExprEval;
|
||||
import io.druid.query.extraction.ExtractionFn;
|
||||
import io.druid.query.monomorphicprocessing.RuntimeShapeInspector;
|
||||
import io.druid.segment.ColumnSelectorFactory;
|
||||
|
@ -54,8 +56,8 @@ public class ExpressionSelectors
|
|||
@Override
|
||||
public long get()
|
||||
{
|
||||
final Number number = baseSelector.get();
|
||||
return number != null ? number.longValue() : nullValue;
|
||||
final ExprEval exprEval = baseSelector.get();
|
||||
return exprEval.isNull() ? nullValue : exprEval.asLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -79,8 +81,8 @@ public class ExpressionSelectors
|
|||
@Override
|
||||
public float get()
|
||||
{
|
||||
final Number number = baseSelector.get();
|
||||
return number != null ? number.floatValue() : nullValue;
|
||||
final ExprEval exprEval = baseSelector.get();
|
||||
return exprEval.isNull() ? nullValue : (float) exprEval.asDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -106,8 +108,7 @@ public class ExpressionSelectors
|
|||
@Override
|
||||
protected String getValue()
|
||||
{
|
||||
final Number number = baseSelector.get();
|
||||
return number == null ? null : String.valueOf(number);
|
||||
return Strings.emptyToNull(baseSelector.get().asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -123,7 +124,7 @@ public class ExpressionSelectors
|
|||
@Override
|
||||
protected String getValue()
|
||||
{
|
||||
return extractionFn.apply(baseSelector.get());
|
||||
return extractionFn.apply(Strings.emptyToNull(baseSelector.get().asString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -43,21 +43,22 @@ import java.util.Objects;
|
|||
|
||||
public class ExpressionVirtualColumn implements VirtualColumn
|
||||
{
|
||||
private static final ColumnCapabilities CAPABILITIES = new ColumnCapabilitiesImpl().setType(ValueType.FLOAT);
|
||||
|
||||
private final String name;
|
||||
private final String expression;
|
||||
private final ValueType outputType;
|
||||
private final Expr parsedExpression;
|
||||
|
||||
@JsonCreator
|
||||
public ExpressionVirtualColumn(
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("expression") String expression,
|
||||
@JsonProperty("outputType") ValueType outputType,
|
||||
@JacksonInject ExprMacroTable macroTable
|
||||
)
|
||||
{
|
||||
this.name = Preconditions.checkNotNull(name, "name");
|
||||
this.expression = Preconditions.checkNotNull(expression, "expression");
|
||||
this.outputType = outputType != null ? outputType : ValueType.FLOAT;
|
||||
this.parsedExpression = Parser.parse(expression, macroTable);
|
||||
}
|
||||
|
||||
|
@ -74,6 +75,12 @@ public class ExpressionVirtualColumn implements VirtualColumn
|
|||
return expression;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public ValueType getOutputType()
|
||||
{
|
||||
return outputType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectColumnSelector makeObjectColumnSelector(
|
||||
final String columnName,
|
||||
|
@ -95,7 +102,7 @@ public class ExpressionVirtualColumn implements VirtualColumn
|
|||
@Override
|
||||
public Object get()
|
||||
{
|
||||
return baseSelector.get();
|
||||
return baseSelector.get().value();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -136,7 +143,7 @@ public class ExpressionVirtualColumn implements VirtualColumn
|
|||
@Override
|
||||
public ColumnCapabilities capabilities(String columnName)
|
||||
{
|
||||
return CAPABILITIES;
|
||||
return new ColumnCapabilitiesImpl().setType(outputType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -157,6 +164,7 @@ public class ExpressionVirtualColumn implements VirtualColumn
|
|||
return new CacheKeyBuilder(VirtualColumnCacheHelper.CACHE_TYPE_ID_EXPRESSION)
|
||||
.appendString(name)
|
||||
.appendString(expression)
|
||||
.appendString(outputType.toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -171,13 +179,14 @@ public class ExpressionVirtualColumn implements VirtualColumn
|
|||
}
|
||||
final ExpressionVirtualColumn that = (ExpressionVirtualColumn) o;
|
||||
return Objects.equals(name, that.name) &&
|
||||
Objects.equals(expression, that.expression);
|
||||
Objects.equals(expression, that.expression) &&
|
||||
outputType == that.outputType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(name, expression);
|
||||
return Objects.hash(name, expression, outputType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -186,7 +195,7 @@ public class ExpressionVirtualColumn implements VirtualColumn
|
|||
return "ExpressionVirtualColumn{" +
|
||||
"name='" + name + '\'' +
|
||||
", expression='" + expression + '\'' +
|
||||
", parsedExpression=" + parsedExpression +
|
||||
", outputType=" + outputType +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package io.druid.query;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.druid.query.aggregation.AggregatorFactory;
|
||||
import io.druid.query.aggregation.CountAggregatorFactory;
|
||||
import io.druid.query.aggregation.DoubleSumAggregatorFactory;
|
||||
|
@ -59,7 +60,7 @@ public class QueriesTest
|
|||
boolean exceptionOccured = false;
|
||||
|
||||
try {
|
||||
Queries.prepareAggregations(aggFactories, postAggs);
|
||||
Queries.prepareAggregations(ImmutableList.of(), aggFactories, postAggs);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
exceptionOccured = true;
|
||||
|
@ -91,7 +92,7 @@ public class QueriesTest
|
|||
boolean exceptionOccured = false;
|
||||
|
||||
try {
|
||||
Queries.prepareAggregations(aggFactories, postAggs);
|
||||
Queries.prepareAggregations(ImmutableList.of(), aggFactories, postAggs);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
exceptionOccured = true;
|
||||
|
@ -145,7 +146,7 @@ public class QueriesTest
|
|||
boolean exceptionOccured = false;
|
||||
|
||||
try {
|
||||
Queries.prepareAggregations(aggFactories, postAggs);
|
||||
Queries.prepareAggregations(ImmutableList.of(), aggFactories, postAggs);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
exceptionOccured = true;
|
||||
|
@ -199,7 +200,7 @@ public class QueriesTest
|
|||
boolean exceptionOccured = false;
|
||||
|
||||
try {
|
||||
Queries.prepareAggregations(aggFactories, postAggs);
|
||||
Queries.prepareAggregations(ImmutableList.of(), aggFactories, postAggs);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
exceptionOccured = true;
|
||||
|
|
|
@ -269,8 +269,9 @@ public class SchemaEvolutionTest
|
|||
.build();
|
||||
|
||||
// Only string(1)
|
||||
// Note: Expressions implicitly cast strings to numbers, leading to the a/b vs c/d difference.
|
||||
Assert.assertEquals(
|
||||
timeseriesResult(ImmutableMap.of("a", 0L, "b", 0.0, "c", 0L, "d", 0.0)),
|
||||
timeseriesResult(ImmutableMap.of("a", 0L, "b", 0.0, "c", 31L, "d", THIRTY_ONE_POINT_ONE)),
|
||||
runQuery(query, factory, ImmutableList.of(index1))
|
||||
);
|
||||
|
||||
|
@ -293,12 +294,13 @@ public class SchemaEvolutionTest
|
|||
);
|
||||
|
||||
// string(1) + long(2) + float(3) + nonexistent(4)
|
||||
// Note: Expressions implicitly cast strings to numbers, leading to the a/b vs c/d difference.
|
||||
Assert.assertEquals(
|
||||
timeseriesResult(ImmutableMap.of(
|
||||
"a", 31L * 2,
|
||||
"b", THIRTY_ONE_POINT_ONE + 31,
|
||||
"c", 31L * 2,
|
||||
"d", THIRTY_ONE_POINT_ONE + 31
|
||||
"c", 31L * 3,
|
||||
"d", THIRTY_ONE_POINT_ONE * 2 + 31
|
||||
)),
|
||||
runQuery(query, factory, ImmutableList.of(index1, index2, index3, index4))
|
||||
);
|
||||
|
|
|
@ -446,6 +446,134 @@ public class GroupByQueryRunnerTest
|
|||
TestHelper.assertExpectedObjects(expectedResults, results, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupByWithStringPostAggregator()
|
||||
{
|
||||
GroupByQuery query = GroupByQuery
|
||||
.builder()
|
||||
.setDataSource(QueryRunnerTestHelper.dataSource)
|
||||
.setQuerySegmentSpec(QueryRunnerTestHelper.firstToThird)
|
||||
.setDimensions(Lists.<DimensionSpec>newArrayList(new DefaultDimensionSpec("quality", "alias")))
|
||||
.setAggregatorSpecs(
|
||||
Arrays.asList(
|
||||
QueryRunnerTestHelper.rowsCount,
|
||||
new LongSumAggregatorFactory("idx", "index")
|
||||
)
|
||||
)
|
||||
.setPostAggregatorSpecs(
|
||||
ImmutableList.of(
|
||||
new ExpressionPostAggregator("post", "alias + 'x'", null, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
)
|
||||
.setGranularity(QueryRunnerTestHelper.dayGran)
|
||||
.setLimitSpec(
|
||||
new DefaultLimitSpec(
|
||||
ImmutableList.of(
|
||||
new OrderByColumnSpec("post", OrderByColumnSpec.Direction.DESCENDING)
|
||||
),
|
||||
Integer.MAX_VALUE
|
||||
)
|
||||
)
|
||||
.build();
|
||||
|
||||
List<Row> expectedResults = Arrays.asList(
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "travel", "post", "travelx", "rows", 1L, "idx", 119L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "technology", "post", "technologyx", "rows", 1L, "idx", 78L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "premium", "post", "premiumx", "rows", 3L, "idx", 2900L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "news", "post", "newsx", "rows", 1L, "idx", 121L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "mezzanine", "post", "mezzaninex", "rows", 3L, "idx", 2870L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "health", "post", "healthx", "rows", 1L, "idx", 120L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "entertainment", "post", "entertainmentx", "rows", 1L, "idx", 158L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "business", "post", "businessx", "rows", 1L, "idx", 118L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "automotive", "post", "automotivex", "rows", 1L, "idx", 135L),
|
||||
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "travel", "post", "travelx", "rows", 1L, "idx", 126L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "technology", "post", "technologyx", "rows", 1L, "idx", 97L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "premium", "post", "premiumx", "rows", 3L, "idx", 2505L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "news", "post", "newsx", "rows", 1L, "idx", 114L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "mezzanine", "post", "mezzaninex", "rows", 3L, "idx", 2447L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "health", "post", "healthx", "rows", 1L, "idx", 113L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "entertainment", "post", "entertainmentx", "rows", 1L, "idx", 166L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "business", "post", "businessx", "rows", 1L, "idx", 112L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "automotive", "post", "automotivex", "rows", 1L, "idx", 147L)
|
||||
);
|
||||
|
||||
Iterable<Row> results = GroupByQueryRunnerTestHelper.runQuery(factory, runner, query);
|
||||
TestHelper.assertExpectedObjects(expectedResults, results, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupByWithStringVirtualColumn()
|
||||
{
|
||||
GroupByQuery query = GroupByQuery
|
||||
.builder()
|
||||
.setDataSource(QueryRunnerTestHelper.dataSource)
|
||||
.setQuerySegmentSpec(QueryRunnerTestHelper.firstToThird)
|
||||
.setVirtualColumns(
|
||||
new ExpressionVirtualColumn(
|
||||
"vc",
|
||||
"quality + 'x'",
|
||||
ValueType.STRING,
|
||||
TestExprMacroTable.INSTANCE
|
||||
)
|
||||
)
|
||||
.setDimensions(Lists.<DimensionSpec>newArrayList(new DefaultDimensionSpec("vc", "alias")))
|
||||
.setAggregatorSpecs(
|
||||
Arrays.asList(
|
||||
QueryRunnerTestHelper.rowsCount,
|
||||
new LongSumAggregatorFactory("idx", "index")
|
||||
)
|
||||
)
|
||||
.setGranularity(QueryRunnerTestHelper.dayGran)
|
||||
.build();
|
||||
|
||||
if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
|
||||
expectedException.expect(UnsupportedOperationException.class);
|
||||
expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
|
||||
}
|
||||
|
||||
List<Row> expectedResults = Arrays.asList(
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "automotivex", "rows", 1L, "idx", 135L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "businessx", "rows", 1L, "idx", 118L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow(
|
||||
"2011-04-01",
|
||||
"alias",
|
||||
"entertainmentx",
|
||||
"rows",
|
||||
1L,
|
||||
"idx",
|
||||
158L
|
||||
),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "healthx", "rows", 1L, "idx", 120L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "mezzaninex", "rows", 3L, "idx", 2870L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "newsx", "rows", 1L, "idx", 121L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "premiumx", "rows", 3L, "idx", 2900L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "technologyx", "rows", 1L, "idx", 78L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-01", "alias", "travelx", "rows", 1L, "idx", 119L),
|
||||
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "automotivex", "rows", 1L, "idx", 147L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "businessx", "rows", 1L, "idx", 112L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow(
|
||||
"2011-04-02",
|
||||
"alias",
|
||||
"entertainmentx",
|
||||
"rows",
|
||||
1L,
|
||||
"idx",
|
||||
166L
|
||||
),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "healthx", "rows", 1L, "idx", 113L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "mezzaninex", "rows", 3L, "idx", 2447L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "newsx", "rows", 1L, "idx", 114L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "premiumx", "rows", 3L, "idx", 2505L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "technologyx", "rows", 1L, "idx", 97L),
|
||||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "travelx", "rows", 1L, "idx", 126L)
|
||||
);
|
||||
|
||||
Iterable<Row> results = GroupByQueryRunnerTestHelper.runQuery(factory, runner, query);
|
||||
TestHelper.assertExpectedObjects(expectedResults, results, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupByWithDurationGranularity()
|
||||
{
|
||||
|
@ -493,7 +621,7 @@ public class GroupByQueryRunnerTest
|
|||
public void testGroupByWithOutputNameCollisions()
|
||||
{
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage("Duplicate output name[alias]");
|
||||
expectedException.expectMessage("[alias] already defined");
|
||||
|
||||
GroupByQuery query = GroupByQuery
|
||||
.builder()
|
||||
|
@ -2508,6 +2636,7 @@ public class GroupByQueryRunnerTest
|
|||
new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"index * 2 + indexMin / 10",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
)
|
||||
)
|
||||
|
@ -2747,7 +2876,7 @@ public class GroupByQueryRunnerTest
|
|||
// Now try it with an expression virtual column.
|
||||
builder.setLimit(Integer.MAX_VALUE)
|
||||
.setVirtualColumns(
|
||||
new ExpressionVirtualColumn("expr", "index / 2 + indexMin", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "index / 2 + indexMin", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
.setAggregatorSpecs(
|
||||
Arrays.asList(
|
||||
|
@ -4337,7 +4466,7 @@ public class GroupByQueryRunnerTest
|
|||
|
||||
subquery = new GroupByQuery.Builder(subquery)
|
||||
.setVirtualColumns(
|
||||
new ExpressionVirtualColumn("expr", "-index + 100", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "-index + 100", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
.setAggregatorSpecs(
|
||||
Arrays.asList(
|
||||
|
@ -5439,7 +5568,7 @@ public class GroupByQueryRunnerTest
|
|||
.builder()
|
||||
.setDataSource(subquery)
|
||||
.setQuerySegmentSpec(QueryRunnerTestHelper.firstToThird)
|
||||
.setVirtualColumns(new ExpressionVirtualColumn("expr", "1", TestExprMacroTable.INSTANCE))
|
||||
.setVirtualColumns(new ExpressionVirtualColumn("expr", "1", ValueType.FLOAT, TestExprMacroTable.INSTANCE))
|
||||
.setDimensions(Lists.<DimensionSpec>newArrayList())
|
||||
.setAggregatorSpecs(ImmutableList.<AggregatorFactory>of(new LongSumAggregatorFactory("count", "expr")))
|
||||
.setGranularity(QueryRunnerTestHelper.allGran)
|
||||
|
@ -8612,7 +8741,7 @@ public class GroupByQueryRunnerTest
|
|||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "travel", "rows", 2L, "idx", 243L)
|
||||
);
|
||||
|
||||
Iterable<Row> results = Sequences.toList(mergedRunner.run(allGranQuery, context), Lists.newArrayList());
|
||||
Iterable<Row> results = Sequences.toList(mergedRunner.run(allGranQuery, context), Lists.<Row>newArrayList());
|
||||
TestHelper.assertExpectedObjects(allGranExpectedResults, results, "merged");
|
||||
}
|
||||
|
||||
|
@ -8706,7 +8835,7 @@ public class GroupByQueryRunnerTest
|
|||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "premium", "market", "spot", "rows", 2L, "idx", 257L)
|
||||
);
|
||||
|
||||
Iterable<Row> results = Sequences.toList(mergedRunner.run(allGranQuery, context), Lists.newArrayList());
|
||||
Iterable<Row> results = Sequences.toList(mergedRunner.run(allGranQuery, context), Lists.<Row>newArrayList());
|
||||
TestHelper.assertExpectedObjects(allGranExpectedResults, results, "merged");
|
||||
}
|
||||
|
||||
|
@ -8804,7 +8933,7 @@ public class GroupByQueryRunnerTest
|
|||
GroupByQueryRunnerTestHelper.createExpectedRow("2011-04-02", "alias", "premium", "market", "spot", "rows", 2L, "idx", 257L)
|
||||
);
|
||||
|
||||
Iterable<Row> results = Sequences.toList(mergedRunner.run(allGranQuery, context), Lists.newArrayList());
|
||||
Iterable<Row> results = Sequences.toList(mergedRunner.run(allGranQuery, context), Lists.<Row>newArrayList());
|
||||
TestHelper.assertExpectedObjects(allGranExpectedResults, results, "merged");
|
||||
}
|
||||
|
||||
|
|
|
@ -510,7 +510,7 @@ public class SelectQueryRunnerTest
|
|||
.metrics(Lists.<String>newArrayList(QueryRunnerTestHelper.indexMetric))
|
||||
.pagingSpec(new PagingSpec(null, 10, true))
|
||||
.virtualColumns(
|
||||
new ExpressionVirtualColumn("expr", "index / 10.0", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "index / 10.0", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
.build();
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ import io.druid.query.lookup.LookupExtractionFn;
|
|||
import io.druid.query.ordering.StringComparators;
|
||||
import io.druid.query.spec.MultipleIntervalSegmentSpec;
|
||||
import io.druid.segment.TestHelper;
|
||||
import io.druid.segment.column.ValueType;
|
||||
import io.druid.segment.virtual.ExpressionVirtualColumn;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
@ -421,6 +422,7 @@ public class TimeseriesQueryRunnerTest
|
|||
new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"index",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
)
|
||||
)
|
||||
|
|
|
@ -62,10 +62,12 @@ import io.druid.query.aggregation.first.LongFirstAggregatorFactory;
|
|||
import io.druid.query.aggregation.hyperloglog.HyperUniqueFinalizingPostAggregator;
|
||||
import io.druid.query.aggregation.hyperloglog.HyperUniquesAggregatorFactory;
|
||||
import io.druid.query.aggregation.last.LongLastAggregatorFactory;
|
||||
import io.druid.query.aggregation.post.ExpressionPostAggregator;
|
||||
import io.druid.query.dimension.DefaultDimensionSpec;
|
||||
import io.druid.query.dimension.DimensionSpec;
|
||||
import io.druid.query.dimension.ExtractionDimensionSpec;
|
||||
import io.druid.query.dimension.ListFilteredDimensionSpec;
|
||||
import io.druid.query.expression.TestExprMacroTable;
|
||||
import io.druid.query.extraction.DimExtractionFn;
|
||||
import io.druid.query.extraction.ExtractionFn;
|
||||
import io.druid.query.extraction.JavaScriptExtractionFn;
|
||||
|
@ -416,6 +418,78 @@ public class TopNQueryRunnerTest
|
|||
assertExpectedResults(expectedResults, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullOnTopNOverPostAggsOnDimension()
|
||||
{
|
||||
TopNQuery query = new TopNQueryBuilder()
|
||||
.dataSource(QueryRunnerTestHelper.dataSource)
|
||||
.granularity(QueryRunnerTestHelper.allGran)
|
||||
.dimension(QueryRunnerTestHelper.marketDimension)
|
||||
.metric("dimPostAgg")
|
||||
.threshold(4)
|
||||
.intervals(QueryRunnerTestHelper.fullOnInterval)
|
||||
.aggregators(
|
||||
Lists.<AggregatorFactory>newArrayList(
|
||||
Iterables.concat(
|
||||
QueryRunnerTestHelper.commonAggregators,
|
||||
Lists.newArrayList(
|
||||
new DoubleMaxAggregatorFactory("maxIndex", "index"),
|
||||
new DoubleMinAggregatorFactory("minIndex", "index")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.postAggregators(
|
||||
ImmutableList.of(
|
||||
new ExpressionPostAggregator(
|
||||
"dimPostAgg",
|
||||
"market + 'x'",
|
||||
null,
|
||||
TestExprMacroTable.INSTANCE
|
||||
)
|
||||
)
|
||||
)
|
||||
.build();
|
||||
|
||||
List<Result<TopNResultValue>> expectedResults = Arrays.asList(
|
||||
new Result<TopNResultValue>(
|
||||
new DateTime("2011-01-12T00:00:00.000Z"),
|
||||
new TopNResultValue(
|
||||
Arrays.<Map<String, Object>>asList(
|
||||
ImmutableMap.<String, Object>builder()
|
||||
.put(QueryRunnerTestHelper.marketDimension, "upfront")
|
||||
.put("dimPostAgg", "upfrontx")
|
||||
.put("rows", 186L)
|
||||
.put("index", 192046.1060180664D)
|
||||
.put("uniques", QueryRunnerTestHelper.UNIQUES_2)
|
||||
.put("maxIndex", 1870.06103515625D)
|
||||
.put("minIndex", 545.9906005859375D)
|
||||
.build(),
|
||||
ImmutableMap.<String, Object>builder()
|
||||
.put(QueryRunnerTestHelper.marketDimension, "total_market")
|
||||
.put("dimPostAgg", "total_marketx")
|
||||
.put("rows", 186L)
|
||||
.put("index", 215679.82879638672D)
|
||||
.put("uniques", QueryRunnerTestHelper.UNIQUES_2)
|
||||
.put("maxIndex", 1743.9217529296875D)
|
||||
.put("minIndex", 792.3260498046875D)
|
||||
.build(),
|
||||
ImmutableMap.<String, Object>builder()
|
||||
.put(QueryRunnerTestHelper.marketDimension, "spot")
|
||||
.put("dimPostAgg", "spotx")
|
||||
.put("rows", 837L)
|
||||
.put("index", 95606.57232284546D)
|
||||
.put("uniques", QueryRunnerTestHelper.UNIQUES_9)
|
||||
.put("maxIndex", 277.2735290527344D)
|
||||
.put("minIndex", 59.02102279663086D)
|
||||
.build()
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
assertExpectedResults(expectedResults, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullOnTopNOverUniques()
|
||||
{
|
||||
|
@ -4211,7 +4285,7 @@ public class TopNQueryRunnerTest
|
|||
)
|
||||
)
|
||||
.postAggregators(Arrays.<PostAggregator>asList(QueryRunnerTestHelper.addRowsIndexConstant))
|
||||
.virtualColumns(new ExpressionVirtualColumn("ql_expr", "qualityLong", ExprMacroTable.nil()))
|
||||
.virtualColumns(new ExpressionVirtualColumn("ql_expr", "qualityLong", ValueType.LONG, ExprMacroTable.nil()))
|
||||
.build();
|
||||
|
||||
List<Result<TopNResultValue>> expectedResults = Arrays.asList(
|
||||
|
@ -4262,6 +4336,61 @@ public class TopNQueryRunnerTest
|
|||
assertExpectedResults(expectedResults, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTopNStringVirtualColumn()
|
||||
{
|
||||
TopNQuery query = new TopNQueryBuilder()
|
||||
.dataSource(QueryRunnerTestHelper.dataSource)
|
||||
.granularity(QueryRunnerTestHelper.allGran)
|
||||
.virtualColumns(
|
||||
new ExpressionVirtualColumn(
|
||||
"vc",
|
||||
"market + ' ' + market",
|
||||
ValueType.STRING,
|
||||
TestExprMacroTable.INSTANCE
|
||||
)
|
||||
)
|
||||
.dimension("vc")
|
||||
.metric("rows")
|
||||
.threshold(4)
|
||||
.intervals(QueryRunnerTestHelper.firstToThird)
|
||||
.aggregators(QueryRunnerTestHelper.commonAggregators)
|
||||
.postAggregators(Arrays.<PostAggregator>asList(QueryRunnerTestHelper.addRowsIndexConstant))
|
||||
.build();
|
||||
|
||||
List<Result<TopNResultValue>> expectedResults = Arrays.asList(
|
||||
new Result<>(
|
||||
new DateTime("2011-04-01T00:00:00.000Z"),
|
||||
new TopNResultValue(
|
||||
Arrays.<Map<String, Object>>asList(
|
||||
ImmutableMap.<String, Object>of(
|
||||
"vc", "spot spot",
|
||||
"rows", 18L,
|
||||
"index", 2231.8768157958984D,
|
||||
"addRowsIndexConstant", 2250.8768157958984D,
|
||||
"uniques", QueryRunnerTestHelper.UNIQUES_9
|
||||
),
|
||||
ImmutableMap.<String, Object>of(
|
||||
"vc", "total_market total_market",
|
||||
"rows", 4L,
|
||||
"index", 5351.814697265625D,
|
||||
"addRowsIndexConstant", 5356.814697265625D,
|
||||
"uniques", QueryRunnerTestHelper.UNIQUES_2
|
||||
),
|
||||
ImmutableMap.<String, Object>of(
|
||||
"vc", "upfront upfront",
|
||||
"rows", 4L,
|
||||
"index", 4875.669677734375D,
|
||||
"addRowsIndexConstant", 4880.669677734375D,
|
||||
"uniques", QueryRunnerTestHelper.UNIQUES_2
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
assertExpectedResults(expectedResults, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullOnTopNLongColumnWithExFn()
|
||||
{
|
||||
|
|
|
@ -42,6 +42,7 @@ import io.druid.query.aggregation.DoubleSumAggregatorFactory;
|
|||
import io.druid.query.aggregation.hyperloglog.HyperUniquesAggregatorFactory;
|
||||
import io.druid.query.aggregation.hyperloglog.HyperUniquesSerde;
|
||||
import io.druid.query.expression.TestExprMacroTable;
|
||||
import io.druid.segment.column.ValueType;
|
||||
import io.druid.segment.incremental.IncrementalIndex;
|
||||
import io.druid.segment.incremental.IncrementalIndexSchema;
|
||||
import io.druid.segment.incremental.OnheapIncrementalIndex;
|
||||
|
@ -113,7 +114,7 @@ public class TestIndex
|
|||
private static final Interval DATA_INTERVAL = new Interval("2011-01-12T00:00:00.000Z/2011-05-01T00:00:00.000Z");
|
||||
private static final VirtualColumns VIRTUAL_COLUMNS = VirtualColumns.create(
|
||||
Collections.<VirtualColumn>singletonList(
|
||||
new ExpressionVirtualColumn("expr", "index + 10", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "index + 10", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
);
|
||||
public static final AggregatorFactory[] METRIC_AGGS = new AggregatorFactory[]{
|
||||
|
|
|
@ -84,7 +84,7 @@ public abstract class BaseFilterTest
|
|||
{
|
||||
private static final VirtualColumns VIRTUAL_COLUMNS = VirtualColumns.create(
|
||||
ImmutableList.<VirtualColumn>of(
|
||||
new ExpressionVirtualColumn("expr", "1.0 + 0.1", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "1.0 + 0.1", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -31,11 +31,29 @@ import java.util.List;
|
|||
|
||||
public class ExpressionObjectSelectorTest
|
||||
{
|
||||
@Test
|
||||
public void testSupplierFromDimensionSelector()
|
||||
{
|
||||
final SettableSupplier<String> settableSupplier = new SettableSupplier<>();
|
||||
final Supplier<Object> supplier = ExpressionObjectSelector.supplierFromDimensionSelector(
|
||||
dimensionSelectorFromSupplier(settableSupplier)
|
||||
);
|
||||
|
||||
Assert.assertNotNull(supplier);
|
||||
Assert.assertEquals(null, supplier.get());
|
||||
|
||||
settableSupplier.set(null);
|
||||
Assert.assertEquals(null, supplier.get());
|
||||
|
||||
settableSupplier.set("1234");
|
||||
Assert.assertEquals("1234", supplier.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupplierFromObjectSelectorObject()
|
||||
{
|
||||
final SettableSupplier<Object> settableSupplier = new SettableSupplier<>();
|
||||
final Supplier<Number> supplier = ExpressionObjectSelector.supplierFromObjectSelector(
|
||||
final Supplier<Object> supplier = ExpressionObjectSelector.supplierFromObjectSelector(
|
||||
objectSelectorFromSupplier(settableSupplier, Object.class)
|
||||
);
|
||||
|
||||
|
@ -49,17 +67,17 @@ public class ExpressionObjectSelectorTest
|
|||
Assert.assertEquals(1L, supplier.get());
|
||||
|
||||
settableSupplier.set("1234");
|
||||
Assert.assertEquals(1234L, supplier.get());
|
||||
Assert.assertEquals("1234", supplier.get());
|
||||
|
||||
settableSupplier.set("1.234");
|
||||
Assert.assertEquals(1.234d, supplier.get());
|
||||
Assert.assertEquals("1.234", supplier.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupplierFromObjectSelectorNumber()
|
||||
{
|
||||
final SettableSupplier<Number> settableSupplier = new SettableSupplier<>();
|
||||
final Supplier<Number> supplier = ExpressionObjectSelector.supplierFromObjectSelector(
|
||||
final Supplier<Object> supplier = ExpressionObjectSelector.supplierFromObjectSelector(
|
||||
objectSelectorFromSupplier(settableSupplier, Number.class)
|
||||
);
|
||||
|
||||
|
@ -78,7 +96,7 @@ public class ExpressionObjectSelectorTest
|
|||
public void testSupplierFromObjectSelectorString()
|
||||
{
|
||||
final SettableSupplier<String> settableSupplier = new SettableSupplier<>();
|
||||
final Supplier<Number> supplier = ExpressionObjectSelector.supplierFromObjectSelector(
|
||||
final Supplier<Object> supplier = ExpressionObjectSelector.supplierFromObjectSelector(
|
||||
objectSelectorFromSupplier(settableSupplier, String.class)
|
||||
);
|
||||
|
||||
|
@ -86,17 +104,17 @@ public class ExpressionObjectSelectorTest
|
|||
Assert.assertEquals(null, supplier.get());
|
||||
|
||||
settableSupplier.set("1.1");
|
||||
Assert.assertEquals(1.1d, supplier.get());
|
||||
Assert.assertEquals("1.1", supplier.get());
|
||||
|
||||
settableSupplier.set("1");
|
||||
Assert.assertEquals(1L, supplier.get());
|
||||
Assert.assertEquals("1", supplier.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupplierFromObjectSelectorList()
|
||||
{
|
||||
final SettableSupplier<List> settableSupplier = new SettableSupplier<>();
|
||||
final Supplier<Number> supplier = ExpressionObjectSelector.supplierFromObjectSelector(
|
||||
final Supplier<Object> supplier = ExpressionObjectSelector.supplierFromObjectSelector(
|
||||
objectSelectorFromSupplier(settableSupplier, List.class)
|
||||
);
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import io.druid.segment.DimensionSelector;
|
|||
import io.druid.segment.FloatColumnSelector;
|
||||
import io.druid.segment.LongColumnSelector;
|
||||
import io.druid.segment.ObjectColumnSelector;
|
||||
import io.druid.segment.column.ValueType;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -41,30 +42,49 @@ public class ExpressionVirtualColumnTest
|
|||
{
|
||||
private static final InputRow ROW0 = new MapBasedInputRow(
|
||||
0,
|
||||
ImmutableList.<String>of(),
|
||||
ImmutableMap.<String, Object>of()
|
||||
ImmutableList.of(),
|
||||
ImmutableMap.of()
|
||||
);
|
||||
|
||||
private static final InputRow ROW1 = new MapBasedInputRow(
|
||||
0,
|
||||
ImmutableList.<String>of(),
|
||||
ImmutableMap.<String, Object>of("x", 4)
|
||||
ImmutableList.of(),
|
||||
ImmutableMap.of("x", 4)
|
||||
);
|
||||
|
||||
private static final InputRow ROW2 = new MapBasedInputRow(
|
||||
0,
|
||||
ImmutableList.<String>of(),
|
||||
ImmutableMap.<String, Object>of("x", 2.1, "y", 3L)
|
||||
ImmutableList.of(),
|
||||
ImmutableMap.of("x", 2.1, "y", 3L, "z", "foobar")
|
||||
);
|
||||
private static final InputRow ROW3 = new MapBasedInputRow(
|
||||
0,
|
||||
ImmutableList.of(),
|
||||
ImmutableMap.of("x", 2L, "y", 3L, "z", "foobar")
|
||||
);
|
||||
|
||||
private static final ExpressionVirtualColumn XPLUSY = new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"x + y",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
private static final ExpressionVirtualColumn CONSTANT_LIKE = new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"like('foo', 'f%')",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
private static final ExpressionVirtualColumn ZLIKE = new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"like(z, 'f%')",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
private static final ExpressionVirtualColumn ZCONCATX = new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"z + cast(x, 'string')",
|
||||
ValueType.STRING,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
private static final TestColumnSelectorFactory COLUMN_SELECTOR_FACTORY = new TestColumnSelectorFactory();
|
||||
|
@ -78,10 +98,13 @@ public class ExpressionVirtualColumnTest
|
|||
Assert.assertEquals(null, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW1);
|
||||
Assert.assertEquals(null, selector.get());
|
||||
Assert.assertEquals(4.0d, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW2);
|
||||
Assert.assertEquals(5.1d, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW3);
|
||||
Assert.assertEquals(5L, selector.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -93,10 +116,31 @@ public class ExpressionVirtualColumnTest
|
|||
Assert.assertEquals(0L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW1);
|
||||
Assert.assertEquals(0L, selector.get());
|
||||
Assert.assertEquals(4L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW2);
|
||||
Assert.assertEquals(5L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW3);
|
||||
Assert.assertEquals(5L, selector.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongSelectorUsingStringFunction()
|
||||
{
|
||||
final LongColumnSelector selector = ZCONCATX.makeLongColumnSelector("expr", COLUMN_SELECTOR_FACTORY);
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW0);
|
||||
Assert.assertEquals(0L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW1);
|
||||
Assert.assertEquals(4L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW2);
|
||||
Assert.assertEquals(0L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW3);
|
||||
Assert.assertEquals(0L, selector.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -108,17 +152,20 @@ public class ExpressionVirtualColumnTest
|
|||
Assert.assertEquals(0.0f, selector.get(), 0.0f);
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW1);
|
||||
Assert.assertEquals(0.0f, selector.get(), 0.0f);
|
||||
Assert.assertEquals(4.0f, selector.get(), 0.0f);
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW2);
|
||||
Assert.assertEquals(5.1f, selector.get(), 0.0f);
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW3);
|
||||
Assert.assertEquals(5.0f, selector.get(), 0.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDimensionSelector()
|
||||
{
|
||||
final DimensionSelector selector = XPLUSY.makeDimensionSelector(
|
||||
new DefaultDimensionSpec("expr", "x"),
|
||||
new DefaultDimensionSpec("expr", "expr"),
|
||||
COLUMN_SELECTOR_FACTORY
|
||||
);
|
||||
|
||||
|
@ -133,16 +180,49 @@ public class ExpressionVirtualColumnTest
|
|||
Assert.assertEquals(null, selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW1);
|
||||
Assert.assertEquals(true, nullMatcher.matches());
|
||||
Assert.assertEquals(false, nullMatcher.matches());
|
||||
Assert.assertEquals(false, fiveMatcher.matches());
|
||||
Assert.assertEquals(false, nonNullMatcher.matches());
|
||||
Assert.assertEquals(null, selector.lookupName(selector.getRow().get(0)));
|
||||
Assert.assertEquals(true, nonNullMatcher.matches());
|
||||
Assert.assertEquals("4.0", selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW2);
|
||||
Assert.assertEquals(false, nullMatcher.matches());
|
||||
Assert.assertEquals(false, fiveMatcher.matches());
|
||||
Assert.assertEquals(true, nonNullMatcher.matches());
|
||||
Assert.assertEquals("5.1", selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW3);
|
||||
Assert.assertEquals(false, nullMatcher.matches());
|
||||
Assert.assertEquals(true, fiveMatcher.matches());
|
||||
Assert.assertEquals(true, nonNullMatcher.matches());
|
||||
Assert.assertEquals("5", selector.lookupName(selector.getRow().get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDimensionSelectorUsingStringFunction()
|
||||
{
|
||||
final DimensionSelector selector = ZCONCATX.makeDimensionSelector(
|
||||
new DefaultDimensionSpec("expr", "expr"),
|
||||
COLUMN_SELECTOR_FACTORY
|
||||
);
|
||||
|
||||
Assert.assertNotNull(selector);
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW0);
|
||||
Assert.assertEquals(1, selector.getRow().size());
|
||||
Assert.assertEquals(null, selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW1);
|
||||
Assert.assertEquals(1, selector.getRow().size());
|
||||
Assert.assertEquals("4", selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW2);
|
||||
Assert.assertEquals(1, selector.getRow().size());
|
||||
Assert.assertEquals("foobar2.1", selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW3);
|
||||
Assert.assertEquals(1, selector.getRow().size());
|
||||
Assert.assertEquals("foobar2", selector.lookupName(selector.getRow().get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -164,16 +244,22 @@ public class ExpressionVirtualColumnTest
|
|||
Assert.assertEquals(null, selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW1);
|
||||
Assert.assertEquals(true, nullMatcher.matches());
|
||||
Assert.assertEquals(false, nullMatcher.matches());
|
||||
Assert.assertEquals(false, fiveMatcher.matches());
|
||||
Assert.assertEquals(false, nonNullMatcher.matches());
|
||||
Assert.assertEquals(null, selector.lookupName(selector.getRow().get(0)));
|
||||
Assert.assertEquals(true, nonNullMatcher.matches());
|
||||
Assert.assertEquals("4", selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW2);
|
||||
Assert.assertEquals(false, nullMatcher.matches());
|
||||
Assert.assertEquals(true, fiveMatcher.matches());
|
||||
Assert.assertEquals(true, nonNullMatcher.matches());
|
||||
Assert.assertEquals("5", selector.lookupName(selector.getRow().get(0)));
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW3);
|
||||
Assert.assertEquals(false, nullMatcher.matches());
|
||||
Assert.assertEquals(true, fiveMatcher.matches());
|
||||
Assert.assertEquals(true, nonNullMatcher.matches());
|
||||
Assert.assertEquals("5", selector.lookupName(selector.getRow().get(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -185,9 +271,30 @@ public class ExpressionVirtualColumnTest
|
|||
Assert.assertEquals(1L, selector.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongSelectorWithZLikeExprMacro()
|
||||
{
|
||||
final LongColumnSelector selector = ZLIKE.makeLongColumnSelector("expr", COLUMN_SELECTOR_FACTORY);
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW0);
|
||||
Assert.assertEquals(0L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW1);
|
||||
Assert.assertEquals(0L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW2);
|
||||
Assert.assertEquals(1L, selector.get());
|
||||
|
||||
COLUMN_SELECTOR_FACTORY.setRow(ROW3);
|
||||
Assert.assertEquals(1L, selector.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequiredColumns()
|
||||
{
|
||||
Assert.assertEquals(ImmutableList.of("x", "y"), XPLUSY.requiredColumns());
|
||||
Assert.assertEquals(ImmutableList.of(), CONSTANT_LIKE.requiredColumns());
|
||||
Assert.assertEquals(ImmutableList.of("z"), ZLIKE.requiredColumns());
|
||||
Assert.assertEquals(ImmutableList.of("z", "x"), ZCONCATX.requiredColumns());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ public class VirtualColumnsTest
|
|||
final ExpressionVirtualColumn expr = new ExpressionVirtualColumn(
|
||||
"__time",
|
||||
"x + y",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
|
||||
|
@ -142,12 +143,14 @@ public class VirtualColumnsTest
|
|||
final ExpressionVirtualColumn expr = new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"x + y",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
|
||||
final ExpressionVirtualColumn expr2 = new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"x * 2",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
|
||||
|
@ -163,12 +166,14 @@ public class VirtualColumnsTest
|
|||
final ExpressionVirtualColumn expr = new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"x + expr2",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
|
||||
final ExpressionVirtualColumn expr2 = new ExpressionVirtualColumn(
|
||||
"expr2",
|
||||
"expr * 2",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
|
||||
|
@ -183,13 +188,13 @@ public class VirtualColumnsTest
|
|||
{
|
||||
final VirtualColumns virtualColumns = VirtualColumns.create(
|
||||
ImmutableList.<VirtualColumn>of(
|
||||
new ExpressionVirtualColumn("expr", "x + y", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "x + y", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
);
|
||||
|
||||
final VirtualColumns virtualColumns2 = VirtualColumns.create(
|
||||
ImmutableList.<VirtualColumn>of(
|
||||
new ExpressionVirtualColumn("expr", "x + y", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "x + y", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -202,13 +207,13 @@ public class VirtualColumnsTest
|
|||
{
|
||||
final VirtualColumns virtualColumns = VirtualColumns.create(
|
||||
ImmutableList.<VirtualColumn>of(
|
||||
new ExpressionVirtualColumn("expr", "x + y", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "x + y", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
);
|
||||
|
||||
final VirtualColumns virtualColumns2 = VirtualColumns.create(
|
||||
ImmutableList.<VirtualColumn>of(
|
||||
new ExpressionVirtualColumn("expr", "x + y", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "x + y", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -227,8 +232,8 @@ public class VirtualColumnsTest
|
|||
{
|
||||
final ObjectMapper mapper = TestHelper.getJsonMapper();
|
||||
final ImmutableList<VirtualColumn> theColumns = ImmutableList.<VirtualColumn>of(
|
||||
new ExpressionVirtualColumn("expr", "x + y", TestExprMacroTable.INSTANCE),
|
||||
new ExpressionVirtualColumn("expr2", "x + z", TestExprMacroTable.INSTANCE)
|
||||
new ExpressionVirtualColumn("expr", "x + y", ValueType.FLOAT, TestExprMacroTable.INSTANCE),
|
||||
new ExpressionVirtualColumn("expr2", "x + z", ValueType.FLOAT, TestExprMacroTable.INSTANCE)
|
||||
);
|
||||
final VirtualColumns virtualColumns = VirtualColumns.create(theColumns);
|
||||
|
||||
|
@ -254,6 +259,7 @@ public class VirtualColumnsTest
|
|||
final ExpressionVirtualColumn expr = new ExpressionVirtualColumn(
|
||||
"expr",
|
||||
"1",
|
||||
ValueType.FLOAT,
|
||||
TestExprMacroTable.INSTANCE
|
||||
);
|
||||
final DottyVirtualColumn dotty = new DottyVirtualColumn("foo");
|
||||
|
|
Loading…
Reference in New Issue