/* * Copyright 1999-2012 Alibaba Group. * * Licensed 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. */ /** * (created at 2011-3-11) */ package com.alibaba.cobar.parser.recognizer.mysql.lexer; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.SQLSyntaxErrorException; import com.alibaba.cobar.parser.recognizer.mysql.MySQLToken; import com.alibaba.cobar.parser.util.CharTypes; /** * support MySQL 5.5 token * * @author <a href="mailto:shuo.qius@alibaba-inc.com">QIU Shuo</a> */ public class MySQLLexer { private static int C_STYLE_COMMENT_VERSION = 50599; /** * @return previous value */ public static int setCStyleCommentVersion(int version) { int v = C_STYLE_COMMENT_VERSION; C_STYLE_COMMENT_VERSION = version; return v; } /** * End of input character. Used as a sentinel to denote the character one * beyond the last defined character in a source file. */ private final static byte EOI = 0x1A; protected final char[] sql; /** always be {@link #sql}.length - 1 */ protected final int eofIndex; /** current index of {@link #sql} */ protected int curIndex = -1; /** always be {@link #sql}[{@link #curIndex}] */ protected char ch; // /** current token, set by {@link #nextToken()} */ // private int tokenPos = 0; private MySQLToken token; /** keyword only */ private MySQLToken tokenCache; private MySQLToken tokenCache2; /** 1 represents first parameter */ private int paramIndex = 0; /** A character buffer for literals. */ protected final static ThreadLocal<char[]> sbufRef = new ThreadLocal<char[]>(); protected char[] sbuf; private String stringValue; /** make sense only for {@link MySQLToken#IDENTIFIER} */ private String stringValueUppercase; /** * update {@link MySQLLexer#stringValue} and * {@link MySQLLexer#stringValueUppercase}. It is possible that * {@link #sbuf} be changed */ protected void updateStringValue(final char[] src, final int srcOffset, final int len) { // QS_TODO [performance enhance]: use String constant for special // identifier, so that parser can use '==' rather than 'equals' stringValue = new String(src, srcOffset, len); final int end = srcOffset + len; boolean lowerCase = false; int srcIndex = srcOffset; int hash = 0; for (; srcIndex < end; ++srcIndex) { char c = src[srcIndex]; if (c >= 'a' && c <= 'z') { lowerCase = true; if (srcIndex > srcOffset) { System.arraycopy(src, srcOffset, sbuf, 0, srcIndex - srcOffset); } break; } hash = 31 * hash + c; } if (lowerCase) { for (int destIndex = srcIndex - srcOffset; destIndex < len; ++destIndex) { char c = src[srcIndex++]; hash = 31 * hash + c; if (c >= 'a' && c <= 'z') { sbuf[destIndex] = (char) (c - 32); hash -= 32; } else { sbuf[destIndex] = c; } } stringValueUppercase = new String(sbuf, 0, len); } else { stringValueUppercase = new String(src, srcOffset, len); } } public MySQLLexer(char[] sql) throws SQLSyntaxErrorException { if ((this.sbuf = sbufRef.get()) == null) { this.sbuf = new char[1024]; sbufRef.set(this.sbuf); } if (CharTypes.isWhitespace(sql[sql.length - 1])) { this.sql = sql; } else { this.sql = new char[sql.length + 1]; System.arraycopy(sql, 0, this.sql, 0, sql.length); } this.eofIndex = this.sql.length - 1; this.sql[this.eofIndex] = MySQLLexer.EOI; scanChar(); nextToken(); } public MySQLLexer(String sql) throws SQLSyntaxErrorException { this(fromSQL2Chars(sql)); } private static char[] fromSQL2Chars(String sql) { if (CharTypes.isWhitespace(sql.charAt(sql.length() - 1))) { return sql.toCharArray(); } char[] chars = new char[sql.length() + 1]; sql.getChars(0, sql.length(), chars, 0); chars[chars.length - 1] = ' '; return chars; } protected MySQLKeywords keywods = MySQLKeywords.DEFAULT_KEYWORDS; /** * @param token must be a keyword */ public final void addCacheToke(MySQLToken token) { if (tokenCache != null) { tokenCache2 = token; } else { tokenCache = token; } } public final MySQLToken token() { if (tokenCache2 != null) { return tokenCache2; } if (tokenCache != null) { return tokenCache; } return token; } public final int getCurrentIndex() { return this.curIndex; } public final char[] getSQL() { return sql; } public int getOffsetCache() { return offsetCache; } public int getSizeCache() { return sizeCache; } /** * @return start from 1. When there is no parameter yet, return 0. */ public int paramIndex() { return paramIndex; } protected final char scanChar() { return ch = sql[++curIndex]; } /** * @param skip if 1, then equals to {@link #scanChar()} */ protected final char scanChar(int skip) { return ch = sql[curIndex += skip]; } protected final boolean hasChars(int howMany) { return curIndex + howMany <= eofIndex; } protected final boolean eof() { return curIndex >= eofIndex; } private MySQLToken nextTokenInternal() throws SQLSyntaxErrorException { switch (ch) { case '0': switch (sql[curIndex + 1]) { case 'x': scanChar(2); scanHexaDecimal(false); return token; case 'b': scanChar(2); scanBitField(false); return token; } case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': scanNumber(); return token; case '.': if (CharTypes.isDigit(sql[curIndex + 1])) { scanNumber(); } else { scanChar(); token = MySQLToken.PUNC_DOT; } return token; case '\'': case '"': scanString(); return token; case 'n': case 'N': if (sql[curIndex + 1] == '\'') { scanChar(); scanString(); token = MySQLToken.LITERAL_NCHARS; return token; } scanIdentifier(); return token; case 'x': case 'X': if (sql[curIndex + 1] == '\'') { scanChar(2); scanHexaDecimal(true); return token; } scanIdentifier(); return token; case 'b': case 'B': if (sql[curIndex + 1] == '\'') { scanChar(2); scanBitField(true); return token; } scanIdentifier(); return token; case '@': if (sql[curIndex + 1] == '@') { scanSystemVariable(); return token; } scanUserVariable(); return token; case '?': scanChar(); token = MySQLToken.QUESTION_MARK; ++paramIndex; return token; case '(': scanChar(); token = MySQLToken.PUNC_LEFT_PAREN; return token; case ')': scanChar(); token = MySQLToken.PUNC_RIGHT_PAREN; return token; case '[': scanChar(); token = MySQLToken.PUNC_LEFT_BRACKET; return token; case ']': scanChar(); token = MySQLToken.PUNC_RIGHT_BRACKET; return token; case '{': scanChar(); token = MySQLToken.PUNC_LEFT_BRACE; return token; case '}': scanChar(); token = MySQLToken.PUNC_RIGHT_BRACE; return token; case ',': scanChar(); token = MySQLToken.PUNC_COMMA; return token; case ';': scanChar(); token = MySQLToken.PUNC_SEMICOLON; return token; case ':': if (sql[curIndex + 1] == '=') { scanChar(2); token = MySQLToken.OP_ASSIGN; return token; } scanChar(); token = MySQLToken.PUNC_COLON; return token; case '=': scanChar(); token = MySQLToken.OP_EQUALS; return token; case '~': scanChar(); token = MySQLToken.OP_TILDE; return token; case '*': if (inCStyleComment && sql[curIndex + 1] == '/') { inCStyleComment = false; inCStyleCommentIgnore = false; scanChar(2); token = MySQLToken.PUNC_C_STYLE_COMMENT_END; return token; } scanChar(); token = MySQLToken.OP_ASTERISK; return token; case '-': scanChar(); token = MySQLToken.OP_MINUS; return token; case '+': scanChar(); token = MySQLToken.OP_PLUS; return token; case '^': scanChar(); token = MySQLToken.OP_CARET; return token; case '/': scanChar(); token = MySQLToken.OP_SLASH; return token; case '%': scanChar(); token = MySQLToken.OP_PERCENT; return token; case '&': if (sql[curIndex + 1] == '&') { scanChar(2); token = MySQLToken.OP_LOGICAL_AND; return token; } scanChar(); token = MySQLToken.OP_AMPERSAND; return token; case '|': if (sql[curIndex + 1] == '|') { scanChar(2); token = MySQLToken.OP_LOGICAL_OR; return token; } scanChar(); token = MySQLToken.OP_VERTICAL_BAR; return token; case '!': if (sql[curIndex + 1] == '=') { scanChar(2); token = MySQLToken.OP_NOT_EQUALS; return token; } scanChar(); token = MySQLToken.OP_EXCLAMATION; return token; case '>': switch (sql[curIndex + 1]) { case '=': scanChar(2); token = MySQLToken.OP_GREATER_OR_EQUALS; return token; case '>': scanChar(2); token = MySQLToken.OP_RIGHT_SHIFT; return token; default: scanChar(); token = MySQLToken.OP_GREATER_THAN; return token; } case '<': switch (sql[curIndex + 1]) { case '=': if (sql[curIndex + 2] == '>') { scanChar(3); token = MySQLToken.OP_NULL_SAFE_EQUALS; return token; } scanChar(2); token = MySQLToken.OP_LESS_OR_EQUALS; return token; case '>': scanChar(2); token = MySQLToken.OP_LESS_OR_GREATER; return token; case '<': scanChar(2); token = MySQLToken.OP_LEFT_SHIFT; return token; default: scanChar(); token = MySQLToken.OP_LESS_THAN; return token; } case '`': scanIdentifierWithAccent(); return token; default: if (CharTypes.isIdentifierChar(ch)) { scanIdentifier(); } else if (eof()) { token = MySQLToken.EOF; curIndex = eofIndex; // tokenPos = curIndex; } else { throw err("unsupported character: " + ch); } return token; } } public MySQLToken nextToken() throws SQLSyntaxErrorException { if (tokenCache2 != null) { tokenCache2 = null; return tokenCache; } if (tokenCache != null) { tokenCache = null; return token; } if (token == MySQLToken.EOF) { throw new SQLSyntaxErrorException("eof for sql is already reached, cannot get new token"); } MySQLToken t; do { skipSeparator(); t = nextTokenInternal(); } while (inCStyleComment && inCStyleCommentIgnore || MySQLToken.PUNC_C_STYLE_COMMENT_END == t); return t; } protected boolean inCStyleComment; protected boolean inCStyleCommentIgnore; protected int offsetCache; protected int sizeCache; /** * first <code>@</code> is included */ protected void scanUserVariable() throws SQLSyntaxErrorException { if (ch != '@') throw err("first char must be @"); offsetCache = curIndex; sizeCache = 1; boolean dq = false; switch (scanChar()) { case '"': dq = true; case '\'': loop1: for (++sizeCache;; ++sizeCache) { switch (scanChar()) { case '\\': ++sizeCache; scanChar(); break; case '"': if (dq) { ++sizeCache; if (scanChar() == '"') { break; } break loop1; } break; case '\'': if (!dq) { ++sizeCache; if (scanChar() == '\'') { break; } break loop1; } break; } } break; case '`': loop1: for (++sizeCache;; ++sizeCache) { switch (scanChar()) { case '`': ++sizeCache; if (scanChar() == '`') { break; } break loop1; } } break; default: for (; CharTypes.isIdentifierChar(ch) || ch == '.'; ++sizeCache) { scanChar(); } } stringValue = new String(sql, offsetCache, sizeCache); token = MySQLToken.USR_VAR; } /** * first <code>@@</code> is included */ protected void scanSystemVariable() throws SQLSyntaxErrorException { if (ch != '@' || sql[curIndex + 1] != '@') throw err("first char must be @@"); offsetCache = curIndex + 2; sizeCache = 0; scanChar(2); if (ch == '`') { for (++sizeCache;; ++sizeCache) { if (scanChar() == '`') { ++sizeCache; if (scanChar() != '`') { break; } } } } else { for (; CharTypes.isIdentifierChar(ch); ++sizeCache) { scanChar(); } } updateStringValue(sql, offsetCache, sizeCache); token = MySQLToken.SYS_VAR; } protected void scanString() throws SQLSyntaxErrorException { boolean dq = false; if (ch == '\'') { } else if (ch == '"') { dq = true; } else { throw err("first char must be \" or '"); } offsetCache = curIndex; int size = 1; sbuf[0] = '\''; if (dq) { loop: while (true) { switch (scanChar()) { case '\'': putChar('\\', size++); putChar('\'', size++); break; case '\\': putChar('\\', size++); putChar(scanChar(), size++); continue; case '"': if (sql[curIndex + 1] == '"') { putChar('"', size++); scanChar(); continue; } putChar('\'', size++); scanChar(); break loop; default: if (eof()) { throw err("unclosed string"); } putChar(ch, size++); continue; } } } else { loop: while (true) { switch (scanChar()) { case '\\': putChar('\\', size++); putChar(scanChar(), size++); continue; case '\'': if (sql[curIndex + 1] == '\'') { putChar('\\', size++); putChar(scanChar(), size++); continue; } putChar('\'', size++); scanChar(); break loop; default: if (eof()) { throw err("unclosed string"); } putChar(ch, size++); continue; } } } sizeCache = size; stringValue = new String(sbuf, 0, size); token = MySQLToken.LITERAL_CHARS; } /** * Append a character to sbuf. */ protected final void putChar(char ch, int index) { if (index >= sbuf.length) { char[] newsbuf = new char[sbuf.length * 2]; System.arraycopy(sbuf, 0, newsbuf, 0, sbuf.length); sbuf = newsbuf; } sbuf[index] = ch; } /** * @param quoteMode if false: first <code>0x</code> has been skipped; if * true: first <code>x'</code> has been skipped */ protected void scanHexaDecimal(boolean quoteMode) throws SQLSyntaxErrorException { offsetCache = curIndex; for (; CharTypes.isHex(ch); scanChar()); sizeCache = curIndex - offsetCache; // if (sizeCache <= 0) { // throw err("expect at least one hexdigit"); // } if (quoteMode) { if (ch != '\'') { throw err("invalid char for hex: " + ch); } scanChar(); } else if (CharTypes.isIdentifierChar(ch)) { scanIdentifierFromNumber(offsetCache - 2, sizeCache + 2); return; } token = MySQLToken.LITERAL_HEX; } /** * @param quoteMode if false: first <code>0b</code> has been skipped; if * true: first <code>b'</code> has been skipped */ protected void scanBitField(boolean quoteMode) throws SQLSyntaxErrorException { offsetCache = curIndex; for (; ch == '0' || ch == '1'; scanChar()); sizeCache = curIndex - offsetCache; // if (sizeCache <= 0) { // throw err("expect at least one bit"); // } if (quoteMode) { if (ch != '\'') { throw err("invalid char for bit: " + ch); } scanChar(); } else if (CharTypes.isIdentifierChar(ch)) { scanIdentifierFromNumber(offsetCache - 2, sizeCache + 2); return; } token = MySQLToken.LITERAL_BIT; stringValue = new String(sql, offsetCache, sizeCache); } /** * if first char is <code>.</code>, token may be {@link MySQLToken#PUNC_DOT} * if invalid char is presented after <code>.</code> */ protected void scanNumber() throws SQLSyntaxErrorException { offsetCache = curIndex; sizeCache = 1; final boolean fstDot = ch == '.'; boolean dot = fstDot; boolean sign = false; int state = fstDot ? 1 : 0; for (; scanChar() != MySQLLexer.EOI; ++sizeCache) { switch (state) { case 0: if (CharTypes.isDigit(ch)) { } else if (ch == '.') { dot = true; state = 1; } else if (ch == 'e' || ch == 'E') { state = 3; } else if (CharTypes.isIdentifierChar(ch)) { scanIdentifierFromNumber(offsetCache, sizeCache); return; } else { token = MySQLToken.LITERAL_NUM_PURE_DIGIT; return; } break; case 1: if (CharTypes.isDigit(ch)) { state = 2; } else if (ch == 'e' || ch == 'E') { state = 3; } else if (CharTypes.isIdentifierChar(ch) && fstDot) { sizeCache = 1; ch = sql[curIndex = offsetCache + 1]; token = MySQLToken.PUNC_DOT; return; } else { token = MySQLToken.LITERAL_NUM_MIX_DIGIT; return; } break; case 2: if (CharTypes.isDigit(ch)) { } else if (ch == 'e' || ch == 'E') { state = 3; } else if (CharTypes.isIdentifierChar(ch) && fstDot) { sizeCache = 1; ch = sql[curIndex = offsetCache + 1]; token = MySQLToken.PUNC_DOT; return; } else { token = MySQLToken.LITERAL_NUM_MIX_DIGIT; return; } break; case 3: if (CharTypes.isDigit(ch)) { state = 5; } else if (ch == '+' || ch == '-') { sign = true; state = 4; } else if (fstDot) { sizeCache = 1; ch = sql[curIndex = offsetCache + 1]; token = MySQLToken.PUNC_DOT; return; } else if (!dot) { if (CharTypes.isIdentifierChar(ch)) { scanIdentifierFromNumber(offsetCache, sizeCache); } else { updateStringValue(sql, offsetCache, sizeCache); MySQLToken tok = keywods.getKeyword(stringValueUppercase); token = tok == null ? MySQLToken.IDENTIFIER : tok; } return; } else { throw err("invalid char after '.' and 'e' for as part of number: " + ch); } break; case 4: if (CharTypes.isDigit(ch)) { state = 5; break; } else if (fstDot) { sizeCache = 1; ch = sql[curIndex = offsetCache + 1]; token = MySQLToken.PUNC_DOT; } else if (!dot) { ch = sql[--curIndex]; --sizeCache; updateStringValue(sql, offsetCache, sizeCache); MySQLToken tok = keywods.getKeyword(stringValueUppercase); token = tok == null ? MySQLToken.IDENTIFIER : tok; } else { throw err("expect digit char after SIGN for 'e': " + ch); } return; case 5: if (CharTypes.isDigit(ch)) { break; } else if (CharTypes.isIdentifierChar(ch)) { if (fstDot) { sizeCache = 1; ch = sql[curIndex = offsetCache + 1]; token = MySQLToken.PUNC_DOT; } else if (!dot) { if (sign) { ch = sql[curIndex = offsetCache]; scanIdentifierFromNumber(curIndex, 0); } else { scanIdentifierFromNumber(offsetCache, sizeCache); } } else { token = MySQLToken.LITERAL_NUM_MIX_DIGIT; } } else { token = MySQLToken.LITERAL_NUM_MIX_DIGIT; } return; } } switch (state) { case 0: token = MySQLToken.LITERAL_NUM_PURE_DIGIT; return; case 1: if (fstDot) { token = MySQLToken.PUNC_DOT; return; } case 2: case 5: token = MySQLToken.LITERAL_NUM_MIX_DIGIT; return; case 3: if (fstDot) { sizeCache = 1; ch = sql[curIndex = offsetCache + 1]; token = MySQLToken.PUNC_DOT; } else if (!dot) { updateStringValue(sql, offsetCache, sizeCache); MySQLToken tok = keywods.getKeyword(stringValueUppercase); token = tok == null ? MySQLToken.IDENTIFIER : tok; } else { throw err("expect digit char after SIGN for 'e': " + ch); } return; case 4: if (fstDot) { sizeCache = 1; ch = sql[curIndex = offsetCache + 1]; token = MySQLToken.PUNC_DOT; } else if (!dot) { ch = sql[--curIndex]; --sizeCache; updateStringValue(sql, offsetCache, sizeCache); MySQLToken tok = keywods.getKeyword(stringValueUppercase); token = tok == null ? MySQLToken.IDENTIFIER : tok; } else { throw err("expect digit char after SIGN for 'e': " + ch); } return; } } /** * NOTE: {@link MySQLToken#IDENTIFIER id} dosn't include <code>'.'</code> * for sake of performance issue (based on <i>shaojin.wensj</i>'s design). * However, it is not convenient for MySQL compatibility. e.g. * <code>".123f"</code> will be regarded as <code>".123"</code> and * <code>"f"</code> in MySQL, but in this {@link MySQLLexer}, it will be * <code>"."</code> and <code>"123f"</code> because <code>".123f"</code> may * be part of <code>"db1.123f"</code> and <code>"123f"</code> is the table * name. * * @param initSize how many char has already been consumed */ private void scanIdentifierFromNumber(int initOffset, int initSize) throws SQLSyntaxErrorException { offsetCache = initOffset; sizeCache = initSize; for (; CharTypes.isIdentifierChar(ch); ++sizeCache) { scanChar(); } updateStringValue(sql, offsetCache, sizeCache); MySQLToken tok = keywods.getKeyword(stringValueUppercase); token = tok == null ? MySQLToken.IDENTIFIER : tok; } /** * id is NOT included in <code>`</code>. */ protected void scanIdentifier() throws SQLSyntaxErrorException { if (ch == '$') { if (scanChar() == '{') { scanPlaceHolder(); } else { scanIdentifierFromNumber(curIndex - 1, 1); } } else { scanIdentifierFromNumber(curIndex, 0); } } /** * not SQL syntax */ protected void scanPlaceHolder() throws SQLSyntaxErrorException { offsetCache = curIndex + 1; sizeCache = 0; for (scanChar(); ch != '}' && !eof(); ++sizeCache) { scanChar(); } if (ch == '}') scanChar(); updateStringValue(sql, offsetCache, sizeCache); token = MySQLToken.PLACE_HOLDER; } /** * id is included in <code>`</code>. first <code>`</code> is included */ protected void scanIdentifierWithAccent() throws SQLSyntaxErrorException { offsetCache = curIndex; for (; scanChar() != MySQLLexer.EOI;) { if (ch == '`' && scanChar() != '`') { break; } } updateStringValue(sql, offsetCache, sizeCache = curIndex - offsetCache); token = MySQLToken.IDENTIFIER; } /** * skip whitespace and comment */ protected void skipSeparator() { for (; !eof();) { for (; CharTypes.isWhitespace(ch); scanChar()); switch (ch) { case '#': // MySQL specified for (; scanChar() != '\n';) { if (eof()) { return; } } scanChar(); continue; case '/': if (hasChars(2) && '*' == sql[curIndex + 1]) { boolean commentSkip; if ('!' == sql[curIndex + 2]) { scanChar(3); inCStyleComment = true; inCStyleCommentIgnore = false; commentSkip = false; // MySQL use 5 digits to indicate version. 50508 means // MySQL 5.5.8 if (hasChars(5) && CharTypes.isDigit(ch) && CharTypes.isDigit(sql[curIndex + 1]) && CharTypes.isDigit(sql[curIndex + 2]) && CharTypes.isDigit(sql[curIndex + 3]) && CharTypes.isDigit(sql[curIndex + 4])) { int version = ch - '0'; version *= 10; version += sql[curIndex + 1] - '0'; version *= 10; version += sql[curIndex + 2] - '0'; version *= 10; version += sql[curIndex + 3] - '0'; version *= 10; version += sql[curIndex + 4] - '0'; scanChar(5); if (version > C_STYLE_COMMENT_VERSION) { inCStyleCommentIgnore = true; } } skipSeparator(); } else { scanChar(2); commentSkip = true; } if (commentSkip) { for (int state = 0; !eof(); scanChar()) { if (state == 0) { if ('*' == ch) { state = 1; } } else { if ('/' == ch) { scanChar(); break; } else if ('*' != ch) { state = 0; } } } continue; } } return; case '-': if (hasChars(3) && '-' == sql[curIndex + 1] && CharTypes.isWhitespace(sql[curIndex + 2])) { scanChar(3); for (; !eof(); scanChar()) { if ('\n' == ch) { scanChar(); break; } } continue; } default: return; } } } /** * always throw SQLSyntaxErrorException */ protected SQLSyntaxErrorException err(String msg) throws SQLSyntaxErrorException { String errMsg = msg + ". " + toString(); throw new SQLSyntaxErrorException(errMsg); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()).append('@').append(hashCode()).append('{'); String sqlLeft = new String(sql, curIndex, sql.length - curIndex); sb.append("curIndex=") .append(curIndex) .append(", ch=") .append(ch) .append(", token=") .append(token) .append(", sqlLeft=") .append(sqlLeft) .append(", sql=") .append(sql); sb.append('}'); return sb.toString(); } /** * {@link #token} must be {@link MySQLToken#LITERAL_NUM_PURE_DIGIT} */ public Number integerValue() { // 2147483647 // 9223372036854775807 if (sizeCache < 10 || sizeCache == 10 && (sql[offsetCache] < '2' || sql[offsetCache] == '2' && sql[offsetCache + 1] == '0')) { int rst = 0; int end = offsetCache + sizeCache; for (int i = offsetCache; i < end; ++i) { rst = (rst << 3) + (rst << 1); rst += sql[i] - '0'; } return rst; } else if (sizeCache < 19 || sizeCache == 19 && sql[offsetCache] < '9') { long rst = 0; int end = offsetCache + sizeCache; for (int i = offsetCache; i < end; ++i) { rst = (rst << 3) + (rst << 1); rst += sql[i] - '0'; } return rst; } else { return new BigInteger(new String(sql, offsetCache, sizeCache), 10); } } public BigDecimal decimalValue() { // QS_TODO [performance enhance]: prevent BigDecimal's parser return new BigDecimal(sql, offsetCache, sizeCache); } /** * if {@link #stringValue()} returns "'abc\\'d'", then "abc\\'d" is appended */ public void appendStringContent(StringBuilder sb) { sb.append(sbuf, 1, sizeCache - 2); } /** * make sense for those types of token:<br/> * {@link MySQLToken#USR_VAR}: e.g. "@var1", "@'mary''s'";<br/> * {@link MySQLToken#SYS_VAR}: e.g. "var2";<br/> * {@link MySQLToken#LITERAL_CHARS}, {@link MySQLToken#LITERAL_NCHARS}: e.g. * "'ab\\'c'";<br/> * {@link MySQLToken#LITERAL_BIT}: e.g. "0101" <br/> * {@link MySQLToken#IDENTIFIER} */ public final String stringValue() { return stringValue; } /** * for {@link MySQLToken#IDENTIFIER}, {@link MySQLToken#SYS_VAR} */ public final String stringValueUppercase() { return stringValueUppercase; } }