diff --git a/cql/src/main/java/com/huawei/streaming/cql/CQLUtils.java b/cql/src/main/java/com/huawei/streaming/cql/CQLUtils.java index 6dab7fe..d84ae94 100644 --- a/cql/src/main/java/com/huawei/streaming/cql/CQLUtils.java +++ b/cql/src/main/java/com/huawei/streaming/cql/CQLUtils.java @@ -18,6 +18,10 @@ package com.huawei.streaming.cql; +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; import java.util.List; import java.util.Set; @@ -78,17 +82,8 @@ public static String cqlStringLiteralTrim(String str) { return null; } - - String s = str; - if (s.startsWith("'") || s.startsWith("\"")) - { - s = s.substring(1, s.length()); - } - if (s.endsWith("'") || s.endsWith("\"")) - { - s = s.substring(0, s.length() - 1); - } - return s; + + return unescapeSQLString(str); } /** @@ -103,4 +98,214 @@ public static ClassLoader getClassLoader() } return classLoader; } + + private static final int[] multiplier = new int[] {1000, 100, 10, 1}; + + /** + * 转义字符处理 + * @param b + * @return + */ + public static String unescapeSQLString(String b) { + Character enclosure = null; + + // Some of the strings can be passed in as unicode. For example, the + // delimiter can be passed in as \002 - So, we first check if the + // string is a unicode number, else go back to the old behavior + StringBuilder sb = new StringBuilder(b.length()); + for (int i = 0; i < b.length(); i++) { + + char currentChar = b.charAt(i); + if (enclosure == null) { + if (currentChar == '\'' || currentChar == '\"') { + enclosure = currentChar; + } + // ignore all other chars outside the enclosure + continue; + } + + if (enclosure.equals(currentChar)) { + enclosure = null; + continue; + } + + if (currentChar == '\\' && (i + 6 < b.length()) && b.charAt(i + 1) == 'u') { + int code = 0; + int base = i + 2; + for (int j = 0; j < 4; j++) { + int digit = Character.digit(b.charAt(j + base), 16); + code += digit * multiplier[j]; + } + sb.append((char)code); + i += 5; + continue; + } + + if (currentChar == '\\' && (i + 4 < b.length())) { + char i1 = b.charAt(i + 1); + char i2 = b.charAt(i + 2); + char i3 = b.charAt(i + 3); + if ((i1 >= '0' && i1 <= '1') && (i2 >= '0' && i2 <= '7') + && (i3 >= '0' && i3 <= '7')) { + byte bVal = (byte) ((i3 - '0') + ((i2 - '0') * 8) + ((i1 - '0') * 8 * 8)); + byte[] bValArr = new byte[1]; + bValArr[0] = bVal; + String tmp = new String(bValArr); + sb.append(tmp); + i += 3; + continue; + } + } + + if (currentChar == '\\' && (i + 2 < b.length())) { + char n = b.charAt(i + 1); + switch (n) { + case '0': + sb.append("\0"); + break; + case '\'': + sb.append("'"); + break; + case '"': + sb.append("\""); + break; + case 'b': + sb.append("\b"); + break; + case 'n': + sb.append("\n"); + break; + case 'r': + sb.append("\r"); + break; + case 't': + sb.append("\t"); + break; + case 'Z': + sb.append("\u001A"); + break; + case '\\': + sb.append("\\"); + break; + // The following 2 lines are exactly what MySQL does TODO: why do we do this? + case '%': + sb.append("\\%"); + break; + case '_': + sb.append("\\_"); + break; + default: + sb.append(n); + } + i++; + } else { + sb.append(currentChar); + } + } + return sb.toString(); + } + + public static String escapeSQLString(String b) { + // There's usually nothing to escape so we will be optimistic. + String result = b; + for (int i = 0; i < result.length(); ++i) { + char currentChar = result.charAt(i); + if (currentChar == '\\' && ((i + 1) < result.length())) { + // TODO: do we need to handle the "this is what MySQL does" here? + char nextChar = result.charAt(i + 1); + if (nextChar == '%' || nextChar == '_') { + ++i; + continue; + } + } + switch (currentChar) { + case '\0': result = spliceString(result, i, "\\0"); ++i; break; + case '\'': result = spliceString(result, i, "\\'"); ++i; break; + case '\"': result = spliceString(result, i, "\\\""); ++i; break; + case '\b': result = spliceString(result, i, "\\b"); ++i; break; + case '\n': result = spliceString(result, i, "\\n"); ++i; break; + case '\r': result = spliceString(result, i, "\\r"); ++i; break; + case '\t': result = spliceString(result, i, "\\t"); ++i; break; + case '\\': result = spliceString(result, i, "\\\\"); ++i; break; + case '\u001A': result = spliceString(result, i, "\\Z"); ++i; break; + default: { + if (currentChar < ' ') { + String hex = Integer.toHexString(currentChar); + String unicode = "\\u"; + for (int j = 4; j > hex.length(); --j) { + unicode += '0'; + } + unicode += hex; + result = spliceString(result, i, unicode); + i += (unicode.length() - 1); + } + break; // if not a control character, do nothing + } + } + } + + return "'"+result+"'"; + } + + private static String spliceString(String str, int i, String replacement) { + return spliceString(str, i, 1, replacement); + } + + private static String spliceString(String str, int i, int length, String replacement) { + return str.substring(0, i) + replacement + str.substring(i + length); + } + + public static String constantToString(Class datatype,Object value) { + if (datatype == null) + { + return "NULL"; + } + + if (datatype == Integer.class) + { + return value.toString(); + } + + if (datatype == Long.class) + { + return value.toString() + "L"; + } + + if (datatype == Float.class) + { + return value.toString() + "F"; + } + + if (datatype == Double.class) + { + return value.toString() + "D"; + } + + if (datatype == Date.class) + { + return value.toString() + "DT"; + } + + if (datatype == Time.class) + { + return value.toString() + "TM"; + } + + if (datatype == Timestamp.class) + { + return value.toString() + "TS"; + } + + if (datatype == BigDecimal.class) + { + return value.toString() + "BD"; + } + + if (datatype == Boolean.class) + { + return value.toString(); + } + + return CQLUtils.escapeSQLString(value.toString()); + } } diff --git a/cql/src/main/java/com/huawei/streaming/cql/semanticanalyzer/analyzecontext/expressiondesc/ConstExpressionDesc.java b/cql/src/main/java/com/huawei/streaming/cql/semanticanalyzer/analyzecontext/expressiondesc/ConstExpressionDesc.java index b90dc3f..ccfd296 100644 --- a/cql/src/main/java/com/huawei/streaming/cql/semanticanalyzer/analyzecontext/expressiondesc/ConstExpressionDesc.java +++ b/cql/src/main/java/com/huawei/streaming/cql/semanticanalyzer/analyzecontext/expressiondesc/ConstExpressionDesc.java @@ -18,13 +18,12 @@ package com.huawei.streaming.cql.semanticanalyzer.analyzecontext.expressiondesc; +import com.huawei.streaming.cql.CQLUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.huawei.streaming.cql.executor.expressioncreater.ConstExpressionCreator; import com.huawei.streaming.cql.executor.operatorinfocreater.ExpressionCreatorAnnotation; -import com.huawei.streaming.exception.StreamingException; -import com.huawei.streaming.util.StreamingDataType; /** * 常量表达式的描述 @@ -62,29 +61,7 @@ public ConstExpressionDesc(Object constValue, Class< ? > type) @Override public String toString() { - - //对应常量值为null的场景 - if (constValue == null) - { - return null; - } - - StreamingDataType dataType = null; - - try - { - dataType = StreamingDataType.getStreamingDataType(type); - } - catch (StreamingException e) - { - LOG.warn("Ignore an StreamingExpression."); - return "'" + constValue + "'"; - } - - String constStringValue = constValue.toString(); - String postfix = getConstPostfix(dataType); - //没有时间类型的常量,统一按照字符串标准进行处理 - return postfix == null ? "'" + constValue + "'" : constStringValue + postfix; + return CQLUtils.constantToString(type,constValue); } public Object getConstValue() @@ -107,25 +84,4 @@ public void setType(Class< ? > type) this.type = type; } - private String getConstPostfix(StreamingDataType dataType) - { - //各种事件类型没有常量,统一按照字符串来处理 - //int和boolean没有后缀 - switch (dataType) - { - case INT: - case BOOLEAN: - return ""; - case LONG: - return "L"; - case FLOAT: - return "F"; - case DOUBLE: - return "D"; - case DECIMAL: - return "BD"; - default: - return null; - } - } } diff --git a/cql/src/main/java/com/huawei/streaming/cql/semanticanalyzer/parser/context/ConstantContext.java b/cql/src/main/java/com/huawei/streaming/cql/semanticanalyzer/parser/context/ConstantContext.java index d64609b..8aaf68b 100644 --- a/cql/src/main/java/com/huawei/streaming/cql/semanticanalyzer/parser/context/ConstantContext.java +++ b/cql/src/main/java/com/huawei/streaming/cql/semanticanalyzer/parser/context/ConstantContext.java @@ -18,11 +18,8 @@ package com.huawei.streaming.cql.semanticanalyzer.parser.context; -import java.math.BigDecimal; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; +import com.huawei.streaming.cql.CQLUtils; import com.huawei.streaming.cql.exception.SemanticAnalyzerException; import com.huawei.streaming.cql.semanticanalyzer.analyzecontext.expressiondesc.ConstExpressionDesc; import com.huawei.streaming.cql.semanticanalyzer.analyzecontext.expressiondesc.ExpressionDescribe; @@ -76,60 +73,7 @@ public void setValue(Object value) @Override public String toString() { - if (datatype == null) - { - return "NULL"; - } - - if (datatype == Integer.class) - { - return value.toString(); - } - - if (datatype == Long.class) - { - return value.toString() + "L"; - } - - if (datatype == Float.class) - { - return value.toString() + "F"; - } - - if (datatype == Double.class) - { - return value.toString() + "D"; - } - - if (datatype == Date.class) - { - return value.toString() + "DT"; - } - - if (datatype == Time.class) - { - return value.toString() + "TM"; - } - - if (datatype == Timestamp.class) - { - return value.toString() + "TS"; - } - - if (datatype == BigDecimal.class) - { - return value.toString() + "BD"; - } - - if (datatype == Boolean.class) - { - return value.toString(); - } - if (value.toString().contains("\"")) - { - return "'" + value.toString() + "'"; - } - return "\"" + value.toString() + "\""; + return CQLUtils.constantToString(datatype,value); } /**