package org.rakam.report;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.Optional.ofNullable;
import static org.rakam.report.QueryStats.State.RUNNING;
public class ChainQueryExecution
implements QueryExecution
{
private final List<QueryExecution> executions;
private final String query;
private final CompletableFuture<QueryExecution> chainedQuery;
public ChainQueryExecution(List<QueryExecution> executions, String query, Function<List<QueryResult>, QueryExecution> chainedQuery)
{
this(executions, query, Optional.of(chainedQuery));
}
public ChainQueryExecution(List<QueryExecution> executions, String query)
{
this(executions, query, Optional.empty());
}
public ChainQueryExecution(List<QueryExecution> executions, String query, Optional<Function<List<QueryResult>, QueryExecution>> chainedQuery)
{
this.executions = executions;
this.query = query;
if(chainedQuery.isPresent()) {
CompletableFuture<Void> afterAll = CompletableFuture
.allOf(executions.stream().map(e -> e.getResult())
.toArray(CompletableFuture[]::new));
this.chainedQuery = afterAll.thenApply(r -> {
List<QueryResult> collect = executions.stream().map(e -> e.getResult().join())
.collect(Collectors.toList());
return chainedQuery.get().apply(collect);
});
} else {
this.chainedQuery = null;
}
}
@Override
public QueryStats currentStats()
{
QueryStats currentStats = null;
for (QueryExecution queryExecution : executions) {
QueryStats queryStats = queryExecution.currentStats();
if (currentStats == null) {
currentStats = queryStats;
}
else {
currentStats = merge(currentStats, queryStats);
}
}
if (chainedQuery != null && chainedQuery.isDone()) {
currentStats = merge(currentStats, chainedQuery.join().currentStats());
}
return currentStats;
}
private QueryStats merge(QueryStats currentStats, QueryStats stats)
{
return new QueryStats(
ofNullable(currentStats.percentage).orElse(0) + ofNullable(stats.percentage).orElse(0),
Objects.equals(currentStats.state, stats.state) ? currentStats.state : RUNNING,
(currentStats.node != null && stats.node != null) ?
Math.max(currentStats.node, stats.node) :
currentStats.node != null ? currentStats.node : stats.node,
ofNullable(stats.processedRows).orElse(0L) + ofNullable(currentStats.processedRows).orElse(0L),
ofNullable(stats.processedBytes).orElse(0L) + ofNullable(currentStats.processedBytes).orElse(0L),
ofNullable(stats.userTime).orElse(0L) + ofNullable(currentStats.userTime).orElse(0L),
ofNullable(stats.cpuTime).orElse(0L) + ofNullable(currentStats.cpuTime).orElse(0L),
ofNullable(stats.wallTime).orElse(0L) + ofNullable(currentStats.wallTime).orElse(0L)
);
}
@Override
public boolean isFinished()
{
if (chainedQuery != null && chainedQuery.isDone()) {
QueryExecution join = chainedQuery.join();
return join == null || join.isFinished();
}
else {
return false;
}
}
@Override
public CompletableFuture<QueryResult> getResult()
{
if (chainedQuery == null) {
return CompletableFuture
.allOf(executions.stream().map(e -> e.getResult())
.toArray(CompletableFuture[]::new)).thenApply(r -> QueryResult.empty());
}
CompletableFuture<QueryResult> future = new CompletableFuture<>();
chainedQuery.thenAccept(r -> {
if (r == null) {
future.complete(null);
}
else {
r.getResult().thenAccept(result -> {
future.complete(result);
});
}
});
return future;
}
@Override
public void kill()
{
executions.forEach(org.rakam.report.QueryExecution::kill);
if (chainedQuery != null) {
chainedQuery.thenAccept(q -> q.kill());
}
}
}