/*
* Licensed to 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.executor.transport;
import io.crate.action.job.ContextPreparer;
import io.crate.action.sql.DDLStatementDispatcher;
import io.crate.action.sql.ShowStatementDispatcher;
import io.crate.analyze.EvaluatingNormalizer;
import io.crate.analyze.symbol.SelectSymbol;
import io.crate.data.BatchConsumer;
import io.crate.data.CollectingBatchConsumer;
import io.crate.data.Row;
import io.crate.executor.Executor;
import io.crate.executor.Task;
import io.crate.executor.task.DDLTask;
import io.crate.executor.task.ExplainTask;
import io.crate.executor.task.NoopTask;
import io.crate.executor.task.SetSessionTask;
import io.crate.executor.transport.executionphases.ExecutionPhasesTask;
import io.crate.executor.transport.task.*;
import io.crate.executor.transport.task.elasticsearch.*;
import io.crate.jobs.JobContextService;
import io.crate.metadata.Functions;
import io.crate.metadata.ReplaceMode;
import io.crate.operation.InputFactory;
import io.crate.operation.NodeOperationTree;
import io.crate.operation.collect.sources.SystemCollectSource;
import io.crate.operation.projectors.ProjectionToProjectorVisitor;
import io.crate.planner.*;
import io.crate.planner.node.ddl.*;
import io.crate.planner.node.dml.ESDelete;
import io.crate.planner.node.dml.Upsert;
import io.crate.planner.node.dml.UpsertById;
import io.crate.planner.node.dql.ESGet;
import io.crate.planner.node.dql.QueryThenFetch;
import io.crate.planner.node.dql.join.NestedLoop;
import io.crate.planner.node.management.ExplainPlan;
import io.crate.planner.node.management.GenericShowPlan;
import io.crate.planner.node.management.KillPlan;
import io.crate.planner.statement.SetSessionPlan;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.action.bulk.BulkRetryCoordinatorPool;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Singleton;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.threadpool.ThreadPool;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
@Singleton
public class TransportExecutor implements Executor {
private static final Logger LOGGER = Loggers.getLogger(TransportExecutor.class);
private final IndexNameExpressionResolver indexNameExpressionResolver;
private final Functions functions;
private final TaskCollectingVisitor plan2TaskVisitor;
private DDLStatementDispatcher ddlAnalysisDispatcherProvider;
private ShowStatementDispatcher showStatementDispatcherProvider;
private final ClusterService clusterService;
private final JobContextService jobContextService;
private final ContextPreparer contextPreparer;
private final TransportActionProvider transportActionProvider;
private final IndicesService indicesService;
private final BulkRetryCoordinatorPool bulkRetryCoordinatorPool;
private final ProjectionToProjectorVisitor globalProjectionToProjectionVisitor;
private final MultiPhaseExecutor multiPhaseExecutor = new MultiPhaseExecutor();
private final static BulkNodeOperationTreeGenerator BULK_NODE_OPERATION_VISITOR = new BulkNodeOperationTreeGenerator();
@Inject
public TransportExecutor(Settings settings,
JobContextService jobContextService,
ContextPreparer contextPreparer,
TransportActionProvider transportActionProvider,
IndexNameExpressionResolver indexNameExpressionResolver,
ThreadPool threadPool,
Functions functions,
DDLStatementDispatcher ddlAnalysisDispatcherProvider,
ShowStatementDispatcher showStatementDispatcherProvider,
ClusterService clusterService,
IndicesService indicesService,
BulkRetryCoordinatorPool bulkRetryCoordinatorPool,
SystemCollectSource systemCollectSource) {
this.jobContextService = jobContextService;
this.contextPreparer = contextPreparer;
this.transportActionProvider = transportActionProvider;
this.indexNameExpressionResolver = indexNameExpressionResolver;
this.functions = functions;
this.ddlAnalysisDispatcherProvider = ddlAnalysisDispatcherProvider;
this.showStatementDispatcherProvider = showStatementDispatcherProvider;
this.clusterService = clusterService;
this.indicesService = indicesService;
this.bulkRetryCoordinatorPool = bulkRetryCoordinatorPool;
plan2TaskVisitor = new TaskCollectingVisitor();
EvaluatingNormalizer normalizer = EvaluatingNormalizer.functionOnlyNormalizer(functions, ReplaceMode.COPY);
globalProjectionToProjectionVisitor = new ProjectionToProjectorVisitor(
clusterService,
functions,
indexNameExpressionResolver,
threadPool,
settings,
transportActionProvider,
bulkRetryCoordinatorPool,
new InputFactory(functions),
normalizer,
systemCollectSource::getRowUpdater
);
}
@Override
public void execute(Plan plan, BatchConsumer consumer, Row parameters) {
CompletableFuture<Plan> planFuture = multiPhaseExecutor.process(plan, null);
planFuture
.thenAccept(p -> plan2TaskVisitor.process(p, null).execute(consumer, parameters))
.exceptionally(t -> { consumer.accept(null, t); return null; });
}
@Override
public List<CompletableFuture<Long>> executeBulk(Plan plan) {
Task task = plan2TaskVisitor.process(plan, null);
return task.executeBulk();
}
private class TaskCollectingVisitor extends PlanVisitor<Void, Task> {
@Override
public Task visitNoopPlan(NoopPlan plan, Void context) {
return NoopTask.INSTANCE;
}
@Override
public Task visitSetSessionPlan(SetSessionPlan plan, Void context) {
return new SetSessionTask(plan.jobId(), plan.settings(), plan.sessionContext());
}
@Override
public Task visitExplainPlan(ExplainPlan explainPlan, Void context) {
return new ExplainTask(explainPlan);
}
@Override
protected Task visitPlan(Plan plan, Void context) {
return executionPhasesTask(plan);
}
private ExecutionPhasesTask executionPhasesTask(Plan plan) {
List<NodeOperationTree> nodeOperationTrees = BULK_NODE_OPERATION_VISITOR.createNodeOperationTrees(
plan, clusterService.localNode().getId());
LOGGER.debug("Created NodeOperationTrees from Plan: {}", nodeOperationTrees);
return new ExecutionPhasesTask(
plan.jobId(),
clusterService,
contextPreparer,
jobContextService,
indicesService,
transportActionProvider.transportJobInitAction(),
transportActionProvider.transportKillJobsNodeAction(),
nodeOperationTrees
);
}
@Override
public Task visitGetPlan(ESGet plan, Void context) {
return new ESGetTask(
functions,
globalProjectionToProjectionVisitor,
transportActionProvider,
plan,
jobContextService);
}
@Override
public Task visitDropTablePlan(DropTablePlan plan, Void context) {
return new DropTableTask(plan,
transportActionProvider.transportDeleteIndexTemplateAction(),
transportActionProvider.transportDeleteIndexAction());
}
@Override
public Task visitKillPlan(KillPlan killPlan, Void context) {
return killPlan.jobToKill().isPresent() ?
new KillJobTask(transportActionProvider.transportKillJobsNodeAction(),
killPlan.jobId(),
killPlan.jobToKill().get()) :
new KillTask(transportActionProvider.transportKillAllNodeAction(), killPlan.jobId());
}
@Override
public Task visitGenericShowPlan(GenericShowPlan genericShowPlan, Void context) {
return new GenericShowTask(genericShowPlan.jobId(), showStatementDispatcherProvider, genericShowPlan.statement());
}
@Override
public Task visitGenericDDLPLan(GenericDDLPlan genericDDLPlan, Void context) {
return new DDLTask(genericDDLPlan.jobId(), ddlAnalysisDispatcherProvider, genericDDLPlan.statement());
}
@Override
public Task visitESClusterUpdateSettingsPlan(ESClusterUpdateSettingsPlan plan, Void context) {
return new ESClusterUpdateSettingsTask(plan, transportActionProvider.transportClusterUpdateSettingsAction());
}
@Override
public Task visitCreateAnalyzerPlan(CreateAnalyzerPlan plan, Void context) {
return new CreateAnalyzerTask(plan, transportActionProvider.transportClusterUpdateSettingsAction());
}
@Override
public Task visitESDelete(ESDelete plan, Void context) {
return new ESDeleteTask(plan, transportActionProvider.transportDeleteAction(), jobContextService);
}
@Override
public Task visitUpsertById(UpsertById plan, Void context) {
return new UpsertByIdTask(
plan,
clusterService,
indexNameExpressionResolver,
clusterService.state().metaData().settings(),
transportActionProvider.transportShardUpsertAction()::execute,
transportActionProvider.transportCreateIndexAction(),
transportActionProvider.transportBulkCreateIndicesAction(),
bulkRetryCoordinatorPool,
jobContextService);
}
@Override
public Task visitESDeletePartition(ESDeletePartition plan, Void context) {
return new ESDeletePartitionTask(plan, transportActionProvider.transportDeleteIndexAction());
}
@Override
public Task visitMultiPhasePlan(MultiPhasePlan multiPhasePlan, final Void context) {
throw new UnsupportedOperationException("MultiPhasePlan should have been processed by MultiPhaseExecutor");
}
}
/**
* Executor that triggers the execution of MultiPhasePlans.
* E.g.:
*
* processing a Plan that looks as follows:
* <pre>
* QTF
* |
* +-- MultiPhasePlan
* root: Collect3
* deps: [Collect1, Collect2]
* </pre>
*
* Executions will be triggered for collect1 and collect2, and if those are completed, Collect3 will be returned
* as future which encapsulated the execution
*/
private class MultiPhaseExecutor extends PlanVisitor<Void, CompletableFuture<Plan>> {
@Override
protected CompletableFuture<Plan> visitPlan(Plan plan, Void context) {
return CompletableFuture.completedFuture(plan);
}
@Override
public CompletableFuture<Plan> visitMerge(Merge merge, Void context) {
return process(merge.subPlan(), context).thenApply(p -> merge);
}
@Override
public CompletableFuture<Plan> visitNestedLoop(NestedLoop plan, Void context) {
CompletableFuture<Plan> fLeft = process(plan.left(), context);
CompletableFuture<Plan> fRight = process(plan.right(), context);
return CompletableFuture.allOf(fLeft, fRight).thenApply(x -> plan);
}
@Override
public CompletableFuture<Plan> visitQueryThenFetch(QueryThenFetch qtf, Void context) {
return process(qtf.subPlan(), context).thenApply(x -> qtf);
}
@Override
public CompletableFuture<Plan> visitMultiPhasePlan(MultiPhasePlan multiPhasePlan, Void context) {
Map<Plan, SelectSymbol> dependencies = multiPhasePlan.dependencies();
List<CompletableFuture<?>> dependencyFutures = new ArrayList<>();
Plan rootPlan = multiPhasePlan.rootPlan();
for (Map.Entry<Plan, SelectSymbol> entry : dependencies.entrySet()) {
Plan plan = entry.getKey();
SubSelectSymbolReplacer replacer = new SubSelectSymbolReplacer(rootPlan, entry.getValue());
CollectingBatchConsumer<Object[], Object> consumer = SingleRowSingleValueConsumer.create();
CompletableFuture<Plan> planFuture = process(plan, context);
planFuture.whenComplete((p, e) -> {
if (e == null) {
// must use plan2TaskVisitor instead of calling execute
// to avoid triggering MultiPhasePlans inside p again (they're already processed).
// since plan's are not mutated to remove them they're still part of the plan tree
plan2TaskVisitor.process(p, null).execute(consumer, Row.EMPTY);
} else {
consumer.accept(null, e);
}
});
dependencyFutures.add(consumer.resultFuture().thenAccept(replacer::onSuccess));
}
CompletableFuture[] cfs = dependencyFutures.toArray(new CompletableFuture[0]);
return CompletableFuture.allOf(cfs).thenCompose(x -> process(rootPlan, context));
}
}
static class BulkNodeOperationTreeGenerator extends PlanVisitor<BulkNodeOperationTreeGenerator.Context, Void> {
List<NodeOperationTree> createNodeOperationTrees(Plan plan, String localNodeId) {
Context context = new Context(localNodeId);
process(plan, context);
return context.nodeOperationTrees;
}
@Override
public Void visitUpsert(Upsert node, Context context) {
for (Plan plan : node.nodes()) {
context.nodeOperationTrees.add(NodeOperationTreeGenerator.fromPlan(plan, context.localNodeId));
}
return null;
}
@Override
protected Void visitPlan(Plan plan, Context context) {
context.nodeOperationTrees.add(NodeOperationTreeGenerator.fromPlan(plan, context.localNodeId));
return null;
}
static class Context {
private final List<NodeOperationTree> nodeOperationTrees = new ArrayList<>();
private final String localNodeId;
public Context(String localNodeId) {
this.localNodeId = localNodeId;
}
}
}
}