blob: 3cf2ab2908ecb35e6e87093659806ba50f5e4600 [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.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import org.apache.impala.catalog.ScalarType;
import org.apache.impala.catalog.Type;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.compat.MetastoreShim;
import org.apache.impala.thrift.TExprNode;
import org.apache.impala.thrift.TExprNodeType;
import org.apache.impala.thrift.TStringLiteral;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import java_cup.runtime.Symbol;
public class StringLiteral extends LiteralExpr {
private final String value_;
public static int MAX_STRING_LEN = Integer.MAX_VALUE;
// Indicates whether this value needs to be unescaped in toThrift().
private final boolean needsUnescaping_;
public StringLiteral(String value) {
this(value, ScalarType.STRING, true);
}
public StringLiteral(String value, Type type, boolean needsUnescaping) {
value_ = value;
type_ = type;
needsUnescaping_ = needsUnescaping;
}
/**
* Copy c'tor used in clone().
*/
protected StringLiteral(StringLiteral other) {
super(other);
value_ = other.value_;
needsUnescaping_ = other.needsUnescaping_;
}
@Override
public boolean localEquals(Expr that) {
if (!super.localEquals(that)) return false;
StringLiteral other = (StringLiteral) that;
return needsUnescaping_ == other.needsUnescaping_ && type_.equals(other.type_)
&& value_.equals(other.value_);
}
@Override
public int hashCode() { return value_.hashCode(); }
@Override
public String toSqlImpl(ToSqlOptions options) {
return "'" + getNormalizedValue() + "'";
}
@Override
protected void toThrift(TExprNode msg) {
msg.node_type = TExprNodeType.STRING_LITERAL;
String val = (needsUnescaping_) ? getUnescapedValue() : value_;
msg.string_literal = new TStringLiteral(val);
}
/**
* Returns the original value that the string literal was constructed with,
* without escaping or unescaping it.
*/
public String getValueWithOriginalEscapes() { return value_; }
public String getUnescapedValue() {
// Unescape string exactly like Hive does. Hive's method assumes
// quotes so we add them here to reuse Hive's code.
return MetastoreShim.unescapeSQLString("'" + getNormalizedValue()
+ "'");
}
/**
* String literals can come directly from the SQL of a query or from rewrites like
* constant folding. So this value normalization to a single-quoted string is necessary
* because we do not know whether single or double quotes are appropriate.
*
* @return a normalized representation of the string value suitable for embedding in
* SQL as a single-quoted string literal.
*/
private String getNormalizedValue() {
final int len = value_.length();
final StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; ++i) {
final char currentChar = value_.charAt(i);
if (currentChar == '\\' && (i + 1) < len) {
final char nextChar = value_.charAt(i + 1);
// unescape an escaped double quote: remove back-slash in front of the quote.
if (nextChar == '"' || nextChar == '\'' || nextChar == '\\') {
if (nextChar != '"') {
sb.append(currentChar);
}
sb.append(nextChar);
++i;
continue;
}
sb.append(currentChar);
} else if (currentChar == '\'') {
// escape a single quote: add back-slash in front of the quote.
sb.append("\\\'");
} else {
sb.append(currentChar);
}
}
return sb.toString();
}
@Override
public String getStringValue() {
return getValueWithOriginalEscapes();
}
@Override
public String debugString() {
return Objects.toStringHelper(this)
.add("value", value_)
.toString();
}
@Override
protected Expr uncheckedCastTo(Type targetType) throws AnalysisException {
Preconditions.checkState(targetType.isNumericType() || targetType.isDateOrTimeType()
|| targetType.equals(this.type_) || targetType.isStringType());
if (targetType.equals(this.type_)) {
return this;
} else if (targetType.isStringType()) {
type_ = targetType;
} else if (targetType.isNumericType()) {
return convertToNumber();
} else if (targetType.isDateOrTimeType()) {
// Let the BE do the cast
// - it is in Boost format in case target type is TIMESTAMP
// - CCTZ is used for conversion in case target type is DATE.
return new CastExpr(targetType, this);
}
return this;
}
/**
* Convert this string literal to numeric literal.
*
* @return new converted literal (not null)
* the type of the literal is determined by the lexical scanner
* @throws AnalysisException
* if NumberFormatException occurs,
* or if floating point value is NaN or infinite
*/
public LiteralExpr convertToNumber()
throws AnalysisException {
StringReader reader = new StringReader(value_);
SqlScanner scanner = new SqlScanner(reader);
// For distinguishing positive and negative numbers.
boolean negative = false;
Symbol sym;
try {
// We allow simple chaining of MINUS to recognize negative numbers.
// Currently we can't handle string literals containing full fledged expressions
// which are implicitly cast to a numeric literal.
// This would require invoking the parser.
sym = scanner.next_token();
while (sym.sym == SqlParserSymbols.SUBTRACT) {
negative = !negative;
sym = scanner.next_token();
}
} catch (IOException e) {
throw new AnalysisException("Failed to convert string literal to number.", e);
}
if (sym.sym == SqlParserSymbols.NUMERIC_OVERFLOW) {
throw new AnalysisException("Number too large: " + value_);
}
if (sym.sym == SqlParserSymbols.INTEGER_LITERAL) {
BigDecimal val = (BigDecimal) sym.value;
if (negative) val = val.negate();
return new NumericLiteral(val);
}
if (sym.sym == SqlParserSymbols.DECIMAL_LITERAL) {
BigDecimal val = (BigDecimal) sym.value;
if (negative) val = val.negate();
return new NumericLiteral(val);
}
// Symbol is not an integer or floating point literal.
throw new AnalysisException("Failed to convert string literal '"
+ value_ + "' to number.");
}
@Override
public int compareTo(LiteralExpr o) {
int ret = super.compareTo(o);
if (ret != 0) return ret;
StringLiteral other = (StringLiteral) o;
return value_.compareTo(other.getStringValue());
}
@Override
public Expr clone() { return new StringLiteral(this); }
}