| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.phoenix.compile; |
| |
| import java.sql.SQLException; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.apache.phoenix.parse.AliasedNode; |
| import org.apache.phoenix.parse.ArrayElemRefNode; |
| import org.apache.phoenix.parse.BetweenParseNode; |
| import org.apache.phoenix.parse.BindTableNode; |
| import org.apache.phoenix.parse.ColumnParseNode; |
| import org.apache.phoenix.parse.ComparisonParseNode; |
| import org.apache.phoenix.parse.DerivedTableNode; |
| import org.apache.phoenix.parse.FamilyWildcardParseNode; |
| import org.apache.phoenix.parse.JoinTableNode; |
| import org.apache.phoenix.parse.NamedNode; |
| import org.apache.phoenix.parse.JoinTableNode.JoinType; |
| import org.apache.phoenix.parse.LessThanOrEqualParseNode; |
| import org.apache.phoenix.parse.NamedTableNode; |
| import org.apache.phoenix.parse.ParseNode; |
| import org.apache.phoenix.parse.ParseNodeRewriter; |
| import org.apache.phoenix.parse.SelectStatement; |
| import org.apache.phoenix.parse.TableName; |
| import org.apache.phoenix.parse.TableNodeVisitor; |
| import org.apache.phoenix.parse.TableWildcardParseNode; |
| import org.apache.phoenix.parse.WildcardParseNode; |
| import org.apache.phoenix.util.SchemaUtil; |
| |
| import com.google.common.collect.Lists; |
| |
| |
| /** |
| * |
| * Class that creates a new select statement ensuring that a literal always occurs |
| * on the RHS (i.e. if literal found on the LHS, then the operator is reversed and |
| * the literal is put on the RHS) |
| * |
| * |
| * @since 0.1 |
| */ |
| public class StatementNormalizer extends ParseNodeRewriter { |
| private boolean multiTable; |
| |
| public StatementNormalizer(ColumnResolver resolver, int expectedAliasCount, boolean multiTable) { |
| super(resolver, expectedAliasCount); |
| this.multiTable = multiTable; |
| } |
| |
| public static ParseNode normalize(ParseNode where, ColumnResolver resolver) throws SQLException { |
| return rewrite(where, new StatementNormalizer(resolver, 0, false)); |
| } |
| |
| /** |
| * Rewrite the select statement by switching any constants to the right hand side |
| * of the expression. |
| * @param statement the select statement |
| * @param resolver |
| * @return new select statement |
| * @throws SQLException |
| */ |
| public static SelectStatement normalize(SelectStatement statement, ColumnResolver resolver) throws SQLException { |
| boolean multiTable = statement.isJoin(); |
| // Replace WildcardParse with a list of TableWildcardParseNode for multi-table queries |
| if (multiTable) { |
| List<AliasedNode> selectNodes = statement.getSelect(); |
| List<AliasedNode> normSelectNodes = selectNodes; |
| for (int i = 0; i < selectNodes.size(); i++) { |
| AliasedNode aliasedNode = selectNodes.get(i); |
| ParseNode selectNode = aliasedNode.getNode(); |
| if (selectNode == WildcardParseNode.INSTANCE) { |
| if (selectNodes == normSelectNodes) { |
| normSelectNodes = Lists.newArrayList(selectNodes.subList(0, i)); |
| } |
| List<TableName> tableNames = statement.getFrom().accept(new TableNameVisitor()); |
| for (TableName tableName : tableNames) { |
| TableWildcardParseNode node = NODE_FACTORY.tableWildcard(tableName); |
| normSelectNodes.add(NODE_FACTORY.aliasedNode(null, node)); |
| } |
| } else if (selectNodes != normSelectNodes) { |
| normSelectNodes.add(aliasedNode); |
| } |
| } |
| if (selectNodes != normSelectNodes) { |
| statement = NODE_FACTORY.select(statement.getFrom(), statement.getHint(), statement.isDistinct(), |
| normSelectNodes, statement.getWhere(), statement.getGroupBy(), statement.getHaving(), statement.getOrderBy(), |
| statement.getLimit(), statement.getOffset(), statement.getBindCount(), statement.isAggregate(), statement.hasSequence(), statement.getSelects(), statement.getUdfParseNodes()); |
| } |
| } |
| |
| return rewrite(statement, new StatementNormalizer(resolver, statement.getSelect().size(), multiTable)); |
| } |
| |
| private static class TableNameVisitor implements TableNodeVisitor<List<TableName>> { |
| |
| @Override |
| public List<TableName> visit(BindTableNode boundTableNode) throws SQLException { |
| TableName name = boundTableNode.getAlias() == null ? boundTableNode.getName() : TableName.create(null, boundTableNode.getAlias()); |
| return Collections.singletonList(name); |
| } |
| |
| @Override |
| public List<TableName> visit(JoinTableNode joinNode) throws SQLException { |
| List<TableName> lhs = joinNode.getLHS().accept(this); |
| List<TableName> rhs = joinNode.getType() == JoinType.Semi || joinNode.getType() == JoinType.Anti ? Collections.<TableName> emptyList() : joinNode.getRHS().accept(this); |
| List<TableName> ret = Lists.<TableName>newArrayListWithExpectedSize(lhs.size() + rhs.size()); |
| ret.addAll(lhs); |
| ret.addAll(rhs); |
| return ret; |
| } |
| |
| @Override |
| public List<TableName> visit(NamedTableNode namedTableNode) |
| throws SQLException { |
| TableName name = namedTableNode.getAlias() == null ? namedTableNode.getName() : TableName.create(null, namedTableNode.getAlias()); |
| return Collections.singletonList(name); |
| } |
| |
| @Override |
| public List<TableName> visit(DerivedTableNode subselectNode) |
| throws SQLException { |
| TableName name = TableName.create(null, subselectNode.getAlias()); |
| return Collections.singletonList(name); |
| } |
| }; |
| |
| @Override |
| public ParseNode visitLeave(ComparisonParseNode node, List<ParseNode> nodes) throws SQLException { |
| if (nodes.get(0).isStateless() && !nodes.get(1).isStateless() |
| && !(nodes.get(1) instanceof ArrayElemRefNode)) { |
| List<ParseNode> normNodes = Lists.newArrayListWithExpectedSize(2); |
| normNodes.add(nodes.get(1)); |
| normNodes.add(nodes.get(0)); |
| nodes = normNodes; |
| node = NODE_FACTORY.comparison(node.getInvertFilterOp(), nodes.get(0), nodes.get(1)); |
| } |
| return super.visitLeave(node, nodes); |
| } |
| |
| @Override |
| public ParseNode visitLeave(final BetweenParseNode node, List<ParseNode> nodes) throws SQLException { |
| |
| LessThanOrEqualParseNode lhsNode = NODE_FACTORY.lte(node.getChildren().get(1), node.getChildren().get(0)); |
| LessThanOrEqualParseNode rhsNode = NODE_FACTORY.lte(node.getChildren().get(0), node.getChildren().get(2)); |
| List<ParseNode> parseNodes = Lists.newArrayListWithExpectedSize(2); |
| parseNodes.add(this.visitLeave(lhsNode, lhsNode.getChildren())); |
| parseNodes.add(this.visitLeave(rhsNode, rhsNode.getChildren())); |
| return super.visitLeave(node, parseNodes); |
| } |
| |
| @Override |
| public ParseNode visit(ColumnParseNode node) throws SQLException { |
| if (multiTable |
| && node.getAlias() != null |
| && node.getTableName() != null |
| && SchemaUtil.normalizeIdentifier(node.getAlias()).equals(node.getName())) { |
| node = NODE_FACTORY.column(TableName.create(node.getSchemaName(), node.getTableName()), |
| node.isCaseSensitive() ? '"' + node.getName() + '"' : node.getName(), |
| node.isCaseSensitive() ? '"' + node.getFullName() + '"' : node.getFullName()); |
| } |
| return super.visit(node); |
| } |
| |
| @Override |
| public ParseNode visit(FamilyWildcardParseNode node) throws SQLException { |
| if (!multiTable) |
| return super.visit(node); |
| |
| return super.visit(NODE_FACTORY.tableWildcard(NODE_FACTORY.table(null, node.isCaseSensitive() ? '"' + node.getName() + '"' : node.getName()))); |
| } |
| } |
| |