/* * 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 com.google.common.collect.ImmutableList; import io.crate.analyze.HavingClause; import io.crate.analyze.QueriedTable; import io.crate.analyze.QueriedTableRelation; import io.crate.analyze.QuerySpec; import io.crate.analyze.relations.AnalyzedRelation; 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.Routing; import io.crate.metadata.RowGranularity; import io.crate.metadata.doc.DocTableInfo; import io.crate.operation.projectors.TopN; import io.crate.planner.Limits; import io.crate.planner.Merge; import io.crate.planner.Plan; import io.crate.planner.Planner; import io.crate.planner.distribution.DistributionInfo; import io.crate.planner.node.dql.Collect; import io.crate.planner.node.dql.GroupByConsumer; import io.crate.planner.node.dql.MergePhase; import io.crate.planner.node.dql.RoutedCollectPhase; 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.Collections; import java.util.List; import java.util.Optional; class NonDistributedGroupByConsumer implements Consumer { private final Visitor visitor; NonDistributedGroupByConsumer(ProjectionBuilder projectionBuilder) { this.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; } DocTableInfo tableInfo = table.tableRelation().tableInfo(); if (table.querySpec().where().hasVersions()) { context.validationException(new VersionInvalidException()); return null; } Routing routing = context.plannerContext().allocateRouting(tableInfo, table.querySpec().where(), null); if (routing.hasLocations() && routing.locations().size() > 1) { return null; } GroupByConsumer.validateGroupBySymbols(table.querySpec().groupBy().get()); return nonDistributedGroupBy(table, context, RowGranularity.SHARD); } @Override public Plan visitQueriedTable(QueriedTable table, ConsumerContext context) { if (!table.querySpec().groupBy().isPresent()) { return null; } return nonDistributedGroupBy(table, context, RowGranularity.CLUSTER); } /** * Group by on System Tables (never needs distribution) * or Group by on user tables (RowGranulariy.DOC) with only one node. * <p> * produces: * <p> * SELECT: * Collect ( GroupProjection ITER -> PARTIAL ) * LocalMerge ( GroupProjection PARTIAL -> FINAL, [FilterProjection], TopN ) */ private Plan nonDistributedGroupBy(QueriedTableRelation table, ConsumerContext context, RowGranularity groupProjectionGranularity) { Planner.Context plannerContext = context.plannerContext(); QuerySpec querySpec = table.querySpec(); List<Symbol> groupKeys = querySpec.groupBy().get(); SplitPoints splitPoints = SplitPoints.create(querySpec); // mapper / collect GroupProjection groupProjection = projectionBuilder.groupProjection( splitPoints.toCollect(), groupKeys, splitPoints.aggregates(), AggregateMode.ITER_PARTIAL, groupProjectionGranularity); RoutedCollectPhase collectPhase = RoutedCollectPhase.forQueriedTable( plannerContext, table, splitPoints.toCollect(), ImmutableList.of(groupProjection)); Collect collect = new Collect( collectPhase, TopN.NO_LIMIT, 0, groupProjection.outputs().size(), -1, null); // handler List<Symbol> collectOutputs = Lists2.concat(groupKeys, splitPoints.aggregates()); List<Projection> mergeProjections = new ArrayList<>(); mergeProjections.add(projectionBuilder.groupProjection( collectOutputs, groupKeys, splitPoints.aggregates(), AggregateMode.PARTIAL_FINAL, RowGranularity.CLUSTER )); Optional<HavingClause> havingClause = querySpec.having(); if (havingClause.isPresent()) { HavingClause having = havingClause.get(); mergeProjections.add(ProjectionBuilder.filterProjection(collectOutputs, having)); } Limits limits = plannerContext.getLimits(querySpec); List<Symbol> qsOutputs = querySpec.outputs(); mergeProjections.add(ProjectionBuilder.topNOrEval( collectOutputs, querySpec.orderBy().orElse(null), limits.offset(), limits.finalLimit(), qsOutputs )); MergePhase mergePhase = new MergePhase( plannerContext.jobId(), plannerContext.nextExecutionPhaseId(), "mergeOnHandler", collect.resultDescription().nodeIds().size(), Collections.emptyList(), collect.resultDescription().streamOutputs(), mergeProjections, DistributionInfo.DEFAULT_SAME_NODE, null ); return new Merge(collect, mergePhase, TopN.NO_LIMIT, 0, qsOutputs.size(), limits.finalLimit(), null); } } }