/*
* 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());
}
}