Source:
<code>public PlanWithProperties visitAggregation(AggregationNode node, StreamPreferredProperties parentPreferences) { checkState(node.getStep() == AggregationNode.Step.SINGLE, "step of aggregation is expected to be SINGLE, but it is %s", node.getStep()); if (node.hasSingleNodeExecutionPreference(metadata.getFunctionRegistry())) { return planAndEnforceChildren(node, singleStream(), defaultParallelism(session)); } List<Symbol> groupingKeys = node.getGroupingKeys(); if (node.hasDefaultOutput()) { checkState(node.isDecomposable(metadata.getFunctionRegistry())); PlanWithProperties child = planAndEnforce(node.getSource(), any(), defaultParallelism(session)); PlanWithProperties exchange = deriveProperties( partitionedExchange( idAllocator.getNextId(), LOCAL, child.getNode(), groupingKeys, Optional.empty()), child.getProperties()); return rebaseAndDeriveProperties(node, ImmutableList.of(exchange)); } <START> StreamPreferredProperties childRequirements = parentPreferences <END> .constrainTo(node.getSource().getOutputSymbols()) .withDefaultParallelism(session) .withPartitioning(groupingKeys); PlanWithProperties child = planAndEnforce(node.getSource(), childRequirements, childRequirements); List<Symbol> preGroupedSymbols; if (!groupingKeys.isEmpty()) { List<LocalProperty<Symbol>> desiredProperties = ImmutableList.of(new GroupingProperty<>(groupingKeys)); Iterator<Optional<LocalProperty<Symbol>>> matchIterator = LocalProperties.match(child.getProperties().getLocalProperties(), desiredProperties).iterator(); Optional<LocalProperty<Symbol>> groupingRequirement = matchIterator.next(); Set<Symbol> unPartitionedInputs = groupingRequirement.map(LocalProperty::getColumns).orElse(ImmutableSet.of()); preGroupedSymbols = groupingKeys.stream() .filter(symbol -> !unPartitionedInputs.contains(symbol)) .collect(toImmutableList()); } else { preGroupedSymbols = ImmutableList.of(); } AggregationNode result = new AggregationNode( node.getId(), child.getNode(), node.getAggregations(), node.getGroupingSets(), preGroupedSymbols, node.getStep(), node.getHashSymbol(), node.getGroupIdSymbol()); return deriveProperties(result, child.getProperties()); }</code><technical_language>please move this a separate rule, similar rule (computes local stream properties): <LINK_0></technical_language>

Target:
public PlanWithProperties visitAggregation(AggregationNode node, StreamPreferredProperties parentPreferences) { checkState(node.getStep() == AggregationNode.Step.SINGLE, "step of aggregation is expected to be SINGLE, but it is %s", node.getStep()); if (node.hasSingleNodeExecutionPreference(metadata.getFunctionRegistry())) { return planAndEnforceChildren(node, singleStream(), defaultParallelism(session)); } List<Symbol> groupingKeys = node.getGroupingKeys(); if (node.hasDefaultOutput()) { checkState(node.isDecomposable(metadata.getFunctionRegistry())); PlanWithProperties child = planAndEnforce(node.getSource(), any(), defaultParallelism(session)); PlanWithProperties exchange = deriveProperties( partitionedExchange( idAllocator.getNextId(), LOCAL, child.getNode(), groupingKeys, Optional.empty()), child.getProperties()); return rebaseAndDeriveProperties(node, ImmutableList.of(exchange)); } StreamPreferredProperties childRequirements = parentPreferences .constrainTo(node.getSource().getOutputSymbols()) .withDefaultParallelism(session) .withPartitioning(groupingKeys); PlanWithProperties child = planAndEnforce(node.getSource(), childRequirements, childRequirements); List<Symbol> preGroupedSymbols = ImmutableList.of(); if (!LocalProperties.match(child.getProperties().getLocalProperties(), LocalProperties.grouped(groupingKeys)).get(0).isPresent()) { preGroupedSymbols = groupingKeys; } AggregationNode result = new AggregationNode( node.getId(), child.getNode(), node.getAggregations(), node.getGroupingSets(), preGroupedSymbols, node.getStep(), node.getHashSymbol(), node.getGroupIdSymbol()); return deriveProperties(result, child.getProperties()); }