/* * 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. */ package com.alibaba.cobar.parser.recognizer.mysql.syntax; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.IDENTIFIER; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_LIMIT; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.PUNC_DOT; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.SYS_VAR; import java.sql.SQLSyntaxErrorException; import java.util.HashMap; import java.util.Map; import com.alibaba.cobar.parser.ast.expression.primary.Identifier; import com.alibaba.cobar.parser.ast.expression.primary.ParamMarker; import com.alibaba.cobar.parser.ast.expression.primary.PlaceHolder; import com.alibaba.cobar.parser.ast.expression.primary.SysVarPrimary; import com.alibaba.cobar.parser.ast.expression.primary.Wildcard; import com.alibaba.cobar.parser.ast.fragment.Limit; import com.alibaba.cobar.parser.ast.fragment.VariableScope; import com.alibaba.cobar.parser.recognizer.mysql.MySQLToken; import com.alibaba.cobar.parser.recognizer.mysql.lexer.MySQLLexer; /** * @author <a href="mailto:shuo.qius@alibaba-inc.com">QIU Shuo</a> */ public abstract class MySQLParser { public static final String DEFAULT_CHARSET = "utf-8"; protected final MySQLLexer lexer; public MySQLParser(MySQLLexer lexer) { this(lexer, true); } public MySQLParser(MySQLLexer lexer, boolean cacheEvalRst) { this.lexer = lexer; this.cacheEvalRst = cacheEvalRst; } private static enum SpecialIdentifier { GLOBAL, LOCAL, SESSION } private static final Map<String, SpecialIdentifier> specialIdentifiers = new HashMap<String, SpecialIdentifier>(); static { specialIdentifiers.put("GLOBAL", SpecialIdentifier.GLOBAL); specialIdentifiers.put("SESSION", SpecialIdentifier.SESSION); specialIdentifiers.put("LOCAL", SpecialIdentifier.LOCAL); } protected final boolean cacheEvalRst; /** * @return type of {@link Wildcard} is possible. never null * @throws SQLSyntaxErrorException if identifier dose not matched */ public Identifier identifier() throws SQLSyntaxErrorException { if (lexer.token() == null) { lexer.nextToken(); } Identifier id; switch (lexer.token()) { case OP_ASTERISK: lexer.nextToken(); Wildcard wc = new Wildcard(null); wc.setCacheEvalRst(cacheEvalRst); return wc; case IDENTIFIER: id = new Identifier(null, lexer.stringValue(), lexer.stringValueUppercase()); id.setCacheEvalRst(cacheEvalRst); lexer.nextToken(); break; default: throw err("expect id or * after '.'"); } for (; lexer.token() == PUNC_DOT;) { switch (lexer.nextToken()) { case OP_ASTERISK: lexer.nextToken(); Wildcard wc = new Wildcard(id); wc.setCacheEvalRst(cacheEvalRst); return wc; case IDENTIFIER: id = new Identifier(id, lexer.stringValue(), lexer.stringValueUppercase()); id.setCacheEvalRst(cacheEvalRst); lexer.nextToken(); break; default: throw err("expect id or * after '.'"); } } return id; } /** * first token must be {@link MySQLToken#SYS_VAR} */ public SysVarPrimary systemVariale() throws SQLSyntaxErrorException { SysVarPrimary sys; VariableScope scope = VariableScope.SESSION; String str = lexer.stringValue(); String strUp = lexer.stringValueUppercase(); match(SYS_VAR); SpecialIdentifier si = specialIdentifiers.get(strUp); if (si != null) { switch (si) { case GLOBAL: scope = VariableScope.GLOBAL; case SESSION: case LOCAL: match(PUNC_DOT); str = lexer.stringValue(); strUp = lexer.stringValueUppercase(); match(IDENTIFIER); sys = new SysVarPrimary(scope, str, strUp); sys.setCacheEvalRst(cacheEvalRst); return sys; } } sys = new SysVarPrimary(scope, str, strUp); sys.setCacheEvalRst(cacheEvalRst); return sys; } protected ParamMarker createParam(int index) { ParamMarker param = new ParamMarker(index); param.setCacheEvalRst(cacheEvalRst); return param; } protected PlaceHolder createPlaceHolder(String str, String strUp) { PlaceHolder ph = new PlaceHolder(str, strUp); ph.setCacheEvalRst(cacheEvalRst); return ph; } /** * nothing has been pre-consumed * * @return null if there is no order limit */ protected Limit limit() throws SQLSyntaxErrorException { if (lexer.token() != KW_LIMIT) { return null; } int paramIndex1; int paramIndex2; Number num1; switch (lexer.nextToken()) { case LITERAL_NUM_PURE_DIGIT: num1 = lexer.integerValue(); switch (lexer.nextToken()) { case PUNC_COMMA: switch (lexer.nextToken()) { case LITERAL_NUM_PURE_DIGIT: Number num2 = lexer.integerValue(); lexer.nextToken(); return new Limit(num1, num2); case QUESTION_MARK: paramIndex1 = lexer.paramIndex(); lexer.nextToken(); return new Limit(num1, createParam(paramIndex1)); default: throw err("expect digit or ? after , for limit"); } case IDENTIFIER: if ("OFFSET".equals(lexer.stringValueUppercase())) { switch (lexer.nextToken()) { case LITERAL_NUM_PURE_DIGIT: Number num2 = lexer.integerValue(); lexer.nextToken(); return new Limit(num2, num1); case QUESTION_MARK: paramIndex1 = lexer.paramIndex(); lexer.nextToken(); return new Limit(createParam(paramIndex1), num1); default: throw err("expect digit or ? after , for limit"); } } } return new Limit(new Integer(0), num1); case QUESTION_MARK: paramIndex1 = lexer.paramIndex(); switch (lexer.nextToken()) { case PUNC_COMMA: switch (lexer.nextToken()) { case LITERAL_NUM_PURE_DIGIT: num1 = lexer.integerValue(); lexer.nextToken(); return new Limit(createParam(paramIndex1), num1); case QUESTION_MARK: paramIndex2 = lexer.paramIndex(); lexer.nextToken(); return new Limit(createParam(paramIndex1), createParam(paramIndex2)); default: throw err("expect digit or ? after , for limit"); } case IDENTIFIER: if ("OFFSET".equals(lexer.stringValueUppercase())) { switch (lexer.nextToken()) { case LITERAL_NUM_PURE_DIGIT: num1 = lexer.integerValue(); lexer.nextToken(); return new Limit(num1, createParam(paramIndex1)); case QUESTION_MARK: paramIndex2 = lexer.paramIndex(); lexer.nextToken(); return new Limit(createParam(paramIndex2), createParam(paramIndex1)); default: throw err("expect digit or ? after , for limit"); } } } return new Limit(new Integer(0), createParam(paramIndex1)); default: throw err("expect digit or ? after limit"); } } /** * @param expectTextUppercase must be upper-case * @return index (start from 0) of expected text which is first matched. -1 * if none is matched. */ protected int equalsIdentifier(String... expectTextUppercases) throws SQLSyntaxErrorException { if (lexer.token() == MySQLToken.IDENTIFIER) { String id = lexer.stringValueUppercase(); for (int i = 0; i < expectTextUppercases.length; ++i) { if (expectTextUppercases[i].equals(id)) { return i; } } } return -1; } /** * @return index of expected token, start from 0 * @throws SQLSyntaxErrorException if no token is matched */ protected int matchIdentifier(String... expectTextUppercase) throws SQLSyntaxErrorException { if (expectTextUppercase == null || expectTextUppercase.length <= 0) throw new IllegalArgumentException("at least one expect token"); if (lexer.token() != MySQLToken.IDENTIFIER) { throw err("expect an id"); } String id = lexer.stringValueUppercase(); for (int i = 0; i < expectTextUppercase.length; ++i) { if (id == null ? expectTextUppercase[i] == null : id.equals(expectTextUppercase[i])) { lexer.nextToken(); return i; } } throw err("expect " + expectTextUppercase); } /** * @return index of expected token, start from 0 * @throws SQLSyntaxErrorException if no token is matched */ protected int match(MySQLToken... expectToken) throws SQLSyntaxErrorException { if (expectToken == null || expectToken.length <= 0) throw new IllegalArgumentException("at least one expect token"); MySQLToken token = lexer.token(); for (int i = 0; i < expectToken.length; ++i) { if (token == expectToken[i]) { if (token != MySQLToken.EOF || i < expectToken.length - 1) { lexer.nextToken(); } return i; } } throw err("expect " + expectToken); } /** * side-effect is forbidden */ protected SQLSyntaxErrorException err(String msg) throws SQLSyntaxErrorException { StringBuilder errmsg = new StringBuilder(); errmsg.append(msg).append(". lexer state: ").append(String.valueOf(lexer)); throw new SQLSyntaxErrorException(errmsg.toString()); } }