/* * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor * license agreements. See the NOTICE file distributed with this work for * additional information regarding copyright ownership. Crate 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. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial agreement. */ package io.crate.planner.consumer; import io.crate.analyze.HavingClause; import io.crate.analyze.OrderBy; import io.crate.analyze.QuerySpec; import io.crate.analyze.relations.AnalyzedRelation; import io.crate.analyze.relations.DocTableRelation; import io.crate.analyze.relations.QueriedDocTable; import io.crate.analyze.symbol.AggregateMode; import io.crate.analyze.symbol.Symbol; import io.crate.collections.Lists2; import io.crate.exceptions.VersionInvalidException; import io.crate.metadata.RowGranularity; import io.crate.planner.Limits; import io.crate.planner.Plan; import io.crate.planner.PositionalOrderBy; import io.crate.planner.node.dql.Collect; import io.crate.planner.node.dql.GroupByConsumer; import io.crate.planner.node.dql.RoutedCollectPhase; import io.crate.planner.projection.FilterProjection; import io.crate.planner.projection.GroupProjection; import io.crate.planner.projection.Projection; import io.crate.planner.projection.builder.ProjectionBuilder; import io.crate.planner.projection.builder.SplitPoints; import java.util.ArrayList; import java.util.List; import java.util.Optional; class ReduceOnCollectorGroupByConsumer implements Consumer { private final Visitor visitor; ReduceOnCollectorGroupByConsumer(ProjectionBuilder projectionBuilder) { visitor = new Visitor(projectionBuilder); } @Override public Plan consume(AnalyzedRelation relation, ConsumerContext context) { return visitor.process(relation, context); } private static class Visitor extends RelationPlanningVisitor { private final ProjectionBuilder projectionBuilder; public Visitor(ProjectionBuilder projectionBuilder) { this.projectionBuilder = projectionBuilder; } @Override public Plan visitQueriedDocTable(QueriedDocTable table, ConsumerContext context) { if (!table.querySpec().groupBy().isPresent()) { return null; } DocTableRelation tableRelation = table.tableRelation(); if (!GroupByConsumer.groupedByClusteredColumnOrPrimaryKeys( tableRelation.tableInfo(), table.querySpec().where(), table.querySpec().groupBy().get())) { return null; } if (table.querySpec().where().hasVersions()) { context.validationException(new VersionInvalidException()); return null; } return optimizedReduceOnCollectorGroupBy(table, context); } /** * grouping on doc tables by clustered column or primary keys, no distribution needed * only one aggregation step as the mappers (shards) have row-authority * <p> * produces: * <p> * SELECT: * CollectNode ( GroupProjection, [FilterProjection], [TopN] ) * LocalMergeNode ( TopN ) */ private Plan optimizedReduceOnCollectorGroupBy(QueriedDocTable table, ConsumerContext context) { QuerySpec querySpec = table.querySpec(); Optional<List<Symbol>> optGroupBy = querySpec.groupBy(); assert optGroupBy.isPresent() : "must have groupBy if optimizeReduceOnCollectorGroupBy is called"; List<Symbol> groupKeys = optGroupBy.get(); assert GroupByConsumer.groupedByClusteredColumnOrPrimaryKeys( table.tableRelation().tableInfo(), querySpec.where(), groupKeys) : "not grouped by clustered column or primary keys"; GroupByConsumer.validateGroupBySymbols(groupKeys); SplitPoints splitPoints = SplitPoints.create(querySpec); // mapper / collect List<Symbol> collectOutputs = Lists2.concat(groupKeys, splitPoints.aggregates()); List<Projection> projections = new ArrayList<>(); GroupProjection groupProjection = projectionBuilder.groupProjection( splitPoints.toCollect(), groupKeys, splitPoints.aggregates(), AggregateMode.ITER_FINAL, RowGranularity.SHARD ); projections.add(groupProjection); Optional<HavingClause> havingClause = querySpec.having(); if (havingClause.isPresent()) { HavingClause having = havingClause.get(); FilterProjection fp = ProjectionBuilder.filterProjection(collectOutputs, having); fp.requiredGranularity(RowGranularity.SHARD); projections.add(fp); } OrderBy orderBy = querySpec.orderBy().orElse(null); Limits limits = context.plannerContext().getLimits(querySpec); List<Symbol> qsOutputs = querySpec.outputs(); projections.add(ProjectionBuilder.topNOrEval( collectOutputs, orderBy, 0, // no offset limits.limitAndOffset(), qsOutputs )); RoutedCollectPhase collectPhase = RoutedCollectPhase.forQueriedTable( context.plannerContext(), table, splitPoints.toCollect(), projections ); return new Collect( collectPhase, limits.finalLimit(), limits.offset(), qsOutputs.size(), limits.limitAndOffset(), PositionalOrderBy.of(orderBy, qsOutputs) ); } } }