/* * 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; import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import io.crate.analyze.symbol.Symbol; import io.crate.analyze.symbol.format.SymbolPrinter; import io.crate.planner.distribution.DistributionInfo; import io.crate.planner.distribution.UpstreamPhase; import io.crate.planner.node.ExecutionPhase; import io.crate.planner.node.ExecutionPhaseVisitor; import io.crate.planner.node.dql.*; import io.crate.planner.node.dql.join.NestedLoop; import io.crate.planner.node.dql.join.NestedLoopPhase; import io.crate.planner.node.fetch.FetchPhase; import io.crate.planner.projection.Projection; import io.crate.planner.projection.ProjectionVisitor; import io.crate.planner.projection.TopNProjection; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; public class PlanPrinter { private PlanPrinter() { } public static Map<String, Object> objectMap(Plan plan) { return Plan2MapVisitor.toMap(plan); } private static List<Object> refs(Collection<? extends Symbol> symbols) { List<Object> refs = new ArrayList<>(symbols.size()); for (Symbol s : symbols) { refs.add(SymbolPrinter.INSTANCE.print(s, SymbolPrinter.Style.FULL_QUALIFIED)); } return refs; } private static class ExecutionPhase2MapVisitor extends ExecutionPhaseVisitor<Void, ImmutableMap.Builder<String, Object>> { public static final ExecutionPhase2MapVisitor INSTANCE = new ExecutionPhase2MapVisitor(); private ExecutionPhase2MapVisitor() { } static ImmutableMap.Builder<String, Object> toBuilder(ExecutionPhase executionPhase) { assert executionPhase != null : "executionPhase must not be null"; return INSTANCE.process(executionPhase, null); } private static Iterable<Map<String, Object>> projections(Iterable<Projection> projections) { return FluentIterable.from(projections).transform(new Function<Projection, Map<String, Object>>() { @Nullable @Override public Map<String, Object> apply(@Nullable Projection projection) { assert projection != null : "projection must not be null"; return Projection2MapVisitor.toBuilder(projection).build(); } }).toList(); // need to use a List because this is part of a map which is streamed to the client. // Map streaming uses StreamOutput#writeGenericValue which can't handle Iterable } private static ImmutableMap.Builder<String, Object> newBuilder() { return ImmutableMap.builder(); } @Override protected ImmutableMap.Builder<String, Object> visitExecutionPhase(ExecutionPhase phase, Void context) { return newBuilder() .put("phaseType", phase.type().toString()) .put("id", phase.phaseId()) // Converting TreeMap.keySet() to be able to stream .put("executionNodes", new ArrayList<>(phase.nodeIds()) ); } private ImmutableMap.Builder<String, Object> process(DistributionInfo info) { return newBuilder() .put("distributedByColumn", info.distributeByColumn()) .put("type", info.distributionType().toString()); } private ImmutableMap.Builder<String, Object> upstreamPhase(UpstreamPhase phase, ImmutableMap.Builder<String, Object> b) { return b.put("distribution", process(phase.distributionInfo()).build()); } private ImmutableMap.Builder<String, Object> dqlPlanNode(AbstractProjectionsPhase phase, ImmutableMap.Builder<String, Object> b) { if (phase.hasProjections()) { b.put("projections", projections(phase.projections())); } return b; } @Override public ImmutableMap.Builder<String, Object> visitRoutedCollectPhase(RoutedCollectPhase phase, Void context) { ImmutableMap.Builder<String, Object> builder = visitCollectPhase(phase, context); builder = dqlPlanNode(phase, builder); builder.put("routing", phase.routing().locations()); return builder; } @Override public ImmutableMap.Builder<String, Object> visitCollectPhase(CollectPhase phase, Void context) { ImmutableMap.Builder<String, Object> builder = upstreamPhase(phase, visitExecutionPhase(phase, context)); builder.put("toCollect", refs(phase.toCollect())); return builder; } @Override public ImmutableMap.Builder<String, Object> visitCountPhase(CountPhase phase, Void context) { return upstreamPhase(phase, visitExecutionPhase(phase, context)); } @Override public ImmutableMap.Builder<String, Object> visitFetchPhase(FetchPhase phase, Void context) { return visitExecutionPhase(phase, context) .put("fetchRefs", refs(phase.fetchRefs())); } @Override public ImmutableMap.Builder<String, Object> visitMergePhase(MergePhase phase, Void context) { ImmutableMap.Builder<String, Object> b = upstreamPhase(phase, visitExecutionPhase(phase, context)); return dqlPlanNode(phase, b); } @Override public ImmutableMap.Builder<String, Object> visitNestedLoopPhase(NestedLoopPhase phase, Void context) { ImmutableMap.Builder<String, Object> b = upstreamPhase(phase, visitExecutionPhase(phase, context)); return dqlPlanNode(phase, b); } } private static class Projection2MapVisitor extends ProjectionVisitor<Void, ImmutableMap.Builder<String, Object>> { private static final Projection2MapVisitor INSTANCE = new Projection2MapVisitor(); private static ImmutableMap.Builder<String, Object> newBuilder() { return ImmutableMap.builder(); } static ImmutableMap.Builder<String, Object> toBuilder(Projection projection) { assert projection != null : "projection must not be null"; return INSTANCE.process(projection, null); } @Override protected ImmutableMap.Builder<String, Object> visitProjection(Projection projection, Void context) { return newBuilder().put("type", projection.projectionType().toString()); } @Override public ImmutableMap.Builder<String, Object> visitTopNProjection(TopNProjection projection, Void context) { return visitProjection(projection, context) .put("rows", projection.offset() + "-" + projection.limit()); } } private static class Plan2MapVisitor extends PlanVisitor<Void, ImmutableMap.Builder<String, Object>> { private static final Plan2MapVisitor INSTANCE = new Plan2MapVisitor(); private static ImmutableMap.Builder<String, Object> newBuilder() { return ImmutableMap.builder(); } @Override protected ImmutableMap.Builder<String, Object> visitPlan(Plan plan, Void context) { return newBuilder() .put("planType", plan.getClass().getSimpleName()); } private static Map<String, Object> phaseMap(@Nullable ExecutionPhase node) { if (node == null) { return null; } else { return ExecutionPhase2MapVisitor.toBuilder(node).build(); } } static Map<String, Object> toMap(Plan plan) { assert plan != null : "plan must not be null"; return INSTANCE.process(plan, null).build(); } @Override public ImmutableMap.Builder<String, Object> visitCollect(Collect plan, Void context) { ImmutableMap.Builder<String, Object> b = visitPlan(plan, context) .put("collectPhase", phaseMap(plan.collectPhase())); return b; } @Override public ImmutableMap.Builder<String, Object> visitNestedLoop(NestedLoop plan, Void context) { return newBuilder() .put("planType", plan.getClass().getSimpleName()) .put("left", process(plan.left(), context).build()) .put("right", process(plan.right(), context).build()) .put("nestedLoopPhase", phaseMap(plan.nestedLoopPhase())); } @Override public ImmutableMap.Builder<String, Object> visitQueryThenFetch(QueryThenFetch plan, Void context) { return visitPlan(plan, context) .put("subPlan", toMap(plan.subPlan())) .put("fetchPhase", phaseMap(plan.fetchPhase())); } @Override public ImmutableMap.Builder<String, Object> visitMultiPhasePlan(MultiPhasePlan multiPhasePlan, Void context) { List<Map<String, Object>> dependencies = new ArrayList<>(multiPhasePlan.dependencies().size()); for (Plan dependency : multiPhasePlan.dependencies().keySet()) { dependencies.add(toMap(dependency)); } return visitPlan(multiPhasePlan, context) .put("rootPlan", toMap(multiPhasePlan.rootPlan())) .put("dependencies", dependencies); } @Override public ImmutableMap.Builder<String, Object> visitMerge(Merge merge, Void context) { return visitPlan(merge, context) .put("subPlan", toMap(merge.subPlan())) .put("mergePhase", phaseMap(merge.mergePhase())); } } }