blob: 3436e826f3c1e25010c460d441d3a6f60b6261ef [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.pinot.common.request.context;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.Objects;
import javax.annotation.Nullable;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.pinot.common.request.Literal;
import org.apache.pinot.common.utils.PinotDataType;
import org.apache.pinot.spi.data.FieldSpec;
/**
* The {@code LiteralContext} class represents a literal in the query.
* <p>This includes both value and type information. We translate thrift literal to this representation in server.
* Currently, only Boolean literal is correctly encoded in thrift and passed in.
* All integers are encoded as LONG in thrift, and the other numerical types are encoded as DOUBLE.
* The remaining types are encoded as STRING.
*/
public class LiteralContext {
// TODO: Support all of the types for sql.
private final FieldSpec.DataType _type;
private final Object _value;
private final BigDecimal _bigDecimalValue;
private static BigDecimal getBigDecimalValue(FieldSpec.DataType type, Object value) {
switch (type) {
case BIG_DECIMAL:
return (BigDecimal) value;
case BOOLEAN:
return PinotDataType.BOOLEAN.toBigDecimal(value);
case TIMESTAMP:
return PinotDataType.TIMESTAMP.toBigDecimal(Timestamp.valueOf(value.toString()));
default:
if (type.isNumeric()) {
return new BigDecimal(value.toString());
}
return BigDecimal.ZERO;
}
}
@VisibleForTesting
static Pair<FieldSpec.DataType, Object> inferLiteralDataTypeAndValue(String literal) {
// Try to interpret the literal as number
try {
Number number = NumberUtils.createNumber(literal);
if (number instanceof BigDecimal || number instanceof BigInteger) {
return ImmutablePair.of(FieldSpec.DataType.BIG_DECIMAL, new BigDecimal(literal));
} else {
return ImmutablePair.of(FieldSpec.DataType.STRING, literal);
}
} catch (Exception e) {
// Ignored
}
// Try to interpret the literal as TIMESTAMP
try {
Timestamp timestamp = Timestamp.valueOf(literal);
return ImmutablePair.of(FieldSpec.DataType.TIMESTAMP, timestamp);
} catch (Exception e) {
// Ignored
}
return ImmutablePair.of(FieldSpec.DataType.STRING, literal);
}
public LiteralContext(Literal literal) {
Preconditions.checkState(literal.getFieldValue() != null,
"Field value cannot be null for field:" + literal.getSetField());
switch (literal.getSetField()) {
case BOOL_VALUE:
_type = FieldSpec.DataType.BOOLEAN;
_value = literal.getFieldValue();
_bigDecimalValue = PinotDataType.BOOLEAN.toBigDecimal(_value);
break;
case LONG_VALUE:
long longValue = literal.getLongValue();
if (longValue == (int) longValue) {
_type = FieldSpec.DataType.INT;
_value = (int) longValue;
} else {
_type = FieldSpec.DataType.LONG;
_value = longValue;
}
_bigDecimalValue = new BigDecimal(longValue);
break;
case DOUBLE_VALUE:
String stringValue = literal.getFieldValue().toString();
Number floatingNumber = NumberUtils.createNumber(stringValue);
if (floatingNumber instanceof Float) {
_type = FieldSpec.DataType.FLOAT;
_value = floatingNumber;
} else {
_type = FieldSpec.DataType.DOUBLE;
_value = literal.getDoubleValue();
}
_bigDecimalValue = new BigDecimal(stringValue);
break;
case STRING_VALUE:
Pair<FieldSpec.DataType, Object> typeAndValue =
inferLiteralDataTypeAndValue(literal.getFieldValue().toString());
_type = typeAndValue.getLeft();
_value = typeAndValue.getRight();
if (_type == FieldSpec.DataType.BIG_DECIMAL) {
_bigDecimalValue = (BigDecimal) _value;
} else if (_type == FieldSpec.DataType.TIMESTAMP) {
_bigDecimalValue = PinotDataType.TIMESTAMP.toBigDecimal(Timestamp.valueOf(_value.toString()));
} else {
_bigDecimalValue = BigDecimal.ZERO;
}
break;
case NULL_VALUE:
_type = FieldSpec.DataType.UNKNOWN;
_value = null;
_bigDecimalValue = BigDecimal.ZERO;
break;
default:
throw new UnsupportedOperationException("Unsupported data type:" + literal.getSetField());
}
}
public FieldSpec.DataType getType() {
return _type;
}
public int getIntValue() {
return _bigDecimalValue.intValue();
}
public double getDoubleValue() {
return _bigDecimalValue.doubleValue();
}
public BigDecimal getBigDecimalValue() {
return _bigDecimalValue;
}
public String getStringValue() {
return String.valueOf(_value);
}
@Nullable
public Object getValue() {
return _value;
}
// This ctor is only used for special handling in subquery.
public LiteralContext(FieldSpec.DataType type, Object value) {
_type = type;
if (type == FieldSpec.DataType.UNKNOWN) {
_value = null;
} else {
_value = value;
}
_bigDecimalValue = getBigDecimalValue(type, value);
}
@Override
public int hashCode() {
return Objects.hash(_value, _type);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof LiteralContext)) {
return false;
}
LiteralContext that = (LiteralContext) o;
return _type.equals(that._type) && Objects.equals(_value, that._value);
}
@Override
public String toString() {
// TODO: print out the type.
return '\'' + String.valueOf(_value) + '\'';
}
}