/* * 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.execute; import static org.apache.phoenix.query.QueryConstants.*; import java.io.IOException; import java.sql.SQLException; import java.util.Collections; import java.util.List; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.phoenix.compile.ExplainPlan; import org.apache.phoenix.compile.GroupByCompiler.GroupBy; import org.apache.phoenix.compile.OrderByCompiler.OrderBy; import org.apache.phoenix.compile.QueryPlan; import org.apache.phoenix.compile.RowProjector; import org.apache.phoenix.compile.StatementContext; import org.apache.phoenix.coprocessor.BaseScannerRegionObserver; import org.apache.phoenix.expression.Expression; import org.apache.phoenix.expression.OrderByExpression; import org.apache.phoenix.expression.aggregator.Aggregators; import org.apache.phoenix.expression.aggregator.ServerAggregators; import org.apache.phoenix.iterate.AggregatingResultIterator; import org.apache.phoenix.iterate.BaseGroupedAggregatingResultIterator; import org.apache.phoenix.iterate.DistinctAggregatingResultIterator; import org.apache.phoenix.iterate.FilterAggregatingResultIterator; import org.apache.phoenix.iterate.FilterResultIterator; import org.apache.phoenix.iterate.GroupedAggregatingResultIterator; import org.apache.phoenix.iterate.LimitingResultIterator; import org.apache.phoenix.iterate.LookAheadResultIterator; import org.apache.phoenix.iterate.OrderedAggregatingResultIterator; import org.apache.phoenix.iterate.OrderedResultIterator; import org.apache.phoenix.iterate.PeekingResultIterator; import org.apache.phoenix.iterate.ResultIterator; import org.apache.phoenix.iterate.SequenceResultIterator; import org.apache.phoenix.iterate.UngroupedAggregatingResultIterator; import org.apache.phoenix.parse.FilterableStatement; import org.apache.phoenix.query.QueryServices; import org.apache.phoenix.query.QueryServicesOptions; import org.apache.phoenix.schema.TableRef; import org.apache.phoenix.schema.tuple.MultiKeyValueTuple; import org.apache.phoenix.schema.tuple.Tuple; import org.apache.phoenix.util.TupleUtil; import com.google.common.collect.Lists; public class ClientAggregatePlan extends ClientProcessingPlan { private final GroupBy groupBy; private final Expression having; private final Aggregators serverAggregators; private final Aggregators clientAggregators; public ClientAggregatePlan(StatementContext context, FilterableStatement statement, TableRef table, RowProjector projector, Integer limit, Expression where, OrderBy orderBy, GroupBy groupBy, Expression having, QueryPlan delegate) { super(context, statement, table, projector, limit, where, orderBy, delegate); this.groupBy = groupBy; this.having = having; this.serverAggregators = ServerAggregators.deserialize(context.getScan() .getAttribute(BaseScannerRegionObserver.AGGREGATORS), QueryServicesOptions.withDefaults().getConfiguration()); this.clientAggregators = context.getAggregationManager().getAggregators(); } @Override public ResultIterator iterator() throws SQLException { ResultIterator iterator = delegate.iterator(); if (where != null) { iterator = new FilterResultIterator(iterator, where); } AggregatingResultIterator aggResultIterator; if (groupBy.isEmpty()) { aggResultIterator = new ClientUngroupedAggregatingResultIterator(LookAheadResultIterator.wrap(iterator), serverAggregators); aggResultIterator = new UngroupedAggregatingResultIterator(LookAheadResultIterator.wrap(aggResultIterator), clientAggregators); } else { if (!groupBy.isOrderPreserving()) { int thresholdBytes = context.getConnection().getQueryServices().getProps().getInt( QueryServices.SPOOL_THRESHOLD_BYTES_ATTRIB, QueryServicesOptions.DEFAULT_SPOOL_THRESHOLD_BYTES); List<Expression> keyExpressions = groupBy.getKeyExpressions(); List<OrderByExpression> keyExpressionOrderBy = Lists.newArrayListWithExpectedSize(keyExpressions.size()); for (Expression keyExpression : keyExpressions) { keyExpressionOrderBy.add(new OrderByExpression(keyExpression, false, true)); } iterator = new OrderedResultIterator(iterator, keyExpressionOrderBy, thresholdBytes, limit, projector.getEstimatedRowByteSize()); } aggResultIterator = new ClientGroupedAggregatingResultIterator(LookAheadResultIterator.wrap(iterator), serverAggregators, groupBy.getKeyExpressions()); aggResultIterator = new GroupedAggregatingResultIterator(LookAheadResultIterator.wrap(aggResultIterator), clientAggregators); } if (having != null) { aggResultIterator = new FilterAggregatingResultIterator(aggResultIterator, having); } if (statement.isDistinct() && statement.isAggregate()) { // Dedup on client if select distinct and aggregation aggResultIterator = new DistinctAggregatingResultIterator(aggResultIterator, getProjector()); } ResultIterator resultScanner = aggResultIterator; if (orderBy.getOrderByExpressions().isEmpty()) { if (limit != null) { resultScanner = new LimitingResultIterator(aggResultIterator, limit); } } else { int thresholdBytes = context.getConnection().getQueryServices().getProps().getInt( QueryServices.SPOOL_THRESHOLD_BYTES_ATTRIB, QueryServicesOptions.DEFAULT_SPOOL_THRESHOLD_BYTES); resultScanner = new OrderedAggregatingResultIterator(aggResultIterator, orderBy.getOrderByExpressions(), thresholdBytes, limit); } if (context.getSequenceManager().getSequenceCount() > 0) { resultScanner = new SequenceResultIterator(resultScanner, context.getSequenceManager()); } return resultScanner; } @Override public ExplainPlan getExplainPlan() throws SQLException { List<String> planSteps = Lists.newArrayList(delegate.getExplainPlan().getPlanSteps()); if (where != null) { planSteps.add("CLIENT FILTER BY " + where.toString()); } if (!groupBy.isEmpty()) { if (!groupBy.isOrderPreserving()) { planSteps.add("CLIENT SORTED BY " + groupBy.getKeyExpressions().toString()); } planSteps.add("CLIENT AGGREGATE INTO DISTINCT ROWS BY " + groupBy.getExpressions().toString()); } else { planSteps.add("CLIENT AGGREGATE INTO SINGLE ROW"); } if (having != null) { planSteps.add("CLIENT AFTER-AGGREGATION FILTER BY " + having.toString()); } if (statement.isDistinct() && statement.isAggregate()) { planSteps.add("CLIENT DISTINCT ON " + projector.toString()); } if (orderBy.getOrderByExpressions().isEmpty()) { if (limit != null) { planSteps.add("CLIENT " + limit + " ROW LIMIT"); } } else { planSteps.add("CLIENT" + (limit == null ? "" : " TOP " + limit + " ROW" + (limit == 1 ? "" : "S")) + " SORTED BY " + orderBy.getOrderByExpressions().toString()); } if (context.getSequenceManager().getSequenceCount() > 0) { int nSequences = context.getSequenceManager().getSequenceCount(); planSteps.add("CLIENT RESERVE VALUES FROM " + nSequences + " SEQUENCE" + (nSequences == 1 ? "" : "S")); } return new ExplainPlan(planSteps); } @Override public GroupBy getGroupBy() { return groupBy; } private static class ClientGroupedAggregatingResultIterator extends BaseGroupedAggregatingResultIterator { private final List<Expression> groupByExpressions; public ClientGroupedAggregatingResultIterator(PeekingResultIterator iterator, Aggregators aggregators, List<Expression> groupByExpressions) { super(iterator, aggregators); this.groupByExpressions = groupByExpressions; } @Override protected ImmutableBytesWritable getGroupingKey(Tuple tuple, ImmutableBytesWritable ptr) throws SQLException { try { ImmutableBytesWritable key = TupleUtil.getConcatenatedValue(tuple, groupByExpressions); ptr.set(key.get(), key.getOffset(), key.getLength()); return ptr; } catch (IOException e) { throw new SQLException(e); } } @Override protected Tuple wrapKeyValueAsResult(KeyValue keyValue) { return new MultiKeyValueTuple(Collections.<Cell> singletonList(keyValue)); } @Override public String toString() { return "ClientGroupedAggregatingResultIterator [resultIterator=" + resultIterator + ", aggregators=" + aggregators + ", groupByExpressions=" + groupByExpressions + "]"; } } private static class ClientUngroupedAggregatingResultIterator extends BaseGroupedAggregatingResultIterator { public ClientUngroupedAggregatingResultIterator(PeekingResultIterator iterator, Aggregators aggregators) { super(iterator, aggregators); } @Override protected ImmutableBytesWritable getGroupingKey(Tuple tuple, ImmutableBytesWritable ptr) throws SQLException { ptr.set(UNGROUPED_AGG_ROW_KEY); return ptr; } @Override protected Tuple wrapKeyValueAsResult(KeyValue keyValue) throws SQLException { return new MultiKeyValueTuple(Collections.<Cell> singletonList(keyValue)); } @Override public String toString() { return "ClientUngroupedAggregatingResultIterator [resultIterator=" + resultIterator + ", aggregators=" + aggregators + "]"; } } }