/* * Copyright 1999-2015 dangdang.com. * <p> * 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. * </p> */ package com.dangdang.ddframe.rdb.sharding.parser.visitor.basic.mysql; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLOrderBy; import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr; import com.alibaba.druid.sql.ast.expr.SQLAllColumnExpr; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr; import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr; import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectItem; import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlSelectGroupByExpr; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock; import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor; import com.dangdang.ddframe.rdb.sharding.exception.SQLParserException; import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AbstractSortableColumn; import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn; import com.dangdang.ddframe.rdb.sharding.parser.result.merger.AggregationColumn.AggregationType; import com.dangdang.ddframe.rdb.sharding.parser.result.merger.Limit; import com.dangdang.ddframe.rdb.sharding.parser.result.merger.OrderByColumn.OrderByType; import com.google.common.base.Optional; import com.google.common.base.Strings; import java.util.List; /** * MySQL的SELECT语句访问器. * * @author gaohongtao * @author zhangliang */ public class MySQLSelectVisitor extends AbstractMySQLVisitor { @Override protected void printSelectList(final List<SQLSelectItem> selectList) { super.printSelectList(selectList); // TODO 提炼成print,或者是否不应该由token的方式替换? printToken(getParseContext().getAutoGenTokenKey(), null); } @Override public boolean visit(final MySqlSelectQueryBlock x) { stepInQuery(); if (x.getFrom() instanceof SQLExprTableSource) { SQLExprTableSource tableExpr = (SQLExprTableSource) x.getFrom(); getParseContext().setCurrentTable(tableExpr.getExpr().toString(), Optional.fromNullable(tableExpr.getAlias())); } return super.visit(x); } /** * 解析 {@code SELECT item1,item2 FROM }中的item. * * @param x SELECT item 表达式 * @return true表示继续遍历AST, false表示终止遍历AST */ // TODO SELECT * 导致index不准,不支持SELECT *,且生产环境不建议使用SELECT * public boolean visit(final SQLSelectItem x) { getParseContext().increaseItemIndex(); if (Strings.isNullOrEmpty(x.getAlias())) { SQLExpr expr = x.getExpr(); if (expr instanceof SQLIdentifierExpr) { getParseContext().registerSelectItem(((SQLIdentifierExpr) expr).getName()); } else if (expr instanceof SQLPropertyExpr) { getParseContext().registerSelectItem(((SQLPropertyExpr) expr).getName()); } else if (expr instanceof SQLAllColumnExpr) { getParseContext().registerSelectItem("*"); } } else { getParseContext().registerSelectItem(x.getAlias()); } return super.visit(x); } @Override public boolean visit(final SQLAggregateExpr x) { if (!(x.getParent() instanceof SQLSelectItem)) { return super.visit(x); } AggregationType aggregationType; try { aggregationType = AggregationType.valueOf(x.getMethodName().toUpperCase()); } catch (final IllegalArgumentException ex) { return super.visit(x); } StringBuilder expression = new StringBuilder(); x.accept(new MySqlOutputVisitor(expression)); // TODO index获取不准,考虑使用别名替换 AggregationColumn column = new AggregationColumn(expression.toString(), aggregationType, Optional.fromNullable(((SQLSelectItem) x.getParent()).getAlias()), null == x.getOption() ? Optional.<String>absent() : Optional.of(x.getOption().toString()), getParseContext().getItemIndex()); getParseContext().getParsedResult().getMergeContext().getAggregationColumns().add(column); if (AggregationType.AVG.equals(aggregationType)) { getParseContext().addDerivedColumnsForAvgColumn(column); // TODO 将AVG列替换成常数,避免数据库再计算无用的AVG函数 } return super.visit(x); } public boolean visit(final SQLOrderBy x) { for (SQLSelectOrderByItem each : x.getItems()) { SQLExpr expr = each.getExpr(); OrderByType orderByType = null == each.getType() ? OrderByType.ASC : OrderByType.valueOf(each.getType()); if (expr instanceof SQLIntegerExpr) { getParseContext().addOrderByColumn(((SQLIntegerExpr) expr).getNumber().intValue(), orderByType); } else if (expr instanceof SQLIdentifierExpr) { getParseContext().addOrderByColumn(Optional.<String>absent(), ((SQLIdentifierExpr) expr).getName(), orderByType); } else if (expr instanceof SQLPropertyExpr) { SQLPropertyExpr sqlPropertyExpr = (SQLPropertyExpr) expr; getParseContext().addOrderByColumn(Optional.of(sqlPropertyExpr.getOwner().toString()), sqlPropertyExpr.getName(), orderByType); } } return super.visit(x); } /** * 将GROUP BY列放入parseResult. * 直接返回false,防止重复解析GROUP BY表达式. * * @param x GROUP BY 表达式 * @return false 停止遍历AST */ @Override public boolean visit(final MySqlSelectGroupByExpr x) { OrderByType orderByType = null == x.getType() ? OrderByType.ASC : OrderByType.valueOf(x.getType()); if (x.getExpr() instanceof SQLPropertyExpr) { SQLPropertyExpr expr = (SQLPropertyExpr) x.getExpr(); getParseContext().addGroupByColumns(Optional.of(expr.getOwner().toString()), expr.getName(), orderByType); } else if (x.getExpr() instanceof SQLIdentifierExpr) { SQLIdentifierExpr expr = (SQLIdentifierExpr) x.getExpr(); getParseContext().addGroupByColumns(Optional.<String>absent(), expr.getName(), orderByType); } return super.visit(x); } /** * LIMIT 解析. * * @param x LIMIT表达式 * @return false 停止遍历AST */ @Override public boolean visit(final MySqlSelectQueryBlock.Limit x) { if (getParseContext().getParseContextIndex() > 0) { return super.visit(x); } print("LIMIT "); int offset = 0; int offSetIndex = -1; if (null != x.getOffset()) { if (x.getOffset() instanceof SQLNumericLiteralExpr) { offset = ((SQLNumericLiteralExpr) x.getOffset()).getNumber().intValue(); printToken(Limit.OFFSET_NAME, String.valueOf(offset)); print(", "); } else { offset = ((Number) getParameters().get(((SQLVariantRefExpr) x.getOffset()).getIndex())).intValue(); offSetIndex = ((SQLVariantRefExpr) x.getOffset()).getIndex(); print("?, "); } } int rowCount; int rowCountIndex = -1; if (x.getRowCount() instanceof SQLNumericLiteralExpr) { rowCount = ((SQLNumericLiteralExpr) x.getRowCount()).getNumber().intValue(); printToken(Limit.COUNT_NAME, String.valueOf(rowCount)); } else { rowCount = ((Number) getParameters().get(((SQLVariantRefExpr) x.getRowCount()).getIndex())).intValue(); rowCountIndex = ((SQLVariantRefExpr) x.getRowCount()).getIndex(); print("?"); } if (offset < 0 || rowCount < 0) { throw new SQLParserException("LIMIT offset and row count can not be a negative value"); } // "LIMIT {rowCount} OFFSET {offset}" will transform to "LIMIT {offset}, {rowCount}".So exchange parameter index if (offSetIndex > -1 && rowCountIndex > -1 && offSetIndex > rowCountIndex) { int tmp = rowCountIndex; rowCountIndex = offSetIndex; offSetIndex = tmp; } getParseContext().getParsedResult().getMergeContext().setLimit(new Limit(offset, rowCount, offSetIndex, rowCountIndex)); return false; } @Override public void endVisit(final MySqlSelectQueryBlock x) { StringBuilder derivedSelectItems = new StringBuilder(); for (AggregationColumn aggregationColumn : getParseContext().getParsedResult().getMergeContext().getAggregationColumns()) { for (AggregationColumn derivedColumn : aggregationColumn.getDerivedColumns()) { derivedSelectItems.append(", ").append(derivedColumn.getExpression()).append(" AS ").append(derivedColumn.getAlias().get()); } } appendSortableColumn(derivedSelectItems, getParseContext().getParsedResult().getMergeContext().getGroupByColumns()); appendSortableColumn(derivedSelectItems, getParseContext().getParsedResult().getMergeContext().getOrderByColumns()); if (0 != derivedSelectItems.length()) { getSQLBuilder().buildSQL(getParseContext().getAutoGenTokenKey(), derivedSelectItems.toString(), true); } super.endVisit(x); stepOutQuery(); } private void appendSortableColumn(final StringBuilder derivedSelectItems, final List<? extends AbstractSortableColumn> sortableColumns) { for (AbstractSortableColumn each : sortableColumns) { if (!each.getAlias().isPresent()) { continue; } derivedSelectItems.append(", "); if (each.getOwner().isPresent()) { derivedSelectItems.append(each.getOwner().get()).append("."); } derivedSelectItems.append(each.getName().get()).append(" AS ").append(each.getAlias().get()); } } }