-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Expressions work better with strings. #4394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 static ExprEval bestEffortOf(Object val) | |
| } | ||
| 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 boolean isNull() | |
| return value == null; | ||
| } | ||
|
|
||
| public Number numericValue() | ||
| { | ||
| return (Number) value; | ||
| } | ||
|
|
||
| public abstract int asInt(); | ||
|
|
||
| public abstract long asLong(); | ||
|
|
@@ -120,7 +119,8 @@ public String asString() | |
|
|
||
| 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 @@ private static class DoubleExprEval extends NumericExprEval | |
| { | ||
| private DoubleExprEval(Number value) | ||
| { | ||
| super(value); | ||
| super(Preconditions.checkNotNull(value, "value")); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -182,15 +182,15 @@ public final ExprEval castTo(ExprType castTo) | |
| @Override | ||
| public Expr toExpr() | ||
| { | ||
| return new DoubleExpr(value == null ? null : value.doubleValue()); | ||
| return new DoubleExpr(value.doubleValue()); | ||
| } | ||
| } | ||
|
|
||
| private static class LongExprEval extends NumericExprEval | ||
| { | ||
| private LongExprEval(Number value) | ||
| { | ||
| super(value); | ||
| super(Preconditions.checkNotNull(value, "value")); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure about this. LongExpr.getLiteralValue() is nullable, which means its value can be null. Also, why is null checking needed here instead of treating nulls as 0s?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same response as #4394 (comment). |
||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -222,15 +222,15 @@ public final ExprEval castTo(ExprType castTo) | |
| @Override | ||
| public Expr toExpr() | ||
| { | ||
| return new LongExpr(value == null ? null : value.longValue()); | ||
| return new LongExpr(value.longValue()); | ||
| } | ||
| } | ||
|
|
||
| private static class StringExprEval extends ExprEval<String> | ||
| { | ||
| private StringExprEval(String value) | ||
| { | ||
| super(value); | ||
| super(Strings.emptyToNull(value)); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -239,28 +239,34 @@ public final ExprType type() | |
| 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why consume parse errors and turn them into nulls?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought it would be more useful to treat unparseable strings as zeroes rather than to throw errors. I don't want one bad string in a column to make the entire query fail. It's similar to the behavior of Druid's "numeric" comparator on string columns, where unparseable numbers as less than all other numbers, not as errors. |
||
| } | ||
|
|
||
| @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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -148,30 +148,14 @@ public void visit(Expr expr) | |
|
|
||
| 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why ignore situation when there is no binding?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because rather than treating expressions referencing missing columns as errors, I'd like to treat them as if they were reading nulls, since that's consistent with what Druid generally does when you ask it to read columns that don't exist. Just doing
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a tradeoff between "ease of use" and the problems from consuming client errors. Maybe it should be a configurable?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this needs to be configurable for expressions, since the behavior of treating nonexistent columns as having some "default" value is very ingrained in Druid -- not just in the expressions but throughout the system in general. I guess you could argue that you want it to be configurable throughout Druid, but that is out of scope for this PR, imo. In this one I'm just trying to make Druid expression behavior better match what the rest of Druid already does. |
||
| } | ||
|
|
||
| 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(); | ||
| }; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this. DoubleExpr.getLiteralValue() is nullable, which means its value can be null. Also, why is null checking needed here instead of treating nulls as 0s?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value can't actually be null. For reasons of trying to make the behavior more consistent with Druid null handling in general,
nullvalue is always going to be aStringExprEval. (See that the constructors for DoubleExprEval and LongExprEval have a non-null check for value). I'll remove the null checks in toExpr, since they are pointless, given value cannot be null.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Then, would you remove
@Nullableannotations at DoubleExpr.getLiteralValue() and LongExpr.getLiteralValue()?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, ok. I removed those and added non-null checks to the constructors to be defensive.