| // 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. |
| // This file is copied from |
| // https://github.com/apache/impala/blob/branch-2.9.0/fe/src/main/java/org/apache/impala/StringLiteral.java |
| // and modified by Doris |
| |
| package org.apache.doris.analysis; |
| |
| import org.apache.doris.catalog.PrimitiveType; |
| import org.apache.doris.catalog.Type; |
| import org.apache.doris.common.AnalysisException; |
| import org.apache.doris.common.DdlException; |
| import org.apache.doris.common.io.Text; |
| import org.apache.doris.qe.VariableVarConverters; |
| import org.apache.doris.thrift.TExprNode; |
| import org.apache.doris.thrift.TExprNodeType; |
| import org.apache.doris.thrift.TStringLiteral; |
| |
| import com.google.common.base.Preconditions; |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.logging.log4j.Logger; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.math.BigDecimal; |
| import java.nio.ByteBuffer; |
| import java.util.Objects; |
| |
| public class StringLiteral extends LiteralExpr { |
| private static final Logger LOG = LogManager.getLogger(StringLiteral.class); |
| private String value; |
| // Means the converted session variable need to be cast to int, such as "cast 'STRICT_TRANS_TABLES' to Integer". |
| private String beConverted = ""; |
| |
| public StringLiteral() { |
| super(); |
| type = Type.VARCHAR; |
| } |
| |
| public StringLiteral(String value) { |
| super(); |
| this.value = value; |
| type = Type.VARCHAR; |
| analysisDone(); |
| } |
| |
| protected StringLiteral(StringLiteral other) { |
| super(other); |
| value = other.value; |
| } |
| |
| public void setBeConverted(String val) { |
| this.beConverted = val; |
| } |
| |
| @Override |
| public Expr clone() { |
| return new StringLiteral(this); |
| } |
| |
| @Override |
| public int compareLiteral(LiteralExpr expr) { |
| if (expr instanceof NullLiteral) { |
| return 1; |
| } |
| if (expr == MaxLiteral.MAX_VALUE) { |
| return -1; |
| } |
| // compare string with utf-8 byte array, same with DM,BE,StorageEngine |
| byte[] thisBytes = null; |
| byte[] otherBytes = null; |
| try { |
| thisBytes = value.getBytes("UTF-8"); |
| otherBytes = expr.getStringValue().getBytes("UTF-8"); |
| } catch (UnsupportedEncodingException e) { |
| Preconditions.checkState(false); |
| } |
| |
| int minLength = Math.min(thisBytes.length, otherBytes.length); |
| int i = 0; |
| for (i = 0; i < minLength; i++) { |
| if (Byte.toUnsignedInt(thisBytes[i]) < Byte.toUnsignedInt(otherBytes[i])) { |
| return -1; |
| } else if (Byte.toUnsignedInt(thisBytes[i]) > Byte.toUnsignedInt(otherBytes[i])) { |
| return 1; |
| } |
| } |
| if (thisBytes.length > otherBytes.length) { |
| if (thisBytes[i] == 0x00) { |
| return 0; |
| } else { |
| return 1; |
| } |
| } else if (thisBytes.length < otherBytes.length) { |
| if (otherBytes[i] == 0x00) { |
| return 0; |
| } else { |
| return -1; |
| } |
| } else { |
| return 0; |
| } |
| } |
| |
| public String getValue() { |
| return value; |
| } |
| |
| @Override |
| public boolean isMinValue() { |
| return false; |
| } |
| |
| @Override |
| public String toSqlImpl() { |
| return "'" + value.replaceAll("'", "''") + "'"; |
| } |
| |
| @Override |
| protected void toThrift(TExprNode msg) { |
| if (value == null) { |
| msg.node_type = TExprNodeType.NULL_LITERAL; |
| } else { |
| msg.string_literal = new TStringLiteral(value); |
| msg.node_type = TExprNodeType.STRING_LITERAL; |
| } |
| } |
| |
| @Override |
| public String getStringValue() { |
| return value; |
| } |
| |
| @Override |
| public String getStringValueForArray() { |
| return "\"" + getStringValue() + "\""; |
| } |
| |
| @Override |
| public long getLongValue() { |
| return Long.valueOf(value); |
| } |
| |
| @Override |
| public double getDoubleValue() { |
| return Double.parseDouble(value); |
| } |
| |
| @Override |
| public String getRealValue() { |
| return getStringValue(); |
| } |
| |
| /** |
| * Convert a string literal to a IPv4 literal |
| * |
| * @return new converted literal (not null) |
| * @throws AnalysisException when entire given string cannot be transformed into a date |
| */ |
| public LiteralExpr convertToIPv4() throws AnalysisException { |
| LiteralExpr newLiteral; |
| newLiteral = new IPv4Literal(value); |
| try { |
| newLiteral.checkValueValid(); |
| } catch (AnalysisException e) { |
| return NullLiteral.create(newLiteral.getType()); |
| } |
| return newLiteral; |
| } |
| |
| /** |
| * Convert a string literal to a IPv6 literal |
| * |
| * @return new converted literal (not null) |
| * @throws AnalysisException when entire given string cannot be transformed into a date |
| */ |
| public LiteralExpr convertToIPv6() throws AnalysisException { |
| LiteralExpr newLiteral; |
| newLiteral = new IPv6Literal(value); |
| try { |
| newLiteral.checkValueValid(); |
| } catch (AnalysisException e) { |
| return NullLiteral.create(newLiteral.getType()); |
| } |
| return newLiteral; |
| } |
| |
| /** |
| * Convert a string literal to a date literal |
| * |
| * @param targetType is the desired type |
| * @return new converted literal (not null) |
| * @throws AnalysisException when entire given string cannot be transformed into a date |
| */ |
| public LiteralExpr convertToDate(Type targetType) throws AnalysisException { |
| LiteralExpr newLiteral = null; |
| try { |
| newLiteral = new DateLiteral(value, targetType); |
| } catch (AnalysisException e) { |
| if (targetType.isScalarType(PrimitiveType.DATETIME)) { |
| newLiteral = new DateLiteral(value, Type.DATE); |
| newLiteral.setType(Type.DATETIME); |
| } else if (targetType.isScalarType(PrimitiveType.DATETIMEV2)) { |
| newLiteral = new DateLiteral(value, Type.DATEV2); |
| newLiteral.setType(targetType); |
| } else { |
| throw e; |
| } |
| } |
| try { |
| newLiteral.checkValueValid(); |
| } catch (AnalysisException e) { |
| return NullLiteral.create(newLiteral.getType()); |
| } |
| return newLiteral; |
| } |
| |
| public boolean canConvertToDateType(Type targetType) { |
| try { |
| Preconditions.checkArgument(targetType.isDateType()); |
| new DateLiteral(value, targetType); |
| return true; |
| } catch (AnalysisException e) { |
| return false; |
| } |
| } |
| |
| @Override |
| protected Expr uncheckedCastTo(Type targetType) throws AnalysisException { |
| if (targetType.isNumericType()) { |
| switch (targetType.getPrimitiveType()) { |
| case TINYINT: |
| case SMALLINT: |
| case INT: |
| case BIGINT: |
| if (VariableVarConverters.hasConverter(beConverted)) { |
| try { |
| return new IntLiteral(VariableVarConverters.encode(beConverted, value), targetType); |
| } catch (DdlException e) { |
| throw new AnalysisException(e.getMessage()); |
| } |
| } |
| return new IntLiteral(value, targetType); |
| case LARGEINT: |
| if (VariableVarConverters.hasConverter(beConverted)) { |
| try { |
| return new LargeIntLiteral(String.valueOf( |
| VariableVarConverters.encode(beConverted, value))); |
| } catch (DdlException e) { |
| throw new AnalysisException(e.getMessage()); |
| } |
| } |
| return new LargeIntLiteral(value); |
| case FLOAT: |
| case DOUBLE: |
| try { |
| return new FloatLiteral(Double.valueOf(value), targetType); |
| } catch (NumberFormatException e) { |
| // consistent with CastExpr's getResultValue() method |
| return new NullLiteral(); |
| } |
| case DECIMALV2: |
| case DECIMAL32: |
| case DECIMAL64: |
| case DECIMAL128: |
| case DECIMAL256: |
| try { |
| DecimalLiteral res = new DecimalLiteral(new BigDecimal(value).stripTrailingZeros()); |
| res.setType(targetType); |
| return res; |
| } catch (Exception e) { |
| throw new AnalysisException( |
| String.format("input value can't parse to decimal, value=%s", value)); |
| } |
| default: |
| break; |
| } |
| } else if (targetType.isDateType()) { |
| // FE only support 'yyyy-MM-dd hh:mm:ss' && 'yyyy-MM-dd' format |
| // so if FE unchecked cast fail, we also build CastExpr for BE |
| // BE support other format such as 'yyyyMMdd'... |
| try { |
| return convertToDate(targetType); |
| } catch (AnalysisException e) { |
| // pass; |
| } |
| } else if (targetType.isIPv4()) { |
| return convertToIPv4(); |
| } else if (targetType.isIPv6()) { |
| return convertToIPv6(); |
| } else if (targetType.equals(type)) { |
| return this; |
| } else if (targetType.isStringType()) { |
| StringLiteral stringLiteral = new StringLiteral(this); |
| stringLiteral.setType(targetType); |
| return stringLiteral; |
| } else if (targetType.isJsonbType()) { |
| return new JsonLiteral(value); |
| } |
| return super.uncheckedCastTo(targetType); |
| } |
| |
| @Override |
| public void write(DataOutput out) throws IOException { |
| super.write(out); |
| Text.writeString(out, value); |
| } |
| |
| public void readFields(DataInput in) throws IOException { |
| super.readFields(in); |
| value = Text.readString(in); |
| } |
| |
| public static StringLiteral read(DataInput in) throws IOException { |
| StringLiteral literal = new StringLiteral(); |
| literal.readFields(in); |
| return literal; |
| } |
| |
| @Override |
| public int hashCode() { |
| return 31 * super.hashCode() + Objects.hashCode(value); |
| } |
| |
| @Override |
| public void setupParamFromBinary(ByteBuffer data) { |
| int strLen = getParmLen(data); |
| if (strLen > data.remaining()) { |
| strLen = data.remaining(); |
| } |
| byte[] bytes = new byte[strLen]; |
| data.get(bytes); |
| value = new String(bytes); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("parsed value '{}'", value); |
| } |
| } |
| } |