blob: 61cdc26f6dd1d3fb945c62cf72df42f9dc0b5dd0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.druid.math.expr;
import com.google.common.primitives.Doubles;
import org.apache.druid.common.config.NullHandling;
import org.apache.druid.common.guava.GuavaUtils;
import org.apache.druid.java.util.common.IAE;
import javax.annotation.Nullable;
import java.util.Arrays;
/**
* Generic result holder for evaluated {@link Expr} containing the value and {@link ExprType} of the value to allow
*/
public abstract class ExprEval<T>
{
public static ExprEval ofLong(@Nullable Number longValue)
{
return new LongExprEval(longValue);
}
public static ExprEval of(long longValue)
{
return new LongExprEval(longValue);
}
public static ExprEval ofDouble(@Nullable Number doubleValue)
{
return new DoubleExprEval(doubleValue);
}
public static ExprEval of(double doubleValue)
{
return new DoubleExprEval(doubleValue);
}
public static ExprEval of(@Nullable String stringValue)
{
if (stringValue == null) {
return StringExprEval.OF_NULL;
}
return new StringExprEval(stringValue);
}
public static ExprEval ofLongArray(@Nullable Long[] longValue)
{
return new LongArrayExprEval(longValue);
}
public static ExprEval ofDoubleArray(@Nullable Double[] doubleValue)
{
return new DoubleArrayExprEval(doubleValue);
}
public static ExprEval ofStringArray(@Nullable String[] stringValue)
{
return new StringArrayExprEval(stringValue);
}
public static ExprEval of(boolean value, ExprType type)
{
switch (type) {
case DOUBLE:
return ExprEval.of(Evals.asDouble(value));
case LONG:
return ExprEval.of(Evals.asLong(value));
case STRING:
return ExprEval.of(String.valueOf(value));
default:
throw new IllegalArgumentException("invalid type " + type);
}
}
public static ExprEval bestEffortOf(@Nullable Object val)
{
if (val instanceof ExprEval) {
return (ExprEval) val;
}
if (val instanceof Number) {
if (val instanceof Float || val instanceof Double) {
return new DoubleExprEval((Number) val);
}
return new LongExprEval((Number) val);
}
if (val instanceof Long[]) {
return new LongArrayExprEval((Long[]) val);
}
if (val instanceof Double[]) {
return new DoubleArrayExprEval((Double[]) val);
}
if (val instanceof Float[]) {
return new DoubleArrayExprEval(Arrays.stream((Float[]) val).map(Float::doubleValue).toArray(Double[]::new));
}
if (val instanceof String[]) {
return new StringArrayExprEval((String[]) val);
}
return new StringExprEval(val == null ? null : String.valueOf(val));
}
// Cached String values
private boolean stringValueCached = false;
@Nullable
private String stringValue;
@Nullable
final T value;
private ExprEval(@Nullable T value)
{
this.value = value;
}
public abstract ExprType type();
@Nullable
public T value()
{
return value;
}
void cacheStringValue(@Nullable String value)
{
stringValue = value;
stringValueCached = true;
}
@Nullable
String getCachedStringValue()
{
assert stringValueCached;
return stringValue;
}
boolean isStringValueCached()
{
return stringValueCached;
}
@Nullable
public String asString()
{
if (!stringValueCached) {
if (value == null) {
stringValue = null;
} else {
stringValue = String.valueOf(value);
}
stringValueCached = true;
}
return stringValue;
}
/**
* returns true if numeric primitive value for this ExprEval is null, otherwise false.
*/
public abstract boolean isNumericNull();
public boolean isArray()
{
return false;
}
public abstract int asInt();
public abstract long asLong();
public abstract double asDouble();
public abstract boolean asBoolean();
@Nullable
public abstract Object[] asArray();
@Nullable
public abstract String[] asStringArray();
@Nullable
public abstract Long[] asLongArray();
@Nullable
public abstract Double[] asDoubleArray();
public abstract ExprEval castTo(ExprType castTo);
public abstract Expr toExpr();
private abstract static class NumericExprEval extends ExprEval<Number>
{
private NumericExprEval(@Nullable Number value)
{
super(value);
}
@Override
public final int asInt()
{
return value.intValue();
}
@Override
public final long asLong()
{
return value.longValue();
}
@Override
public final double asDouble()
{
return value.doubleValue();
}
@Nullable
@Override
public String[] asStringArray()
{
return isNumericNull() ? null : new String[] {value.toString()};
}
@Nullable
@Override
public Long[] asLongArray()
{
return isNumericNull() ? null : new Long[] {value.longValue()};
}
@Nullable
@Override
public Double[] asDoubleArray()
{
return isNumericNull() ? null : new Double[] {value.doubleValue()};
}
@Override
public boolean isNumericNull()
{
return value == null;
}
}
private static class DoubleExprEval extends NumericExprEval
{
private DoubleExprEval(@Nullable Number value)
{
super(value == null ? NullHandling.defaultDoubleValue() : (Double) value.doubleValue());
}
@Override
public final ExprType type()
{
return ExprType.DOUBLE;
}
@Override
public final boolean asBoolean()
{
return Evals.asBoolean(asDouble());
}
@Nullable
@Override
public Object[] asArray()
{
return asDoubleArray();
}
@Override
public final ExprEval castTo(ExprType castTo)
{
switch (castTo) {
case DOUBLE:
return this;
case LONG:
if (value == null) {
return ExprEval.ofLong(null);
} else {
return ExprEval.of(asLong());
}
case STRING:
return ExprEval.of(asString());
case DOUBLE_ARRAY:
return ExprEval.ofDoubleArray(asDoubleArray());
case LONG_ARRAY:
return ExprEval.ofLongArray(asLongArray());
case STRING_ARRAY:
return ExprEval.ofStringArray(asStringArray());
}
throw new IAE("invalid type " + castTo);
}
@Override
public Expr toExpr()
{
if (isNumericNull()) {
return new NullDoubleExpr();
}
return new DoubleExpr(value.doubleValue());
}
}
private static class LongExprEval extends NumericExprEval
{
private LongExprEval(@Nullable Number value)
{
super(value == null ? NullHandling.defaultLongValue() : (Long) value.longValue());
}
@Override
public final ExprType type()
{
return ExprType.LONG;
}
@Override
public final boolean asBoolean()
{
return Evals.asBoolean(asLong());
}
@Nullable
@Override
public Object[] asArray()
{
return asLongArray();
}
@Nullable
@Override
public Long[] asLongArray()
{
return isNumericNull() ? null : new Long[]{value.longValue()};
}
@Override
public final ExprEval castTo(ExprType castTo)
{
switch (castTo) {
case DOUBLE:
if (value == null) {
return ExprEval.ofDouble(null);
} else {
return ExprEval.of(asDouble());
}
case LONG:
return this;
case STRING:
return ExprEval.of(asString());
case DOUBLE_ARRAY:
return ExprEval.ofDoubleArray(asDoubleArray());
case LONG_ARRAY:
return ExprEval.ofLongArray(asLongArray());
case STRING_ARRAY:
return ExprEval.ofStringArray(asStringArray());
}
throw new IAE("invalid type " + castTo);
}
@Override
public Expr toExpr()
{
if (isNumericNull()) {
return new NullLongExpr();
}
return new LongExpr(value.longValue());
}
}
private static class StringExprEval extends ExprEval<String>
{
// Cached primitive values.
private boolean intValueValid = false;
private boolean longValueValid = false;
private boolean doubleValueValid = false;
private boolean booleanValueValid = false;
private int intValue;
private long longValue;
private double doubleValue;
private boolean booleanValue;
private static final StringExprEval OF_NULL = new StringExprEval(null);
@Nullable
private Number numericVal;
private StringExprEval(@Nullable String value)
{
super(NullHandling.emptyToNullIfNeeded(value));
}
@Override
public final ExprType type()
{
return ExprType.STRING;
}
@Override
public int asInt()
{
if (!intValueValid) {
intValue = computeInt();
intValueValid = true;
}
return intValue;
}
@Override
public long asLong()
{
if (!longValueValid) {
longValue = computeLong();
longValueValid = true;
}
return longValue;
}
@Override
public double asDouble()
{
if (!doubleValueValid) {
doubleValue = computeDouble();
doubleValueValid = true;
}
return doubleValue;
}
@Nullable
@Override
public String asString()
{
return value;
}
@Nullable
@Override
public Object[] asArray()
{
return asStringArray();
}
private int computeInt()
{
Number number = computeNumber();
if (number == null) {
assert NullHandling.replaceWithDefault();
return 0;
}
return number.intValue();
}
private long computeLong()
{
Number number = computeNumber();
if (number == null) {
assert NullHandling.replaceWithDefault();
return 0L;
}
return number.longValue();
}
private double computeDouble()
{
Number number = computeNumber();
if (number == null) {
assert NullHandling.replaceWithDefault();
return 0.0d;
}
return number.doubleValue();
}
@Nullable
private Number computeNumber()
{
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;
}
@Override
public boolean isNumericNull()
{
return computeNumber() == null;
}
@Override
public final boolean asBoolean()
{
if (!booleanValueValid) {
booleanValue = Evals.asBoolean(value);
booleanValueValid = true;
}
return booleanValue;
}
@Nullable
@Override
public String[] asStringArray()
{
return value == null ? null : new String[] {value};
}
@Nullable
@Override
public Long[] asLongArray()
{
return value == null ? null : new Long[] {computeLong()};
}
@Nullable
@Override
public Double[] asDoubleArray()
{
return value == null ? null : new Double[] {computeDouble()};
}
@Override
public final ExprEval castTo(ExprType castTo)
{
switch (castTo) {
case DOUBLE:
return ExprEval.ofDouble(computeNumber());
case LONG:
return ExprEval.ofLong(computeNumber());
case STRING:
return this;
case DOUBLE_ARRAY:
return ExprEval.ofDoubleArray(asDoubleArray());
case LONG_ARRAY:
return ExprEval.ofLongArray(asLongArray());
case STRING_ARRAY:
return ExprEval.ofStringArray(asStringArray());
}
throw new IAE("invalid type " + castTo);
}
@Override
public Expr toExpr()
{
return new StringExpr(value);
}
}
abstract static class ArrayExprEval<T> extends ExprEval<T[]>
{
private ArrayExprEval(@Nullable T[] value)
{
super(value);
}
@Override
@Nullable
public String asString()
{
if (!isStringValueCached()) {
if (value == null) {
cacheStringValue(null);
} else {
cacheStringValue(Arrays.toString(value));
}
}
return getCachedStringValue();
}
@Override
public boolean isNumericNull()
{
return false;
}
@Override
public boolean isArray()
{
return true;
}
@Override
public int asInt()
{
return 0;
}
@Override
public long asLong()
{
return 0;
}
@Override
public double asDouble()
{
return 0;
}
@Override
public boolean asBoolean()
{
return false;
}
@Nullable
@Override
public T[] asArray()
{
return value;
}
@Nullable
public T getIndex(int index)
{
return value == null ? null : value[index];
}
}
private static class LongArrayExprEval extends ArrayExprEval<Long>
{
private LongArrayExprEval(@Nullable Long[] value)
{
super(value);
}
@Override
public ExprType type()
{
return ExprType.LONG_ARRAY;
}
@Nullable
@Override
public String[] asStringArray()
{
return value == null ? null : Arrays.stream(value).map(x -> x != null ? x.toString() : null).toArray(String[]::new);
}
@Nullable
@Override
public Long[] asLongArray()
{
return value;
}
@Nullable
@Override
public Double[] asDoubleArray()
{
return value == null ? null : Arrays.stream(value).map(Long::doubleValue).toArray(Double[]::new);
}
@Override
public ExprEval castTo(ExprType castTo)
{
if (value == null) {
return StringExprEval.OF_NULL;
}
switch (castTo) {
case LONG_ARRAY:
return this;
case DOUBLE_ARRAY:
return ExprEval.ofDoubleArray(asDoubleArray());
case STRING_ARRAY:
return ExprEval.ofStringArray(asStringArray());
}
throw new IAE("invalid type " + castTo);
}
@Override
public Expr toExpr()
{
return new LongArrayExpr(value);
}
}
private static class DoubleArrayExprEval extends ArrayExprEval<Double>
{
private DoubleArrayExprEval(@Nullable Double[] value)
{
super(value);
}
@Override
public ExprType type()
{
return ExprType.DOUBLE_ARRAY;
}
@Nullable
@Override
public String[] asStringArray()
{
return value == null ? null : Arrays.stream(value).map(x -> x != null ? x.toString() : null).toArray(String[]::new);
}
@Nullable
@Override
public Long[] asLongArray()
{
return value == null ? null : Arrays.stream(value).map(Double::longValue).toArray(Long[]::new);
}
@Nullable
@Override
public Double[] asDoubleArray()
{
return value;
}
@Override
public ExprEval castTo(ExprType castTo)
{
if (value == null) {
return StringExprEval.OF_NULL;
}
switch (castTo) {
case LONG_ARRAY:
return ExprEval.ofLongArray(asLongArray());
case DOUBLE_ARRAY:
return this;
case STRING_ARRAY:
return ExprEval.ofStringArray(asStringArray());
}
throw new IAE("invalid type " + castTo);
}
@Override
public Expr toExpr()
{
return new DoubleArrayExpr(value);
}
}
private static class StringArrayExprEval extends ArrayExprEval<String>
{
private boolean longValueValid = false;
private boolean doubleValueValid = false;
private Long[] longValues;
private Double[] doubleValues;
private StringArrayExprEval(@Nullable String[] value)
{
super(value);
}
@Override
public ExprType type()
{
return ExprType.STRING_ARRAY;
}
@Nullable
@Override
public String[] asStringArray()
{
return value;
}
@Nullable
@Override
public Long[] asLongArray()
{
if (!longValueValid) {
longValues = computeLongs();
longValueValid = true;
}
return longValues;
}
@Nullable
@Override
public Double[] asDoubleArray()
{
if (!doubleValueValid) {
doubleValues = computeDoubles();
doubleValueValid = true;
}
return doubleValues;
}
@Override
public ExprEval castTo(ExprType castTo)
{
if (value == null) {
return StringExprEval.OF_NULL;
}
switch (castTo) {
case STRING_ARRAY:
return this;
case LONG_ARRAY:
return ExprEval.ofLongArray(asLongArray());
case DOUBLE_ARRAY:
return ExprEval.ofDoubleArray(asDoubleArray());
}
throw new IAE("invalid type " + castTo);
}
@Override
public Expr toExpr()
{
return new StringArrayExpr(value);
}
@Nullable
private Long[] computeLongs()
{
if (value == null) {
return null;
}
return Arrays.stream(value).map(value -> {
Long lv = GuavaUtils.tryParseLong(value);
if (lv == null) {
Double d = Doubles.tryParse(value);
if (d != null) {
lv = d.longValue();
}
}
return lv;
}).toArray(Long[]::new);
}
@Nullable
private Double[] computeDoubles()
{
if (value == null) {
return null;
}
return Arrays.stream(value).map(Doubles::tryParse).toArray(Double[]::new);
}
}
}