blob: a258c3e24641686f30c7a5b6d4bc6f99dacca5f1 [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.impala.analysis;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import org.apache.impala.catalog.ScalarType;
import org.apache.impala.catalog.Type;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.common.InternalException;
import org.apache.impala.common.NotImplementedException;
import org.apache.impala.common.SqlCastException;
import org.apache.impala.common.UnsupportedFeatureException;
import org.apache.impala.service.FeSupport;
import org.apache.impala.thrift.TColumnValue;
import org.apache.impala.thrift.TExprNode;
import org.apache.impala.thrift.TQueryCtx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
/**
* Representation of a literal expression. Literals are comparable to allow
* ordering of HdfsPartitions whose partition-key values are represented as literals.
*/
public abstract class LiteralExpr extends Expr implements Comparable<LiteralExpr> {
private final static Logger LOG = LoggerFactory.getLogger(LiteralExpr.class);
public static final int MAX_STRING_LITERAL_SIZE = 64 * 1024;
public LiteralExpr() {
// Literals start analyzed: there is nothing more to check.
evalCost_ = LITERAL_COST;
numDistinctValues_ = 1;
// Subclass is responsible for setting the type
}
/**
* Copy c'tor used in clone().
*/
protected LiteralExpr(LiteralExpr other) {
super(other);
}
/**
* Returns an analyzed literal of 'type'. Returns null for types that do not have a
* LiteralExpr subclass, e.g. TIMESTAMP.
*/
public static LiteralExpr create(String value, Type type) throws AnalysisException {
if (!type.isValid()) {
throw new UnsupportedFeatureException("Invalid literal type: " + type.toSql());
}
LiteralExpr e = null;
switch (type.getPrimitiveType()) {
case NULL_TYPE:
e = new NullLiteral();
break;
case BOOLEAN:
e = new BoolLiteral(value);
break;
case TINYINT:
case SMALLINT:
case INT:
case BIGINT:
case FLOAT:
case DOUBLE:
case DECIMAL:
e = new NumericLiteral(value, type);
break;
case STRING:
case VARCHAR:
case CHAR:
e = new StringLiteral(value);
break;
case DATE:
e = new DateLiteral(value);
break;
case DATETIME:
case TIMESTAMP:
// TODO: we support TIMESTAMP but no way to specify it in SQL.
throw new UnsupportedFeatureException("Literal unsupported: " + type.toSql());
default:
throw new UnsupportedFeatureException(
String.format("Literals of type '%s' not supported.", type.toSql()));
}
e.analyze(null);
// Need to cast since we cannot infer the type from the value. e.g. value
// can be parsed as tinyint but we need a bigint.
return (LiteralExpr) e.uncheckedCastTo(type);
}
@Override
protected void analyzeImpl(Analyzer analyzer) throws AnalysisException {
// Literals require no analysis.
}
@Override
protected float computeEvalCost() {
return LITERAL_COST;
}
/**
* Returns an analyzed literal from the thrift object.
*/
public static LiteralExpr fromThrift(TExprNode exprNode, Type colType) {
try {
LiteralExpr result = null;
switch (exprNode.node_type) {
case FLOAT_LITERAL:
result = LiteralExpr.create(
Double.toString(exprNode.float_literal.value), colType);
break;
case DECIMAL_LITERAL:
byte[] bytes = exprNode.decimal_literal.getValue();
BigDecimal val = new BigDecimal(new BigInteger(bytes));
ScalarType decimalType = (ScalarType) colType;
// We store the decimal as the unscaled bytes. Need to adjust for the scale.
val = val.movePointLeft(decimalType.decimalScale());
result = new NumericLiteral(val, colType);
break;
case DATE_LITERAL:
result = new DateLiteral(exprNode.date_literal.days_since_epoch,
exprNode.date_literal.date_string);
break;
case INT_LITERAL:
result = LiteralExpr.create(
Long.toString(exprNode.int_literal.value), colType);
break;
case STRING_LITERAL:
result = LiteralExpr.create(exprNode.string_literal.value, colType);
break;
case BOOL_LITERAL:
result = LiteralExpr.create(
Boolean.toString(exprNode.bool_literal.value), colType);
break;
case NULL_LITERAL:
return NullLiteral.create(colType);
default:
throw new UnsupportedOperationException("Unsupported partition key type: " +
exprNode.node_type);
}
Preconditions.checkNotNull(result);
result.analyze(null);
return result;
} catch (Exception e) {
throw new IllegalStateException("Error creating LiteralExpr: ", e);
}
}
// Returns the string representation of the literal's value. Used when passing
// literal values to the metastore rather than to Impala backends. This is similar to
// the toSql() method, but does not perform any formatting of the string values. Neither
// method unescapes string values.
public abstract String getStringValue();
// Swaps the sign of numeric literals.
// Throws for non-numeric literals.
public void swapSign() throws NotImplementedException {
throw new NotImplementedException("swapSign() only implemented for numeric" +
"literals");
}
/**
* If it is known that the size of the rewritten expression is fixed, e.g.,
* the size of an integer, then this method will be called to perform the rewrite.
* Otherwise, the method createBounded that takes an additional argument specifying
* the upper bound on the size of rewritten expression should be invoked.
*/
public static LiteralExpr create(Expr constExpr, TQueryCtx queryCtx)
throws AnalysisException {
return createBounded(constExpr, queryCtx, 0);
}
/**
* Evaluates the given constant expr and returns its result as a LiteralExpr.
* Assumes expr has been analyzed. Returns constExpr if is it already a LiteralExpr.
* Returns null for types that do not have a LiteralExpr subclass, e.g. TIMESTAMP, or
* in cases where the corresponding LiteralExpr is not able to represent the evaluation
* result, e.g., NaN or infinity. Returns null if the expr evaluation encountered errors
* or warnings in the BE.
* TODO: Support non-scalar types.
*/
public static LiteralExpr createBounded(Expr constExpr, TQueryCtx queryCtx,
int maxResultSize) throws AnalysisException {
Preconditions.checkState(constExpr.isConstant());
Preconditions.checkState(constExpr.getType().isValid());
if (constExpr instanceof LiteralExpr) return (LiteralExpr) constExpr;
TColumnValue val = null;
try {
val = FeSupport.EvalExprWithoutRowBounded(constExpr, queryCtx, maxResultSize);
} catch (InternalException e) {
LOG.error(String.format("Failed to evaluate expr '%s': %s",
constExpr.toSql(), e.getMessage()));
return null;
}
LiteralExpr result = null;
switch (constExpr.getType().getPrimitiveType()) {
case NULL_TYPE:
result = new NullLiteral();
break;
case BOOLEAN:
if (val.isSetBool_val()) result = new BoolLiteral(val.bool_val);
break;
case TINYINT:
if (val.isSetByte_val()) {
result = new NumericLiteral(BigDecimal.valueOf(val.byte_val));
}
break;
case SMALLINT:
if (val.isSetShort_val()) {
result = new NumericLiteral(BigDecimal.valueOf(val.short_val));
}
break;
case INT:
if (val.isSetInt_val()) {
result = new NumericLiteral(BigDecimal.valueOf(val.int_val));
}
break;
case BIGINT:
if (val.isSetLong_val()) {
result = new NumericLiteral(BigDecimal.valueOf(val.long_val));
}
break;
case FLOAT:
case DOUBLE:
if (val.isSetDouble_val()) {
// Create using double directly, at extreme ranges the BigDecimal
// value overflows a double due to conversion issues.
// A NumericLiteral cannot represent NaN, infinity or negative zero.
// SqlCastException thrown for these cases.
try {
result = new NumericLiteral(val.double_val, constExpr.getType());
} catch (SqlCastException e) {
return null;
}
}
break;
case DECIMAL:
if (val.isSetString_val()) {
result =
new NumericLiteral(new BigDecimal(val.string_val), constExpr.getType());
}
break;
case STRING:
case VARCHAR:
case CHAR:
if (val.isSetBinary_val()) {
byte[] bytes = new byte[val.binary_val.remaining()];
val.binary_val.get(bytes);
// Converting strings between the BE/FE does not work properly for the
// extended ASCII characters above 127. Bail in such cases to avoid
// producing incorrect results.
for (byte b: bytes) if (b < 0) return null;
try {
// US-ASCII is 7-bit ASCII.
String strVal = new String(bytes, "US-ASCII");
// The evaluation result is a raw string that must not be unescaped.
result = new StringLiteral(strVal, constExpr.getType(), false);
} catch (UnsupportedEncodingException e) {
return null;
}
}
break;
case TIMESTAMP:
// Expects both the binary and string fields to be set, so we get the raw
// representation as well as the string representation.
if (val.isSetBinary_val() && val.isSetString_val()) {
result = new TimestampLiteral(val.getBinary_val(), val.getString_val());
}
break;
case DATE:
// Expects both the int and string fields to be set, so we get the raw
// representation and the string representation.
if (val.isSetInt_val() && val.isSetString_val()) {
result = new DateLiteral(val.int_val, val.getString_val());
}
break;
case DATETIME:
return null;
default:
Preconditions.checkState(false,
String.format("Literals of type '%s' not supported.",
constExpr.getType().toSql()));
}
// None of the fields in the thrift struct were set indicating a NULL.
if (result == null) result = new NullLiteral();
result.analyzeNoThrow(null);
return result;
}
// Order NullLiterals based on the SQL ORDER BY default behavior: NULLS LAST.
@Override
public int compareTo(LiteralExpr other) {
if (Expr.IS_NULL_LITERAL.apply(this) && Expr.IS_NULL_LITERAL.apply(other)) return 0;
if (Expr.IS_NULL_LITERAL.apply(this)) return -1;
if (Expr.IS_NULL_LITERAL.apply(other)) return 1;
if (getClass() != other.getClass()) return -1;
return 0;
}
}