Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.apache.druid.math.expr.ExprEval Maven / Gradle / Ivy
Go to download
A module that is everything required to understands Druid Segments
/*
* 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.base.Preconditions;
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 org.apache.druid.java.util.common.ISE;
import org.apache.druid.java.util.common.NonnullPair;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.segment.column.NullableTypeStrategy;
import org.apache.druid.segment.column.TypeStrategies;
import org.apache.druid.segment.column.TypeStrategy;
import org.apache.druid.segment.column.Types;
import org.apache.druid.segment.nested.StructuredData;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Generic result holder for evaluated {@link Expr} containing the value and {@link ExprType} of the value to allow
*/
public abstract class ExprEval
{
/**
* Deserialize an expression stored in a bytebuffer, e.g. for an agg.
*
* This method is not thread-safe with respect to the provided {@link ByteBuffer}, because the position of the
* buffer may be changed transiently during execution of this method. However, it will be restored to its original
* position prior to the method completing. Therefore, if the provided buffer is being used by a single thread, then
* this method does not change the position of the buffer.
*
* The {@code canRetainBufferReference} parameter determines
*
* @param buffer source buffer
* @param offset position to start reading from
* @param maxSize maximum number of bytes from "offset" that may be required. This is used as advice,
* but is not strictly enforced in all cases. It is possible that type strategies may
* attempt reads past this limit.
* @param type data type to read
* @param canRetainBufferReference whether the returned {@link ExprEval} may retain a reference to the provided
* {@link ByteBuffer}. Certain types are deserialized more efficiently if allowed
* to retain references to the provided buffer.
*/
public static ExprEval deserialize(
final ByteBuffer buffer,
final int offset,
final int maxSize,
final ExpressionType type,
final boolean canRetainBufferReference
)
{
switch (type.getType()) {
case LONG:
if (TypeStrategies.isNullableNull(buffer, offset)) {
return ofLong(null);
}
return of(TypeStrategies.readNotNullNullableLong(buffer, offset));
case DOUBLE:
if (TypeStrategies.isNullableNull(buffer, offset)) {
return ofDouble(null);
}
return of(TypeStrategies.readNotNullNullableDouble(buffer, offset));
default:
final NullableTypeStrategy strategy = type.getNullableStrategy();
if (!canRetainBufferReference && strategy.readRetainsBufferReference()) {
final ByteBuffer dataCopyBuffer = ByteBuffer.allocate(maxSize);
final ByteBuffer mutationBuffer = buffer.duplicate();
mutationBuffer.limit(offset + maxSize);
mutationBuffer.position(offset);
dataCopyBuffer.put(mutationBuffer);
dataCopyBuffer.rewind();
return ofType(type, strategy.read(dataCopyBuffer, 0));
} else {
return ofType(type, strategy.read(buffer, offset));
}
}
}
/**
* Write an expression result to a bytebuffer, throwing an {@link ISE} if the data exceeds a maximum size. Primitive
* numeric types are not validated to be lower than max size, so it is expected to be at least 10 bytes. Callers
* of this method should enforce this themselves (instead of doing it here, which might be done every row)
*
* This should be refactored to be consolidated with some of the standard type handling of aggregators probably
*/
public static void serialize(ByteBuffer buffer, int position, ExpressionType type, ExprEval> eval, int maxSizeBytes)
{
int offset = position;
switch (type.getType()) {
case LONG:
if (eval.value() == null) {
TypeStrategies.writeNull(buffer, offset);
} else {
TypeStrategies.writeNotNullNullableLong(buffer, offset, eval.asLong());
}
break;
case DOUBLE:
if (eval.value() == null) {
TypeStrategies.writeNull(buffer, offset);
} else {
TypeStrategies.writeNotNullNullableDouble(buffer, offset, eval.asDouble());
}
break;
default:
final NullableTypeStrategy strategy = type.getNullableStrategy();
// if the types don't match, cast it so things don't get weird
if (type.equals(eval.type())) {
eval = eval.castTo(type);
}
int written = strategy.write(buffer, offset, eval.value(), maxSizeBytes);
if (written < 0) {
throw new ISE(
"Unable to serialize [%s], max size bytes is [%s], but need at least [%s] bytes to write entire value",
type.asTypeString(),
maxSizeBytes,
maxSizeBytes - written
);
}
}
}
/**
* Converts a List to an appropriate array type, optionally doing some conversion to make multi-valued strings
* consistent across selector types, which are not consistent in treatment of null, [], and [null].
*
* If homogenizeMultiValueStrings is true, null and [] will be converted to [null], otherwise they will retain
*/
@Nullable
public static NonnullPair coerceListToArray(@Nullable List> val, boolean homogenizeMultiValueStrings)
{
// if value is not null and has at least 1 element, conversion is unambigous regardless of the selector
if (val != null && val.size() > 0) {
Class> coercedType = null;
for (Object elem : val) {
if (elem != null) {
coercedType = convertType(coercedType, elem.getClass());
}
}
if (coercedType == Long.class || coercedType == Integer.class) {
Object[] array = new Object[val.size()];
int i = 0;
for (Object o : val) {
array[i++] = o != null ? ExprEval.ofType(ExpressionType.LONG, o).value() : null;
}
return new NonnullPair<>(ExpressionType.LONG_ARRAY, array);
} else if (coercedType == Float.class || coercedType == Double.class) {
Object[] array = new Object[val.size()];
int i = 0;
for (Object o : val) {
array[i++] = o != null ? ExprEval.ofType(ExpressionType.DOUBLE, o).value() : null;
}
return new NonnullPair<>(ExpressionType.DOUBLE_ARRAY, array);
} else if (coercedType == Object.class) {
// object, fall back to "best effort"
ExprEval>[] evals = new ExprEval[val.size()];
Object[] array = new Object[val.size()];
int i = 0;
ExpressionType elementType = null;
for (Object o : val) {
if (o != null) {
ExprEval> eval = ExprEval.bestEffortOf(o);
elementType = ExpressionTypeConversion.leastRestrictiveType(elementType, eval.type());
evals[i++] = eval;
} else {
evals[i++] = null;
}
}
i = 0;
for (ExprEval> eval : evals) {
if (eval != null) {
array[i++] = eval.castTo(elementType).value();
} else {
array[i++] = ExprEval.ofType(elementType, null).value();
}
}
ExpressionType arrayType = elementType == null
? ExpressionType.STRING_ARRAY
: ExpressionTypeFactory.getInstance().ofArray(elementType);
return new NonnullPair<>(arrayType, array);
}
// default to string
Object[] array = new Object[val.size()];
int i = 0;
for (Object o : val) {
array[i++] = o != null ? ExprEval.ofType(ExpressionType.STRING, o).value() : null;
}
return new NonnullPair<>(ExpressionType.STRING_ARRAY, array);
}
if (homogenizeMultiValueStrings) {
return new NonnullPair<>(ExpressionType.STRING_ARRAY, new Object[]{null});
} else {
if (val != null) {
return new NonnullPair<>(ExpressionType.STRING_ARRAY, new Object[0]);
}
return null;
}
}
/**
* Find the common type to use between 2 types, useful for choosing the appropriate type for an array given a set
* of objects with unknown type, following rules similar to Java, our own native Expr, and SQL implicit type
* conversions. This is used to assist in preparing native java objects for {@link Expr.ObjectBinding} which will
* later be wrapped in {@link ExprEval} when evaluating {@link IdentifierExpr}.
*
* If any type is string, then the result will be string because everything can be converted to a string, but a string
* cannot be converted to everything.
*
* For numbers, integer is the most restrictive type, only chosen if both types are integers. Longs win over integers,
* floats over longs and integers, and doubles win over everything.
*/
private static Class convertType(@Nullable Class existing, Class next)
{
if (existing != null && existing.equals(Object.class)) {
return existing;
}
if (Number.class.isAssignableFrom(next) || next == String.class || next == Boolean.class) {
// coerce booleans
if (next == Boolean.class) {
if (ExpressionProcessing.useStrictBooleans()) {
next = Long.class;
} else {
next = String.class;
}
}
if (existing == null) {
return next;
}
// string wins everything
if (existing == String.class) {
return existing;
}
if (next == String.class) {
return next;
}
// all numbers win over Integer
if (existing == Integer.class) {
return next;
}
if (existing == Float.class) {
// doubles win over floats
if (next == Double.class) {
return next;
}
return existing;
}
if (existing == Long.class) {
if (next == Integer.class) {
// long beats int
return existing;
}
// double and float win over longs
return next;
}
// otherwise double
return Double.class;
}
// its complicated, call it object
return Object.class;
}
public static ExprEval of(long longValue)
{
return new LongExprEval(longValue);
}
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 ofLong(@Nullable Number longValue)
{
if (longValue == null) {
return LongExprEval.OF_NULL;
}
return new LongExprEval(longValue);
}
public static ExprEval ofDouble(@Nullable Number doubleValue)
{
if (doubleValue == null) {
return DoubleExprEval.OF_NULL;
}
return new DoubleExprEval(doubleValue);
}
public static ExprEval ofLongArray(@Nullable Object[] longValue)
{
if (longValue == null) {
return ArrayExprEval.OF_NULL_LONG;
}
return new ArrayExprEval(ExpressionType.LONG_ARRAY, longValue);
}
public static ExprEval ofDoubleArray(@Nullable Object[] doubleValue)
{
if (doubleValue == null) {
return ArrayExprEval.OF_NULL_DOUBLE;
}
return new ArrayExprEval(ExpressionType.DOUBLE_ARRAY, doubleValue);
}
public static ExprEval ofStringArray(@Nullable Object[] stringValue)
{
if (stringValue == null) {
return ArrayExprEval.OF_NULL_STRING;
}
return new ArrayExprEval(ExpressionType.STRING_ARRAY, stringValue);
}
public static ExprEval ofArray(ExpressionType outputType, @Nullable Object[] value)
{
Preconditions.checkArgument(outputType.isArray(), "Output type %s is not an array", outputType);
return new ArrayExprEval(outputType, value);
}
/**
* Convert a boolean back into native expression type
*
* Do not use this method unless {@link ExpressionProcessing#useStrictBooleans()} is set to false.
* {@link ExpressionType#LONG} is the Druid boolean unless this mode is enabled, so use {@link #ofLongBoolean}
* instead.
*/
@Deprecated
public static ExprEval ofBoolean(boolean value, ExpressionType type)
{
switch (type.getType()) {
case DOUBLE:
return ExprEval.of(Evals.asDouble(value));
case LONG:
return ofLongBoolean(value);
case STRING:
return ExprEval.of(String.valueOf(value));
default:
throw new Types.InvalidCastBooleanException(type);
}
}
/**
* Convert a boolean into a long expression type
*/
public static ExprEval ofLongBoolean(boolean value)
{
return value ? LongExprEval.TRUE : LongExprEval.FALSE;
}
public static ExprEval ofComplex(ExpressionType outputType, @Nullable Object value)
{
if (ExpressionType.NESTED_DATA.equals(outputType)) {
return new NestedDataExprEval(value);
}
return new ComplexExprEval(outputType, value);
}
public static ExprEval bestEffortArray(@Nullable List> theList)
{
// do not convert empty lists to arrays with a single null element here, because that should have been done
// by the selectors preparing their ObjectBindings if necessary. If we get to this point it was legitimately
// empty
NonnullPair coerced = coerceListToArray(theList, false);
if (coerced == null) {
return bestEffortOf(null);
}
return ofArray(coerced.lhs, coerced.rhs);
}
/**
* Examine java type to find most appropriate expression type
*/
public static ExprEval bestEffortOf(@Nullable Object val)
{
if (val == null) {
return new StringExprEval(null);
}
if (val instanceof ExprEval) {
return (ExprEval) val;
}
if (val instanceof String) {
return new StringExprEval((String) 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 Boolean) {
if (ExpressionProcessing.useStrictBooleans()) {
return ofLongBoolean((Boolean) val);
}
return new StringExprEval(String.valueOf(val));
}
if (val instanceof Long[]) {
final Long[] inputArray = (Long[]) val;
final Object[] array = new Object[inputArray.length];
for (int i = 0; i < inputArray.length; i++) {
array[i] = inputArray[i];
}
return new ArrayExprEval(ExpressionType.LONG_ARRAY, array);
}
if (val instanceof long[]) {
final long[] longArray = (long[]) val;
final Object[] array = new Object[longArray.length];
for (int i = 0; i < longArray.length; i++) {
array[i] = longArray[i];
}
return new ArrayExprEval(ExpressionType.LONG_ARRAY, array);
}
if (val instanceof Integer[]) {
final Integer[] inputArray = (Integer[]) val;
final Object[] array = new Object[inputArray.length];
for (int i = 0; i < inputArray.length; i++) {
array[i] = inputArray[i] != null ? inputArray[i].longValue() : null;
}
return new ArrayExprEval(ExpressionType.LONG_ARRAY, array);
}
if (val instanceof int[]) {
final int[] longArray = (int[]) val;
final Object[] array = new Object[longArray.length];
for (int i = 0; i < longArray.length; i++) {
array[i] = (long) longArray[i];
}
return new ArrayExprEval(ExpressionType.LONG_ARRAY, array);
}
if (val instanceof Double[]) {
final Double[] inputArray = (Double[]) val;
final Object[] array = new Object[inputArray.length];
for (int i = 0; i < inputArray.length; i++) {
array[i] = inputArray[i] != null ? inputArray[i] : null;
}
return new ArrayExprEval(ExpressionType.DOUBLE_ARRAY, array);
}
if (val instanceof double[]) {
final double[] inputArray = (double[]) val;
final Object[] array = new Object[inputArray.length];
for (int i = 0; i < inputArray.length; i++) {
array[i] = inputArray[i];
}
return new ArrayExprEval(ExpressionType.DOUBLE_ARRAY, array);
}
if (val instanceof Float[]) {
final Float[] inputArray = (Float[]) val;
final Object[] array = new Object[inputArray.length];
for (int i = 0; i < inputArray.length; i++) {
array[i] = inputArray[i] != null ? inputArray[i].doubleValue() : null;
}
return new ArrayExprEval(ExpressionType.DOUBLE_ARRAY, array);
}
if (val instanceof float[]) {
final float[] inputArray = (float[]) val;
final Object[] array = new Object[inputArray.length];
for (int i = 0; i < inputArray.length; i++) {
array[i] = (double) inputArray[i];
}
return new ArrayExprEval(ExpressionType.DOUBLE_ARRAY, array);
}
if (val instanceof String[]) {
final String[] inputArray = (String[]) val;
final Object[] array = new Object[inputArray.length];
for (int i = 0; i < inputArray.length; i++) {
array[i] = inputArray[i];
}
return new ArrayExprEval(ExpressionType.STRING_ARRAY, array);
}
if (val instanceof List || val instanceof Object[]) {
final List> theList = val instanceof List ? ((List>) val) : Arrays.asList((Object[]) val);
return bestEffortArray(theList);
}
// in 'best effort' mode, we couldn't possibly use byte[] as a complex or anything else useful without type
// knowledge, so lets turn it into a base64 encoded string so at least something downstream can use it by decoding
// back into bytes
if (val instanceof byte[]) {
return new StringExprEval(StringUtils.encodeBase64String((byte[]) val));
}
if (val instanceof Map) {
return ofComplex(ExpressionType.NESTED_DATA, val);
}
// is this cool?
return ofComplex(ExpressionType.UNKNOWN_COMPLEX, val);
}
public static ExprEval ofType(@Nullable ExpressionType type, @Nullable Object value)
{
if (type == null) {
return bestEffortOf(value);
}
switch (type.getType()) {
case STRING:
// not all who claim to be "STRING" are always a String, prepare ourselves...
if (value instanceof String[]) {
final String[] inputArray = (String[]) value;
final Object[] array = new Object[inputArray.length];
for (int i = 0; i < inputArray.length; i++) {
array[i] = inputArray[i];
}
return new ArrayExprEval(ExpressionType.STRING_ARRAY, array);
}
if (value instanceof Object[]) {
return bestEffortOf(value);
}
if (value instanceof List) {
return bestEffortOf(value);
}
if (value instanceof byte[]) {
return new StringExprEval(StringUtils.encodeBase64String((byte[]) value));
}
return of(Evals.asString(value));
case LONG:
if (value instanceof Number) {
return ofLong((Number) value);
}
if (value instanceof Boolean) {
return ofLongBoolean((Boolean) value);
}
if (value instanceof String) {
return ofLong(ExprEval.computeNumber((String) value));
}
return ofLong(null);
case DOUBLE:
if (value instanceof Number) {
return ofDouble((Number) value);
}
if (value instanceof Boolean) {
return ofDouble(Evals.asDouble((Boolean) value));
}
if (value instanceof String) {
return ofDouble(ExprEval.computeNumber((String) value));
}
return ofDouble(null);
case COMPLEX:
if (ExpressionType.NESTED_DATA.equals(type)) {
return ofComplex(type, StructuredData.unwrap(value));
}
byte[] bytes = null;
if (value instanceof String) {
try {
bytes = StringUtils.decodeBase64String((String) value);
}
catch (IllegalArgumentException ignored) {
}
} else if (value instanceof byte[]) {
bytes = (byte[]) value;
}
if (bytes != null) {
TypeStrategy> strategy = type.getStrategy();
ByteBuffer bb = ByteBuffer.wrap(bytes);
return ofComplex(type, strategy.read(bb));
}
return ofComplex(type, value);
case ARRAY:
ExpressionType elementType = (ExpressionType) type.getElementType();
if (value == null) {
return ofArray(type, null);
}
if (value instanceof List) {
List> theList = (List>) value;
Object[] array = new Object[theList.size()];
int i = 0;
for (Object o : theList) {
array[i++] = ExprEval.ofType(elementType, o).value();
}
return ofArray(type, array);
}
if (value instanceof Object[]) {
Object[] inputArray = (Object[]) value;
Object[] array = new Object[inputArray.length];
int i = 0;
for (Object o : inputArray) {
array[i++] = ExprEval.ofType(elementType, o).value();
}
return ofArray(type, array);
}
// in a better world, we might get an object that matches the type signature for arrays and could do a switch
// statement here, but this is not that world yet, and things that are array typed might also be non-arrays,
// e.g. we might get a String instead of String[], so just fallback to bestEffortOf
return bestEffortOf(value).castTo(type);
}
throw new IAE("Cannot create type [%s]", type);
}
@Nullable
public static Number computeNumber(@Nullable String value)
{
if (value == null) {
return null;
}
if (Evals.asBoolean(value)) {
return 1.0;
}
if (value.equalsIgnoreCase("false")) {
return 0.0;
}
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);
}
return rv;
}
/**
* Cast an {@link ExprEval} to some {@link ExpressionType} that the value will be compared with. If the value is not
* appropriate to use for comparison after casting, this method returns null. For example, the
* {@link ExpressionType#DOUBLE} value 1.1 when cast to {@link ExpressionType#LONG} becomes 1L, which is no longer
* appropriate to use for value equality comparisons, while 1.0 is valid.
*/
@Nullable
public static ExprEval> castForEqualityComparison(ExprEval> valueToCompare, ExpressionType typeToCompareWith)
{
if (valueToCompare.isArray() && !typeToCompareWith.isArray()) {
final Object[] array = valueToCompare.asArray();
// cannot cast array to scalar if array length is greater than 1
if (array != null && array.length != 1) {
return null;
}
}
ExprEval> cast = valueToCompare.castTo(typeToCompareWith);
if (ExpressionType.LONG.equals(typeToCompareWith) && valueToCompare.asDouble() != cast.asDouble()) {
// make sure the DOUBLE value when cast to LONG is the same before and after the cast
// this lets us match 1.0 to 1, but not 1.1
return null;
} else if (ExpressionType.LONG_ARRAY.equals(typeToCompareWith)) {
// if comparison array is double typed, make sure the values are the same when cast to long
// this lets us match [1.0, 2.0, 3.0] to [1, 2, 3], but not [1.1, 2.2, 3.3]
final ExprEval> doubleCast = valueToCompare.castTo(ExpressionType.DOUBLE_ARRAY);
final ExprEval> castDoubleCast = cast.castTo(ExpressionType.DOUBLE_ARRAY);
if (ExpressionType.DOUBLE_ARRAY.getStrategy().compare(doubleCast.value(), castDoubleCast.value()) != 0) {
return null;
}
}
// did value become null during cast but was not initially null?
if (valueToCompare.value() != null && cast.value() == null) {
return null;
}
return cast;
}
// 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 ExpressionType type();
public ExpressionType elementType()
{
return type().isArray() ? (ExpressionType) type().getElementType() : type();
}
public ExpressionType asArrayType()
{
return type().isArray() ? type() : ExpressionTypeFactory.getInstance().ofArray(type());
}
@Nullable
public T value()
{
return value;
}
@Nullable
public T valueOrDefault()
{
return value;
}
void cacheStringValue(@Nullable String value)
{
stringValue = value;
stringValueCached = true;
}
@Nullable
String getCachedStringValue()
{
return stringValue;
}
boolean isStringValueCached()
{
return stringValueCached;
}
@Nullable
public String asString()
{
if (!stringValueCached) {
stringValue = Evals.asString(value);
stringValueCached = true;
}
return stringValue;
}
/**
* The method returns true if numeric primitive value for this {@link ExprEval} is null, otherwise false.
*
* If this method returns false, then the values returned by {@link #asLong()}, {@link #asDouble()},
* and {@link #asInt()} are "valid", since this method is primarily used during {@link Expr} evaluation to decide
* if primitive numbers can be fetched to use.
*
* If a type cannot sanely convert into a primitive numeric value, then this method should always return true so that
* these primitive numeric getters are not called, since returning false is assumed to mean these values are valid.
*
* Note that all types must still return values for {@link #asInt()}, {@link #asLong()}}, and {@link #asDouble()},
* since this can still happen if {@link NullHandling#sqlCompatible()} is false, but it should be assumed that this
* can only happen in that mode and 0s are typical and expected for values that would otherwise be null.
*/
public abstract boolean isNumericNull();
public boolean isArray()
{
return false;
}
/**
* Get the primitive integer value. Callers should check {@link #isNumericNull()} prior to calling this method,
* otherwise it may improperly return placeholder a value (typically zero, which is expected if
* {@link NullHandling#sqlCompatible()} is false)
*/
public abstract int asInt();
/**
* Get the primitive long value. Callers should check {@link #isNumericNull()} prior to calling this method,
* otherwise it may improperly return a placeholder value (typically zero, which is expected if
* {@link NullHandling#sqlCompatible()} is false)
*/
public abstract long asLong();
/**
* Get the primitive double value. Callers should check {@link #isNumericNull()} prior to calling this method,
* otherwise it may improperly return a placeholder value (typically zero, which is expected if
* {@link NullHandling#sqlCompatible()} is false)
*/
public abstract double asDouble();
public abstract boolean asBoolean();
@Nullable
public abstract Object[] asArray();
public abstract ExprEval castTo(ExpressionType castTo);
public abstract Expr toExpr();
private abstract static class NumericExprEval extends ExprEval
{
private NumericExprEval(@Nullable Number value)
{
super(value);
}
@Override
public final int asInt()
{
if (value == null) {
return 0;
}
return value.intValue();
}
@Override
public final long asLong()
{
if (value == null) {
return 0L;
}
return value.longValue();
}
@Override
public final double asDouble()
{
if (value == null) {
return 0.0;
}
return value.doubleValue();
}
@Override
public boolean isNumericNull()
{
return NullHandling.sqlCompatible() && value == null;
}
}
private static class DoubleExprEval extends NumericExprEval
{
private static final DoubleExprEval OF_NULL = new DoubleExprEval(null);
private DoubleExprEval(@Nullable Number value)
{
super(value == null ? null : value.doubleValue());
}
@Override
public final ExpressionType type()
{
return ExpressionType.DOUBLE;
}
@Override
public Number valueOrDefault()
{
if (value == null) {
return NullHandling.defaultDoubleValue();
}
return value.doubleValue();
}
@Override
public final boolean asBoolean()
{
return Evals.asBoolean(asDouble());
}
@Nullable
@Override
public Object[] asArray()
{
return value == null ? null : new Object[]{valueOrDefault().doubleValue()};
}
@Override
public final ExprEval castTo(ExpressionType castTo)
{
switch (castTo.getType()) {
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 ARRAY:
switch (castTo.getElementType().getType()) {
case DOUBLE:
return ExprEval.ofDoubleArray(asArray());
case LONG:
return ExprEval.ofLongArray(value == null ? null : new Object[]{value.longValue()});
case STRING:
return ExprEval.ofStringArray(value == null ? null : new Object[]{value.toString()});
default:
ExpressionType elementType = (ExpressionType) castTo.getElementType();
return new ArrayExprEval(castTo, new Object[]{castTo(elementType).value()});
}
case COMPLEX:
if (ExpressionType.NESTED_DATA.equals(castTo)) {
return new NestedDataExprEval(value);
}
}
throw invalidCast(type(), castTo);
}
@Override
public Expr toExpr()
{
if (value == null) {
return new NullDoubleExpr();
}
return new DoubleExpr(value.doubleValue());
}
}
private static class LongExprEval extends NumericExprEval
{
private static final LongExprEval TRUE = new LongExprEval(Evals.asLong(true));
private static final LongExprEval FALSE = new LongExprEval(Evals.asLong(false));
private static final LongExprEval OF_NULL = new LongExprEval(null);
private LongExprEval(@Nullable Number value)
{
super(value == null ? null : value.longValue());
}
@Override
public final ExpressionType type()
{
return ExpressionType.LONG;
}
@Override
public Number valueOrDefault()
{
if (value == null) {
return NullHandling.defaultLongValue();
}
return value.longValue();
}
@Override
public final boolean asBoolean()
{
return Evals.asBoolean(asLong());
}
@Nullable
@Override
public Object[] asArray()
{
return value == null ? null : new Object[]{valueOrDefault().longValue()};
}
@Override
public final ExprEval castTo(ExpressionType castTo)
{
switch (castTo.getType()) {
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 ARRAY:
if (value == null) {
return new ArrayExprEval(castTo, null);
}
switch (castTo.getElementType().getType()) {
case DOUBLE:
return ExprEval.ofDoubleArray(new Object[]{value.doubleValue()});
case LONG:
return ExprEval.ofLongArray(asArray());
case STRING:
return ExprEval.ofStringArray(new Object[]{value.toString()});
default:
ExpressionType elementType = (ExpressionType) castTo.getElementType();
return new ArrayExprEval(castTo, new Object[]{castTo(elementType).value()});
}
case COMPLEX:
if (ExpressionType.NESTED_DATA.equals(castTo)) {
return new NestedDataExprEval(value);
}
}
throw invalidCast(type(), castTo);
}
@Override
public Expr toExpr()
{
if (value == null) {
return new NullLongExpr();
}
return new LongExpr(value.longValue());
}
}
private static class StringExprEval extends ExprEval
{
private static final StringExprEval OF_NULL = new StringExprEval(null);
// 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;
@Nullable
private Number numericVal;
private StringExprEval(@Nullable String value)
{
super(NullHandling.emptyToNullIfNeeded(value));
}
@Override
public final ExpressionType type()
{
return ExpressionType.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 value == null ? null : new Object[]{value};
}
private int computeInt()
{
Number number = computeNumber();
if (number == null) {
return 0;
}
return number.intValue();
}
private long computeLong()
{
Number number = computeNumber();
if (number == null) {
return 0L;
}
return number.longValue();
}
private double computeDouble()
{
Number number = computeNumber();
if (number == null) {
return 0.0d;
}
return number.doubleValue();
}
@Nullable
Number computeNumber()
{
if (value == null) {
return null;
}
if (numericVal != null) {
// Optimization for non-null case.
return numericVal;
}
numericVal = computeNumber(value);
return numericVal;
}
@Override
public boolean isNumericNull()
{
return computeNumber() == null;
}
@Override
public final boolean asBoolean()
{
if (!booleanValueValid) {
booleanValue = Evals.asBoolean(value);
booleanValueValid = true;
}
return booleanValue;
}
@Override
public final ExprEval castTo(ExpressionType castTo)
{
switch (castTo.getType()) {
case DOUBLE:
return ExprEval.ofDouble(computeNumber());
case LONG:
return ExprEval.ofLong(computeNumber());
case STRING:
return this;
case ARRAY:
if (value == null) {
return new ArrayExprEval(castTo, null);
}
final Number number = computeNumber();
switch (castTo.getElementType().getType()) {
case DOUBLE:
return ExprEval.ofDoubleArray(
new Object[]{number == null ? null : number.doubleValue()}
);
case LONG:
return ExprEval.ofLongArray(
new Object[]{number == null ? null : number.longValue()}
);
case STRING:
return ExprEval.ofStringArray(new Object[]{value});
default:
ExpressionType elementType = (ExpressionType) castTo.getElementType();
return new ArrayExprEval(castTo, new Object[]{castTo(elementType).value()});
}
case COMPLEX:
if (ExpressionType.NESTED_DATA.equals(castTo)) {
return new NestedDataExprEval(value);
}
}
throw invalidCast(type(), castTo);
}
@Override
public Expr toExpr()
{
return new StringExpr(value);
}
}
static class ArrayExprEval extends ExprEval
{
public static final ExprEval OF_NULL_LONG = new ArrayExprEval(ExpressionType.LONG_ARRAY, null);
public static final ExprEval OF_NULL_DOUBLE = new ArrayExprEval(ExpressionType.DOUBLE_ARRAY, null);
public static final ExprEval OF_NULL_STRING = new ArrayExprEval(ExpressionType.STRING_ARRAY, null);
private final ExpressionType arrayType;
private ArrayExprEval(ExpressionType arrayType, @Nullable Object[] value)
{
super(value);
this.arrayType = arrayType;
Preconditions.checkArgument(arrayType.isArray(), "Output type %s is not an array", arrayType);
}
@Override
public ExpressionType type()
{
return arrayType;
}
@Override
@Nullable
public String asString()
{
if (!isStringValueCached()) {
if (value == null) {
cacheStringValue(null);
} else if (value.length == 1) {
cacheStringValue(Evals.asString(value[0]));
} else {
cacheStringValue(Arrays.deepToString(value));
}
}
return getCachedStringValue();
}
@Override
public boolean isNumericNull()
{
if (isScalar()) {
if (arrayType.getElementType().is(ExprType.STRING)) {
Number n = computeNumber((String) getScalarValue());
return n == null;
}
return getScalarValue() == null;
}
return true;
}
@Override
public boolean isArray()
{
return true;
}
@Override
public int asInt()
{
if (isScalar()) {
Number scalar = null;
if (arrayType.getElementType().isNumeric()) {
scalar = (Number) getScalarValue();
} else if (arrayType.getElementType().is(ExprType.STRING)) {
scalar = computeNumber((String) getScalarValue());
}
if (scalar == null) {
return 0;
}
return scalar.intValue();
}
return 0;
}
@Override
public long asLong()
{
if (isScalar()) {
Number scalar = null;
if (arrayType.getElementType().isNumeric()) {
scalar = (Number) getScalarValue();
} else if (arrayType.getElementType().is(ExprType.STRING)) {
scalar = computeNumber((String) getScalarValue());
}
if (scalar == null) {
return 0;
}
return scalar.longValue();
}
return 0L;
}
@Override
public double asDouble()
{
if (isScalar()) {
Number scalar = null;
if (arrayType.getElementType().isNumeric()) {
scalar = (Number) getScalarValue();
} else if (arrayType.getElementType().is(ExprType.STRING)) {
scalar = computeNumber((String) getScalarValue());
}
if (scalar == null) {
return 0.0;
}
return scalar.doubleValue();
}
return 0.0;
}
@Override
public boolean asBoolean()
{
if (isScalar()) {
if (arrayType.getElementType().isNumeric()) {
Number scalarValue = (Number) getScalarValue();
if (scalarValue == null) {
return false;
}
return Evals.asBoolean(scalarValue.longValue());
}
if (arrayType.getElementType().is(ExprType.STRING)) {
return Evals.asBoolean((String) getScalarValue());
}
}
return false;
}
@Nullable
@Override
public Object[] asArray()
{
return value;
}
@Override
public ExprEval castTo(ExpressionType castTo)
{
if (value == null) {
if (castTo.isArray()) {
return new ArrayExprEval(castTo, null);
}
return ExprEval.ofType(castTo, null);
}
if (type().equals(castTo)) {
return this;
}
switch (castTo.getType()) {
case STRING:
if (value.length == 1) {
return ExprEval.of(asString());
}
break;
case LONG:
if (value.length == 1) {
return isNumericNull() ? ExprEval.ofLong(null) : ExprEval.ofLong(asLong());
}
break;
case DOUBLE:
if (value.length == 1) {
return isNumericNull() ? ExprEval.ofDouble(null) : ExprEval.ofDouble(asDouble());
}
break;
case ARRAY:
ExpressionType elementType = (ExpressionType) castTo.getElementType();
Object[] cast = new Object[value.length];
for (int i = 0; i < value.length; i++) {
cast[i] = ExprEval.ofType(elementType(), value[i]).castTo(elementType).value();
}
return ExprEval.ofArray(castTo, cast);
case COMPLEX:
if (ExpressionType.NESTED_DATA.equals(castTo)) {
return new NestedDataExprEval(value);
}
}
throw invalidCast(type(), castTo);
}
@Override
public Expr toExpr()
{
return new ArrayExpr(arrayType, value);
}
protected boolean isScalar()
{
return value != null && value.length == 1;
}
@Nullable
protected Object getScalarValue()
{
return value[0];
}
}
private static class ComplexExprEval extends ExprEval
{
private final ExpressionType expressionType;
private ComplexExprEval(ExpressionType expressionType, @Nullable Object value)
{
super(value);
this.expressionType = expressionType;
}
@Override
public ExpressionType type()
{
return expressionType;
}
@Override
public boolean isNumericNull()
{
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 Object[] asArray()
{
return null;
}
@Override
public ExprEval castTo(ExpressionType castTo)
{
if (expressionType.equals(castTo)) {
return this;
}
// allow cast of unknown complex to some other complex type
if (expressionType.getComplexTypeName() == null && castTo.getType().equals(ExprType.COMPLEX)) {
return ofComplex(castTo, value);
}
throw invalidCast(expressionType, castTo);
}
@Override
public Expr toExpr()
{
return new ComplexExpr(expressionType, value);
}
}
private static class NestedDataExprEval extends ExprEval
{
@Nullable
private Number number;
private boolean computedNumber = false;
private NestedDataExprEval(@Nullable Object value)
{
super(value);
}
@Override
public ExpressionType type()
{
return ExpressionType.NESTED_DATA;
}
@Override
public boolean isNumericNull()
{
computeNumber();
return number == null;
}
@Override
public int asInt()
{
computeNumber();
if (number != null) {
return number.intValue();
}
return 0;
}
@Override
public long asLong()
{
computeNumber();
if (number != null) {
return number.longValue();
}
return 0L;
}
@Override
public double asDouble()
{
computeNumber();
if (number != null) {
return number.doubleValue();
}
return 0.0;
}
@Override
public boolean asBoolean()
{
Object val = StructuredData.unwrap(value);
if (val != null) {
return Evals.objectAsBoolean(val);
}
return false;
}
private void computeNumber()
{
if (!computedNumber && value != null) {
computedNumber = true;
Object val = StructuredData.unwrap(value);
if (val instanceof Number) {
number = (Number) val;
} else if (val instanceof Boolean) {
number = Evals.asLong((Boolean) val);
} else if (val instanceof String) {
number = ExprEval.computeNumber((String) val);
}
}
}
@Nullable
@Override
public Object[] asArray()
{
Object val = StructuredData.unwrap(value);
ExprEval maybeArray = ExprEval.bestEffortOf(val);
if (maybeArray.type().isPrimitive() || maybeArray.isArray()) {
return maybeArray.asArray();
}
return null;
}
@Override
public ExprEval castTo(ExpressionType castTo)
{
if (ExpressionType.NESTED_DATA.equals(castTo)) {
return this;
}
Object val = StructuredData.unwrap(value);
ExprEval bestEffortOf = ExprEval.bestEffortOf(val);
if (bestEffortOf.type().isPrimitive() || bestEffortOf.type().isArray()) {
return bestEffortOf.castTo(castTo);
}
throw invalidCast(ExpressionType.NESTED_DATA, castTo);
}
@Override
public Expr toExpr()
{
return new ComplexExpr(ExpressionType.NESTED_DATA, value);
}
}
public static Types.InvalidCastException invalidCast(ExpressionType fromType, ExpressionType toType)
{
return new Types.InvalidCastException(fromType, toType);
}
}