-
Notifications
You must be signed in to change notification settings - Fork 3.8k
SQL compatible Null Handling Part 2 - Processing Layer and Druid-SQL changes #5452
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
1887f9f
51be891
f1f655e
92b8451
fd570b3
14a8148
ced5c24
a5b6f9b
97f6114
351aefb
a7418d7
15000cb
8ae2c97
6c81208
c35c14d
d23956d
d4f9b89
bd5f108
ddae594
c20c930
a83c933
c22c43a
b095f45
eddd435
8bc95aa
04199c3
fe17eb8
a7c88a9
2b7ebdf
e88db45
86270e4
3ef44ac
0281185
a909829
ad204a9
0fdecc4
06078ec
78e3b08
a9e9179
4f1f2ca
1c081f4
d9c557d
d2c0084
00e14b7
ef0e725
fc3b129
cd8b124
983af33
7074b69
cc0066d
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 |
|---|---|---|
|
|
@@ -272,6 +272,9 @@ class UnaryMinusExpr extends UnaryExpr | |
| public ExprEval eval(ObjectBinding bindings) | ||
| { | ||
| ExprEval ret = expr.eval(bindings); | ||
| if (NullHandling.sqlCompatible() && (ret.value() == null)) { | ||
| return ExprEval.of(null); | ||
| } | ||
| if (ret.type() == ExprType.LONG) { | ||
| return ExprEval.of(-ret.asLong()); | ||
| } | ||
|
|
@@ -307,6 +310,9 @@ class UnaryNotExpr extends UnaryExpr | |
| public ExprEval eval(ObjectBinding bindings) | ||
| { | ||
| ExprEval ret = expr.eval(bindings); | ||
| if (NullHandling.sqlCompatible() && (ret.value() == null)) { | ||
| return ExprEval.of(null); | ||
| } | ||
| // conforming to other boolean-returning binary operators | ||
| ExprType retType = ret.type() == ExprType.DOUBLE ? ExprType.DOUBLE : ExprType.LONG; | ||
| return ExprEval.of(!ret.asBoolean(), retType); | ||
|
|
@@ -365,15 +371,21 @@ public ExprEval eval(ObjectBinding bindings) | |
|
|
||
| // Result of any Binary expressions is null if any of the argument is null. | ||
| // e.g "select null * 2 as c;" or "select null + 1 as c;" will return null as per Standard SQL spec. | ||
| if (NullHandling.sqlCompatible() && (leftVal.isNull() || rightVal.isNull())) { | ||
| if (NullHandling.sqlCompatible() && (leftVal.value() == null || rightVal.value() == null)) { | ||
|
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 this change?
Member
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. Reason is that for String ExprEval isNull method now checks that the expression can be parsed as a valid long/double/float or not instead of checking the nullability. |
||
| return ExprEval.of(null); | ||
| } | ||
|
|
||
| if (leftVal.type() == ExprType.STRING && rightVal.type() == ExprType.STRING) { | ||
| return evalString(leftVal.asString(), rightVal.asString()); | ||
| } else if (leftVal.type() == ExprType.LONG && rightVal.type() == ExprType.LONG) { | ||
| if (NullHandling.sqlCompatible() && (leftVal.isNumericNull() || rightVal.isNumericNull())) { | ||
| return ExprEval.of(null); | ||
| } | ||
| return ExprEval.of(evalLong(leftVal.asLong(), rightVal.asLong())); | ||
| } else { | ||
| if (NullHandling.sqlCompatible() && (leftVal.isNumericNull() || rightVal.isNumericNull())) { | ||
| return ExprEval.of(null); | ||
| } | ||
| return ExprEval.of(evalDouble(leftVal.asDouble(), rightVal.asDouble())); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,9 +19,7 @@ | |
|
|
||
| package io.druid.math.expr; | ||
|
|
||
| import com.google.common.base.Preconditions; | ||
| import com.google.common.primitives.Doubles; | ||
| import com.google.common.primitives.Ints; | ||
| import io.druid.common.config.NullHandling; | ||
| import io.druid.common.guava.GuavaUtils; | ||
| import io.druid.java.util.common.IAE; | ||
|
|
@@ -32,7 +30,7 @@ | |
| */ | ||
| public abstract class ExprEval<T> | ||
| { | ||
| public static ExprEval ofLong(Number longValue) | ||
| public static ExprEval ofLong(@Nullable Number longValue) | ||
| { | ||
| return new LongExprEval(longValue); | ||
| } | ||
|
|
@@ -42,7 +40,7 @@ public static ExprEval of(long longValue) | |
| return new LongExprEval(longValue); | ||
| } | ||
|
|
||
| public static ExprEval ofDouble(Number doubleValue) | ||
| public static ExprEval ofDouble(@Nullable Number doubleValue) | ||
| { | ||
| return new DoubleExprEval(doubleValue); | ||
| } | ||
|
|
@@ -71,7 +69,7 @@ public static ExprEval of(boolean value, ExprType type) | |
| } | ||
| } | ||
|
|
||
| public static ExprEval bestEffortOf(Object val) | ||
| public static ExprEval bestEffortOf(@Nullable Object val) | ||
| { | ||
| if (val instanceof ExprEval) { | ||
| return (ExprEval) val; | ||
|
|
@@ -85,6 +83,7 @@ public static ExprEval bestEffortOf(Object val) | |
| return new StringExprEval(val == null ? null : String.valueOf(val)); | ||
| } | ||
|
|
||
| @Nullable | ||
| final T value; | ||
|
|
||
| private ExprEval(T value) | ||
|
|
@@ -99,10 +98,10 @@ public Object value() | |
| return value; | ||
|
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. This field should be annotated
Member
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. done. |
||
| } | ||
|
|
||
| public boolean isNull() | ||
| { | ||
| return value == null; | ||
| } | ||
| /** | ||
| * returns true if numeric primitive value for this ExprEval is null, otherwise false. | ||
| */ | ||
| public abstract boolean isNumericNull(); | ||
|
|
||
| public abstract int asInt(); | ||
|
|
||
|
|
@@ -125,7 +124,7 @@ public String asString() | |
| private abstract static class NumericExprEval extends ExprEval<Number> | ||
| { | ||
|
|
||
| private NumericExprEval(Number value) | ||
| private NumericExprEval(@Nullable Number value) | ||
| { | ||
| super(value); | ||
| } | ||
|
|
@@ -147,13 +146,19 @@ public final double asDouble() | |
| { | ||
| return value.doubleValue(); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean isNumericNull() | ||
| { | ||
| return value == null; | ||
| } | ||
| } | ||
|
|
||
| private static class DoubleExprEval extends NumericExprEval | ||
| { | ||
| private DoubleExprEval(Number value) | ||
| private DoubleExprEval(@Nullable Number value) | ||
| { | ||
| super(Preconditions.checkNotNull(value, "value")); | ||
| super(value == null ? NullHandling.defaultDoubleValue() : value); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -175,7 +180,7 @@ public final ExprEval castTo(ExprType castTo) | |
| case DOUBLE: | ||
| return this; | ||
| case LONG: | ||
| return ExprEval.of(asLong()); | ||
| return ExprEval.of(value == null ? null : asLong()); | ||
| case STRING: | ||
| return ExprEval.of(asString()); | ||
| } | ||
|
|
@@ -191,9 +196,9 @@ public Expr toExpr() | |
|
|
||
| private static class LongExprEval extends NumericExprEval | ||
| { | ||
| private LongExprEval(Number value) | ||
| private LongExprEval(@Nullable Number value) | ||
| { | ||
| super(Preconditions.checkNotNull(value, "value")); | ||
| super(value == null ? NullHandling.defaultLongValue() : value); | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -213,7 +218,7 @@ public final ExprEval castTo(ExprType castTo) | |
| { | ||
| switch (castTo) { | ||
| case DOUBLE: | ||
| return ExprEval.of(asDouble()); | ||
| return ExprEval.of(value == null ? null : asDouble()); | ||
| case LONG: | ||
| return this; | ||
| case STRING: | ||
|
|
@@ -231,6 +236,8 @@ public Expr toExpr() | |
|
|
||
| private static class StringExprEval extends ExprEval<String> | ||
| { | ||
| private Number numericVal; | ||
|
|
||
| private StringExprEval(@Nullable String value) | ||
| { | ||
| super(NullHandling.emptyToNullIfNeeded(value)); | ||
|
|
@@ -245,36 +252,63 @@ public final ExprType type() | |
| @Override | ||
| public final int asInt() | ||
| { | ||
| if (value == null) { | ||
| Number number = asNumber(); | ||
| if (number == null) { | ||
| assert NullHandling.replaceWithDefault(); | ||
| return 0; | ||
| } | ||
|
|
||
| final Integer theInt = Ints.tryParse(value); | ||
| assert NullHandling.replaceWithDefault() || theInt != null; | ||
| return theInt == null ? 0 : theInt; | ||
| return number.intValue(); | ||
| } | ||
|
|
||
| @Override | ||
| public final long asLong() | ||
| { | ||
| // GuavaUtils.tryParseLong handles nulls, no need for special null handling here. | ||
| final Long theLong = GuavaUtils.tryParseLong(value); | ||
| assert NullHandling.replaceWithDefault() || theLong != null; | ||
| return theLong == null ? 0L : theLong; | ||
| Number number = asNumber(); | ||
| if (number == null) { | ||
| assert NullHandling.replaceWithDefault(); | ||
| return 0L; | ||
| } | ||
| return number.longValue(); | ||
| } | ||
|
|
||
| @Override | ||
| public final double asDouble() | ||
| { | ||
| if (value == null) { | ||
| Number number = asNumber(); | ||
| if (number == null) { | ||
| assert NullHandling.replaceWithDefault(); | ||
| return 0.0; | ||
| return 0.0d; | ||
| } | ||
| return number.doubleValue(); | ||
| } | ||
|
|
||
| @Nullable | ||
| private Number asNumber() | ||
| { | ||
| if (value == null) { | ||
| return null; | ||
| } | ||
| if (numericVal != null) { | ||
| // Optimization for non-null case. | ||
| return numericVal; | ||
| } | ||
| Number rv; | ||
| Long v = GuavaUtils.tryParseLong(value); | ||
| // Do NOT use ternary operator here, because it makes Java to convert Long to Double | ||
| if (v != null) { | ||
| rv = v; | ||
| } else { | ||
| rv = Doubles.tryParse(value); | ||
| } | ||
|
|
||
| numericVal = rv; | ||
| return rv; | ||
| } | ||
|
|
||
| final Double theDouble = Doubles.tryParse(value); | ||
| assert NullHandling.replaceWithDefault() || theDouble != null; | ||
| return theDouble == null ? 0.0 : theDouble; | ||
| @Override | ||
| public boolean isNumericNull() | ||
| { | ||
| return asNumber() == null; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -288,9 +322,9 @@ public final ExprEval castTo(ExprType castTo) | |
| { | ||
| switch (castTo) { | ||
| case DOUBLE: | ||
| return ExprEval.of(asDouble()); | ||
| return ExprEval.ofDouble(asNumber()); | ||
| case LONG: | ||
| return ExprEval.of(asLong()); | ||
| return ExprEval.ofLong(asNumber()); | ||
| case STRING: | ||
| return this; | ||
| } | ||
|
|
||
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.
Could you implement this thing: #5278 (comment) in this PR?
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.
sure, will add a checkstyle rule.
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.
added.