| /* |
| * 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.parse; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.phoenix.compile.ColumnResolver; |
| import org.apache.phoenix.expression.function.CountAggregateFunction; |
| import org.apache.phoenix.jdbc.PhoenixStatement.Operation; |
| import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; |
| import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunctionInfo; |
| |
| /** |
| * |
| * Top level node representing a SQL statement |
| * |
| * |
| * @since 0.1 |
| */ |
| public class SelectStatement implements FilterableStatement { |
| public static final SelectStatement SELECT_STAR = |
| new SelectStatement( |
| null, null, false, |
| Arrays.asList(), |
| null, Collections.<ParseNode>emptyList(), |
| null, Collections.<OrderByNode>emptyList(), |
| null, null, 0, false, false, Collections.<SelectStatement>emptyList(), new HashMap<String, UDFParseNode>(1)); |
| public static final SelectStatement SELECT_ONE = |
| new SelectStatement( |
| null, null, false, |
| Collections.<AliasedNode>singletonList(new AliasedNode(null, LiteralParseNode.ONE)), |
| null, Collections.<ParseNode>emptyList(), |
| null, Collections.<OrderByNode>emptyList(), |
| null, null, 0, false, false, Collections.<SelectStatement>emptyList(), new HashMap<String, UDFParseNode>(1)); |
| public static final SelectStatement COUNT_ONE = |
| new SelectStatement( |
| null, null, false, |
| Collections.<AliasedNode>singletonList( |
| new AliasedNode(null, |
| new AggregateFunctionParseNode( |
| CountAggregateFunction.NORMALIZED_NAME, |
| LiteralParseNode.STAR, |
| new BuiltInFunctionInfo(CountAggregateFunction.class, CountAggregateFunction.class.getAnnotation(BuiltInFunction.class))))), |
| null, Collections.<ParseNode>emptyList(), |
| null, Collections.<OrderByNode>emptyList(), |
| null,null, 0, true, false, Collections.<SelectStatement>emptyList(), new HashMap<String, UDFParseNode>(1)); |
| public static SelectStatement create(SelectStatement select, HintNode hint) { |
| if (select.getHint() == hint || hint.isEmpty()) { |
| return select; |
| } |
| return new SelectStatement(select.getFrom(), hint, select.isDistinct(), |
| select.getSelect(), select.getWhere(), select.getGroupBy(), select.getHaving(), |
| select.getOrderBy(), select.getLimit(), select.getOffset(), select.getBindCount(), select.isAggregate(), select.hasSequence(), select.getSelects(), select.getUdfParseNodes()); |
| } |
| |
| public static SelectStatement create(SelectStatement select, TableNode tableNode, List<AliasedNode> selects) { |
| return new SelectStatement(tableNode, select.getHint(), select.isDistinct(), |
| selects, select.getWhere(), select.getGroupBy(), select.getHaving(), |
| select.getOrderBy(), select.getLimit(), select.getOffset(), select.getBindCount(), select.isAggregate(), select.hasSequence(), select.getSelects(), select.getUdfParseNodes()); |
| } |
| |
| public SelectStatement combine(ParseNode where) { |
| if (where == null) { |
| return this; |
| } |
| if (this.getWhere() != null) { |
| where = new AndParseNode(Arrays.asList(this.getWhere(), where)); |
| } |
| return new SelectStatement(this.getFrom(), this.getHint(), this.isDistinct(), |
| this.getSelect(), where, this.getGroupBy(), this.getHaving(), |
| this.getOrderBy(), this.getLimit(), this.getOffset(), this.getBindCount(), this.isAggregate(), this.hasSequence(), this.selects, this.udfParseNodes); |
| } |
| |
| public static SelectStatement create(SelectStatement select, List<AliasedNode> selects) { |
| return new SelectStatement(select.getFrom(), select.getHint(), select.isDistinct(), |
| selects, select.getWhere(), select.getGroupBy(), select.getHaving(), |
| select.getOrderBy(), select.getLimit(), select.getOffset(), select.getBindCount(), select.isAggregate(), select.hasSequence(), select.getSelects(), select.getUdfParseNodes()); |
| } |
| |
| // Copy constructor for sub select statements in a union |
| public static SelectStatement create(SelectStatement select, List<OrderByNode> orderBy, LimitNode limit, |
| OffsetNode offset, boolean isAggregate) { |
| return new SelectStatement(select.getFrom(), select.getHint(), select.isDistinct(), select.getSelect(), |
| select.getWhere(), select.getGroupBy(), select.getHaving(), orderBy, limit, offset, |
| select.getBindCount(), isAggregate, select.hasSequence(), select.getSelects(), |
| select.getUdfParseNodes()); |
| } |
| |
| private final TableNode fromTable; |
| private final HintNode hint; |
| private final boolean isDistinct; |
| private final List<AliasedNode> select; |
| private final ParseNode where; |
| private final List<ParseNode> groupBy; |
| private final ParseNode having; |
| private final List<OrderByNode> orderBy; |
| private final LimitNode limit; |
| private final int bindCount; |
| private final boolean isAggregate; |
| private final boolean hasSequence; |
| private final boolean hasWildcard; |
| private final List<SelectStatement> selects = new ArrayList<SelectStatement>(); |
| private final Map<String, UDFParseNode> udfParseNodes; |
| private final OffsetNode offset; |
| |
| @Override |
| public final String toString() { |
| StringBuilder buf = new StringBuilder(); |
| toSQL(null,buf); |
| return buf.toString(); |
| } |
| |
| public void toSQL(ColumnResolver resolver, StringBuilder buf) { |
| buf.append("SELECT "); |
| if (hint != null) buf.append(hint); |
| if (isDistinct) buf.append("DISTINCT "); |
| for (AliasedNode selectNode : select) { |
| selectNode.toSQL(resolver, buf); |
| buf.append(','); |
| } |
| buf.setLength(buf.length()-1); |
| if (fromTable != null) { |
| buf.append(" FROM "); |
| fromTable.toSQL(resolver, buf); |
| } |
| if (where != null) { |
| buf.append(" WHERE "); |
| where.toSQL(resolver, buf); |
| } |
| if (!groupBy.isEmpty()) { |
| buf.append(" GROUP BY "); |
| for (ParseNode node : groupBy) { |
| node.toSQL(resolver, buf); |
| buf.append(','); |
| } |
| buf.setLength(buf.length()-1); |
| } |
| if (having != null) { |
| buf.append(" HAVING "); |
| having.toSQL(resolver, buf); |
| } |
| if (!orderBy.isEmpty()) { |
| buf.append(" ORDER BY "); |
| for (OrderByNode node : orderBy) { |
| node.toSQL(resolver, buf); |
| buf.append(','); |
| } |
| buf.setLength(buf.length()-1); |
| } |
| if (limit != null) { |
| buf.append(" LIMIT " + limit.toString()); |
| } |
| if (offset != null) { |
| buf.append(" OFFSET " + offset.toString()); |
| } |
| } |
| |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((fromTable == null) ? 0 : fromTable.hashCode()); |
| result = prime * result + ((groupBy == null) ? 0 : groupBy.hashCode()); |
| result = prime * result + ((having == null) ? 0 : having.hashCode()); |
| result = prime * result + ((hint == null) ? 0 : hint.hashCode()); |
| result = prime * result + (isDistinct ? 1231 : 1237); |
| result = prime * result + ((limit == null) ? 0 : limit.hashCode()); |
| result = prime * result + ((orderBy == null) ? 0 : orderBy.hashCode()); |
| result = prime * result + ((select == null) ? 0 : select.hashCode()); |
| result = prime * result + ((where == null) ? 0 : where.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null) return false; |
| if (getClass() != obj.getClass()) return false; |
| SelectStatement other = (SelectStatement)obj; |
| if (fromTable == null) { |
| if (other.fromTable != null) return false; |
| } else if (!fromTable.equals(other.fromTable)) return false; |
| if (groupBy == null) { |
| if (other.groupBy != null) return false; |
| } else if (!groupBy.equals(other.groupBy)) return false; |
| if (having == null) { |
| if (other.having != null) return false; |
| } else if (!having.equals(other.having)) return false; |
| if (hint == null) { |
| if (other.hint != null) return false; |
| } else if (!hint.equals(other.hint)) return false; |
| if (isDistinct != other.isDistinct) return false; |
| if (limit == null) { |
| if (other.limit != null) return false; |
| } else if (!limit.equals(other.limit)) return false; |
| if (orderBy == null) { |
| if (other.orderBy != null) return false; |
| } else if (!orderBy.equals(other.orderBy)) return false; |
| if (select == null) { |
| if (other.select != null) return false; |
| } else if (!select.equals(other.select)) return false; |
| if (where == null) { |
| if (other.where != null) return false; |
| } else if (!where.equals(other.where)) return false; |
| return true; |
| } |
| |
| // Count constant expressions |
| private static int countConstants(List<ParseNode> nodes) { |
| int count = 0; |
| for (int i = 0; i < nodes.size(); i++) { |
| if (nodes.get(i).isStateless()) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| protected SelectStatement(TableNode from, HintNode hint, boolean isDistinct, List<AliasedNode> select, |
| ParseNode where, List<ParseNode> groupBy, ParseNode having, List<OrderByNode> orderBy, LimitNode limit, |
| OffsetNode offset, int bindCount, boolean isAggregate, boolean hasSequence, List<SelectStatement> selects, |
| Map<String, UDFParseNode> udfParseNodes) { |
| this.fromTable = from; |
| this.hint = hint == null ? HintNode.EMPTY_HINT_NODE : hint; |
| this.isDistinct = isDistinct; |
| this.select = Collections.unmodifiableList(select); |
| this.where = where; |
| this.groupBy = Collections.unmodifiableList(groupBy); |
| this.having = having; |
| this.orderBy = Collections.unmodifiableList(orderBy); |
| this.limit = limit; |
| this.offset = offset; |
| this.bindCount = bindCount; |
| this.isAggregate = isAggregate || groupBy.size() != countConstants(groupBy) || this.having != null; |
| this.hasSequence = hasSequence; |
| boolean hasWildcard = false; |
| for (AliasedNode aliasedNode : select) { |
| ParseNode node = aliasedNode.getNode(); |
| if (node instanceof WildcardParseNode || node instanceof TableWildcardParseNode || node instanceof FamilyWildcardParseNode) { |
| hasWildcard = true; |
| break; |
| } |
| } |
| this.hasWildcard = hasWildcard; |
| if (!selects.isEmpty()) { |
| this.selects.addAll(selects); |
| } |
| this.udfParseNodes = udfParseNodes; |
| } |
| |
| @Override |
| public boolean isDistinct() { |
| return isDistinct; |
| } |
| |
| @Override |
| public LimitNode getLimit() { |
| return limit; |
| } |
| |
| /** |
| * This method should not be called during the early stage |
| * of the plan preparation phase since fromTable might not |
| * be ConcreteTableNode at that time(e.g., JoinTableNode). |
| * |
| * By the time getTableSamplingRate method is called, |
| * each select statements should have exactly one ConcreteTableNode, |
| * with its corresponding sampling rate associate with it. |
| */ |
| @Override |
| public Double getTableSamplingRate(){ |
| if(fromTable==null || !(fromTable instanceof ConcreteTableNode)) return null; |
| return ((ConcreteTableNode)fromTable).getTableSamplingRate(); |
| } |
| |
| @Override |
| public int getBindCount() { |
| return bindCount; |
| } |
| |
| public TableNode getFrom() { |
| return fromTable; |
| } |
| |
| @Override |
| public HintNode getHint() { |
| return hint; |
| } |
| |
| public List<AliasedNode> getSelect() { |
| return select; |
| } |
| /** |
| * Gets the where condition, or null if none. |
| */ |
| @Override |
| public ParseNode getWhere() { |
| return where; |
| } |
| |
| /** |
| * Gets the group-by, containing at least 1 element, or empty list, if none. |
| */ |
| public List<ParseNode> getGroupBy() { |
| return groupBy; |
| } |
| |
| public ParseNode getHaving() { |
| return having; |
| } |
| |
| /** |
| * Gets the order-by, containing at least 1 element, or null, if none. |
| */ |
| @Override |
| public List<OrderByNode> getOrderBy() { |
| return orderBy; |
| } |
| |
| @Override |
| public boolean isAggregate() { |
| return isAggregate; |
| } |
| |
| public boolean hasSequence() { |
| return hasSequence; |
| } |
| |
| @Override |
| public Operation getOperation() { |
| return Operation.QUERY; |
| } |
| |
| public boolean isJoin() { |
| return fromTable != null && fromTable instanceof JoinTableNode; |
| } |
| |
| public SelectStatement getInnerSelectStatement() { |
| if (fromTable == null || !(fromTable instanceof DerivedTableNode)) |
| return null; |
| |
| return ((DerivedTableNode) fromTable).getSelect(); |
| } |
| |
| public boolean isUnion() { |
| return !getSelects().isEmpty(); |
| } |
| |
| public List<SelectStatement> getSelects() { |
| return selects; |
| } |
| |
| public boolean hasWildcard() { |
| return hasWildcard; |
| } |
| |
| public Map<String, UDFParseNode> getUdfParseNodes() { |
| return udfParseNodes; |
| } |
| |
| @Override |
| public OffsetNode getOffset() { |
| return offset; |
| } |
| |
| } |