/* * 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-5-10) */ 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_AS; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_BY; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_FOR; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_GROUP; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_JOIN; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_ORDER; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_OUTER; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_UNION; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.KW_WITH; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.LITERAL_CHARS; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.PUNC_COMMA; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.PUNC_LEFT_PAREN; import static com.alibaba.cobar.parser.recognizer.mysql.MySQLToken.PUNC_RIGHT_PAREN; import java.sql.SQLSyntaxErrorException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import com.alibaba.cobar.parser.ast.expression.Expression; import com.alibaba.cobar.parser.ast.expression.misc.QueryExpression; import com.alibaba.cobar.parser.ast.expression.primary.Identifier; import com.alibaba.cobar.parser.ast.fragment.GroupBy; import com.alibaba.cobar.parser.ast.fragment.OrderBy; import com.alibaba.cobar.parser.ast.fragment.SortOrder; import com.alibaba.cobar.parser.ast.fragment.tableref.IndexHint; import com.alibaba.cobar.parser.ast.fragment.tableref.InnerJoin; import com.alibaba.cobar.parser.ast.fragment.tableref.NaturalJoin; import com.alibaba.cobar.parser.ast.fragment.tableref.OuterJoin; import com.alibaba.cobar.parser.ast.fragment.tableref.StraightJoin; import com.alibaba.cobar.parser.ast.fragment.tableref.SubqueryFactor; import com.alibaba.cobar.parser.ast.fragment.tableref.TableRefFactor; import com.alibaba.cobar.parser.ast.fragment.tableref.TableReference; import com.alibaba.cobar.parser.ast.fragment.tableref.TableReferences; import com.alibaba.cobar.parser.ast.stmt.dml.DMLQueryStatement; import com.alibaba.cobar.parser.ast.stmt.dml.DMLSelectStatement; import com.alibaba.cobar.parser.ast.stmt.dml.DMLSelectUnionStatement; 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 MySQLDMLParser extends MySQLParser { protected MySQLExprParser exprParser; public MySQLDMLParser(MySQLLexer lexer, MySQLExprParser exprParser) { super(lexer); this.exprParser = exprParser; } /** * nothing has been pre-consumed * * @return null if there is no order by */ protected GroupBy groupBy() throws SQLSyntaxErrorException { if (lexer.token() != KW_GROUP) { return null; } lexer.nextToken(); match(KW_BY); Expression expr = exprParser.expression(); SortOrder order = SortOrder.ASC; GroupBy groupBy; switch (lexer.token()) { case KW_DESC: order = SortOrder.DESC; case KW_ASC: lexer.nextToken(); default: break; } switch (lexer.token()) { case KW_WITH: lexer.nextToken(); matchIdentifier("ROLLUP"); return new GroupBy(expr, order, true); case PUNC_COMMA: break; default: return new GroupBy(expr, order, false); } for (groupBy = new GroupBy().addOrderByItem(expr, order); lexer.token() == PUNC_COMMA;) { lexer.nextToken(); order = SortOrder.ASC; expr = exprParser.expression(); switch (lexer.token()) { case KW_DESC: order = SortOrder.DESC; case KW_ASC: lexer.nextToken(); default: break; } groupBy.addOrderByItem(expr, order); if (lexer.token() == KW_WITH) { lexer.nextToken(); matchIdentifier("ROLLUP"); return groupBy.setWithRollup(); } } return groupBy; } /** * nothing has been pre-consumed * * @return null if there is no order by */ protected OrderBy orderBy() throws SQLSyntaxErrorException { if (lexer.token() != KW_ORDER) { return null; } lexer.nextToken(); match(KW_BY); Expression expr = exprParser.expression(); SortOrder order = SortOrder.ASC; OrderBy orderBy; switch (lexer.token()) { case KW_DESC: order = SortOrder.DESC; case KW_ASC: if (lexer.nextToken() != PUNC_COMMA) { return new OrderBy(expr, order); } case PUNC_COMMA: orderBy = new OrderBy(); orderBy.addOrderByItem(expr, order); break; default: return new OrderBy(expr, order); } for (; lexer.token() == PUNC_COMMA;) { lexer.nextToken(); order = SortOrder.ASC; expr = exprParser.expression(); switch (lexer.token()) { case KW_DESC: order = SortOrder.DESC; case KW_ASC: lexer.nextToken(); } orderBy.addOrderByItem(expr, order); } return orderBy; } /** * @param id never null */ protected List<Identifier> buildIdList(Identifier id) throws SQLSyntaxErrorException { if (lexer.token() != PUNC_COMMA) { List<Identifier> list = new ArrayList<Identifier>(1); list.add(id); return list; } List<Identifier> list = new LinkedList<Identifier>(); list.add(id); for (; lexer.token() == PUNC_COMMA;) { lexer.nextToken(); id = identifier(); list.add(id); } return list; } /** * <code>(id (',' id)*)?</code> * * @return never null or empty. {@link LinkedList} is possible */ protected List<Identifier> idList() throws SQLSyntaxErrorException { return buildIdList(identifier()); } /** * <code>( idName (',' idName)*)? ')'</code> * * @return empty list if emtpy id list */ protected List<String> idNameList() throws SQLSyntaxErrorException { if (lexer.token() != IDENTIFIER) { match(PUNC_RIGHT_PAREN); return Collections.emptyList(); } List<String> list; String str = lexer.stringValue(); if (lexer.nextToken() == PUNC_COMMA) { list = new LinkedList<String>(); list.add(str); for (; lexer.token() == PUNC_COMMA;) { lexer.nextToken(); list.add(lexer.stringValue()); match(IDENTIFIER); } } else { list = new ArrayList<String>(1); list.add(str); } match(PUNC_RIGHT_PAREN); return list; } /** * @return never null */ protected TableReferences tableRefs() throws SQLSyntaxErrorException { TableReference ref = tableReference(); return buildTableReferences(ref); } private TableReferences buildTableReferences(TableReference ref) throws SQLSyntaxErrorException { List<TableReference> list; if (lexer.token() == PUNC_COMMA) { list = new LinkedList<TableReference>(); list.add(ref); for (; lexer.token() == PUNC_COMMA;) { lexer.nextToken(); ref = tableReference(); list.add(ref); } } else { list = new ArrayList<TableReference>(1); list.add(ref); } return new TableReferences(list); } private TableReference tableReference() throws SQLSyntaxErrorException { TableReference ref = tableFactor(); return buildTableReference(ref); } @SuppressWarnings("unchecked") private TableReference buildTableReference(TableReference ref) throws SQLSyntaxErrorException { for (;;) { Expression on; List<String> using; TableReference temp; boolean isOut = false; boolean isLeft = true; switch (lexer.token()) { case KW_INNER: case KW_CROSS: lexer.nextToken(); case KW_JOIN: lexer.nextToken(); temp = tableFactor(); switch (lexer.token()) { case KW_ON: lexer.nextToken(); on = exprParser.expression(); ref = new InnerJoin(ref, temp, on); break; case KW_USING: lexer.nextToken(); match(PUNC_LEFT_PAREN); using = idNameList(); ref = new InnerJoin(ref, temp, using); break; default: ref = new InnerJoin(ref, temp); break; } break; case KW_STRAIGHT_JOIN: lexer.nextToken(); temp = tableFactor(); switch (lexer.token()) { case KW_ON: lexer.nextToken(); on = exprParser.expression(); ref = new StraightJoin(ref, temp, on); break; default: ref = new StraightJoin(ref, temp); break; } break; case KW_RIGHT: isLeft = false; case KW_LEFT: lexer.nextToken(); if (lexer.token() == KW_OUTER) { lexer.nextToken(); } match(KW_JOIN); temp = tableReference(); switch (lexer.token()) { case KW_ON: lexer.nextToken(); on = exprParser.expression(); ref = new OuterJoin(isLeft, ref, temp, on); break; case KW_USING: lexer.nextToken(); match(PUNC_LEFT_PAREN); using = idNameList(); ref = new OuterJoin(isLeft, ref, temp, using); break; default: Object condition = temp.removeLastConditionElement(); if (condition instanceof Expression) { ref = new OuterJoin(isLeft, ref, temp, (Expression) condition); } else if (condition instanceof List) { ref = new OuterJoin(isLeft, ref, temp, (List<String>) condition); } else { throw err("conditionExpr cannot be null for outer join"); } break; } break; case KW_NATURAL: lexer.nextToken(); switch (lexer.token()) { case KW_RIGHT: isLeft = false; case KW_LEFT: lexer.nextToken(); if (lexer.token() == KW_OUTER) { lexer.nextToken(); } isOut = true; case KW_JOIN: lexer.nextToken(); temp = tableFactor(); ref = new NaturalJoin(isOut, isLeft, ref, temp); break; default: throw err("unexpected token after NATURAL for natural join:" + lexer.token()); } break; default: return ref; } } } private TableReference tableFactor() throws SQLSyntaxErrorException { String alias = null; switch (lexer.token()) { case PUNC_LEFT_PAREN: lexer.nextToken(); Object ref = trsOrQuery(); match(PUNC_RIGHT_PAREN); if (ref instanceof QueryExpression) { alias = as(); return new SubqueryFactor((QueryExpression) ref, alias); } return (TableReferences) ref; case IDENTIFIER: Identifier table = identifier(); alias = as(); List<IndexHint> hintList = hintList(); return new TableRefFactor(table, alias, hintList); default: throw err("unexpected token for tableFactor: " + lexer.token()); } } /** * @return never empty. upper-case if id format. * <code>"alias1" |"`al`ias1`" | "'alias1'" | "_latin1'alias1'"</code> */ protected String as() throws SQLSyntaxErrorException { if (lexer.token() == KW_AS) { lexer.nextToken(); } StringBuilder alias = new StringBuilder(); boolean id = false; if (lexer.token() == IDENTIFIER) { alias.append(lexer.stringValueUppercase()); id = true; lexer.nextToken(); } if (lexer.token() == LITERAL_CHARS) { if (!id || id && alias.charAt(0) == '_') { alias.append(lexer.stringValue()); lexer.nextToken(); } } return alias.length() > 0 ? alias.toString() : null; } /** * @return type of {@link QueryExpression} or {@link TableReferences} */ private Object trsOrQuery() throws SQLSyntaxErrorException { Object ref; switch (lexer.token()) { case KW_SELECT: DMLSelectStatement select = selectPrimary(); return buildUnionSelect(select); case PUNC_LEFT_PAREN: lexer.nextToken(); ref = trsOrQuery(); match(PUNC_RIGHT_PAREN); if (ref instanceof QueryExpression) { if (ref instanceof DMLSelectStatement) { QueryExpression rst = buildUnionSelect((DMLSelectStatement) ref); if (rst != ref) { return rst; } } String alias = as(); if (alias != null) { ref = new SubqueryFactor((QueryExpression) ref, alias); } else { return ref; } } // ---- build factor complete--------------- ref = buildTableReference((TableReference) ref); // ---- build ref complete--------------- break; default: ref = tableReference(); } List<TableReference> list; if (lexer.token() == PUNC_COMMA) { list = new LinkedList<TableReference>(); list.add((TableReference) ref); for (; lexer.token() == PUNC_COMMA;) { lexer.nextToken(); ref = tableReference(); list.add((TableReference) ref); } return new TableReferences(list); } list = new ArrayList<TableReference>(1); list.add((TableReference) ref); return new TableReferences(list); } /** * @return null if there is no hint */ private List<IndexHint> hintList() throws SQLSyntaxErrorException { IndexHint hint = hint(); if (hint == null) return null; List<IndexHint> list; IndexHint hint2 = hint(); if (hint2 == null) { list = new ArrayList<IndexHint>(1); list.add(hint); return list; } list = new LinkedList<IndexHint>(); list.add(hint); list.add(hint2); for (; (hint2 = hint()) != null; list.add(hint2)); return list; } /** * @return null if there is no hint */ private IndexHint hint() throws SQLSyntaxErrorException { IndexHint.IndexAction action; switch (lexer.token()) { case KW_USE: action = IndexHint.IndexAction.USE; break; case KW_IGNORE: action = IndexHint.IndexAction.IGNORE; break; case KW_FORCE: action = IndexHint.IndexAction.FORCE; break; default: return null; } IndexHint.IndexType type; switch (lexer.nextToken()) { case KW_INDEX: type = IndexHint.IndexType.INDEX; break; case KW_KEY: type = IndexHint.IndexType.KEY; break; default: throw err("must be INDEX or KEY for hint type, not " + lexer.token()); } IndexHint.IndexScope scope = IndexHint.IndexScope.ALL; if (lexer.nextToken() == KW_FOR) { switch (lexer.nextToken()) { case KW_JOIN: lexer.nextToken(); scope = IndexHint.IndexScope.JOIN; break; case KW_ORDER: lexer.nextToken(); match(KW_BY); scope = IndexHint.IndexScope.ORDER_BY; break; case KW_GROUP: lexer.nextToken(); match(KW_BY); scope = IndexHint.IndexScope.GROUP_BY; break; default: throw err("must be JOIN or ORDER or GROUP for hint scope, not " + lexer.token()); } } match(PUNC_LEFT_PAREN); List<String> indexList = idNameList(); return new IndexHint(action, type, scope, indexList); } /** * @return argument itself if there is no union */ protected DMLQueryStatement buildUnionSelect(DMLSelectStatement select) throws SQLSyntaxErrorException { if (lexer.token() != KW_UNION) { return select; } DMLSelectUnionStatement union = new DMLSelectUnionStatement(select); for (; lexer.token() == KW_UNION;) { lexer.nextToken(); boolean isAll = false; switch (lexer.token()) { case KW_ALL: isAll = true; case KW_DISTINCT: lexer.nextToken(); break; } select = selectPrimary(); union.addSelect(select, isAll); } union.setOrderBy(orderBy()).setLimit(limit()); return union; } protected DMLSelectStatement selectPrimary() throws SQLSyntaxErrorException { switch (lexer.token()) { case KW_SELECT: return select(); case PUNC_LEFT_PAREN: lexer.nextToken(); DMLSelectStatement select = selectPrimary(); match(PUNC_RIGHT_PAREN); return select; default: throw err("unexpected token: " + lexer.token()); } } /** * first token is {@link MySQLToken#KW_SELECT SELECT} which has been scanned * but not yet consumed */ public DMLSelectStatement select() throws SQLSyntaxErrorException { return new MySQLDMLSelectParser(lexer, exprParser).select(); } }