// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed 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. package com.google.devtools.build.lib.query2.engine; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryTaskCallable; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryTaskFuture; import com.google.devtools.build.lib.util.Preconditions; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; /** * A binary algebraic set operation. * * <pre> * expr ::= expr (INTERSECT expr)+ * | expr ('^' expr)+ * | expr (UNION expr)+ * | expr ('+' expr)+ * | expr (EXCEPT expr)+ * | expr ('-' expr)+ * </pre> */ public class BinaryOperatorExpression extends QueryExpression { private final Lexer.TokenKind operator; // ::= INTERSECT/CARET | UNION/PLUS | EXCEPT/MINUS private final ImmutableList<QueryExpression> operands; public BinaryOperatorExpression(Lexer.TokenKind operator, List<QueryExpression> operands) { Preconditions.checkState(operands.size() > 1); this.operator = operator; this.operands = ImmutableList.copyOf(operands); } public Lexer.TokenKind getOperator() { return operator; } public ImmutableList<QueryExpression> getOperands() { return operands; } @Override public <T> QueryTaskFuture<Void> eval( QueryEnvironment<T> env, VariableContext<T> context, Callback<T> callback) { switch (operator) { case PLUS: case UNION: return evalPlus(operands, env, context, callback); case MINUS: case EXCEPT: return evalMinus(operands, env, context, callback); case INTERSECT: case CARET: return evalIntersect(env, context, callback); default: throw new IllegalStateException(operator.toString()); } } /** * Evaluates an expression of the form "e1 + e2 + ... + eK" by evaluating all the subexpressions * separately. * * <p>N.B. {@code operands.size()} may be {@code 1}. */ private static <T> QueryTaskFuture<Void> evalPlus( ImmutableList<QueryExpression> operands, QueryEnvironment<T> env, VariableContext<T> context, Callback<T> callback) { ArrayList<QueryTaskFuture<Void>> queryTasks = new ArrayList<>(operands.size()); for (QueryExpression operand : operands) { queryTasks.add(env.eval(operand, context, callback)); } return env.whenAllSucceed(queryTasks); } /** * Evaluates an expression of the form "e1 - e2 - ... - eK" by noting its equivalence to * "e1 - (e2 + ... + eK)" and evaluating the subexpressions on the right-hand-side separately. */ private static <T> QueryTaskFuture<Void> evalMinus( final ImmutableList<QueryExpression> operands, final QueryEnvironment<T> env, final VariableContext<T> context, final Callback<T> callback) { QueryTaskFuture<Set<T>> lhsValueFuture = QueryUtil.evalAll(env, context, operands.get(0)); Function<Set<T>, QueryTaskFuture<Void>> substractAsyncFunction = new Function<Set<T>, QueryTaskFuture<Void>>() { @Override public QueryTaskFuture<Void> apply(Set<T> lhsValue) { final Set<T> threadSafeLhsValue = Sets.newConcurrentHashSet(lhsValue); Callback<T> subtractionCallback = new Callback<T>() { @Override public void process(Iterable<T> partialResult) { for (T target : partialResult) { threadSafeLhsValue.remove(target); } } }; QueryTaskFuture<Void> rhsEvaluatedFuture = evalPlus( operands.subList(1, operands.size()), env, context, subtractionCallback); return env.whenSucceedsCall( rhsEvaluatedFuture, new QueryTaskCallable<Void>() { @Override public Void call() throws QueryException, InterruptedException { callback.process(threadSafeLhsValue); return null; } }); } }; return env.transformAsync(lhsValueFuture, substractAsyncFunction); } private <T> QueryTaskFuture<Void> evalIntersect( final QueryEnvironment<T> env, final VariableContext<T> context, final Callback<T> callback) { // For each right-hand side operand, intersection cannot be performed in a streaming manner; the // entire result of that operand is needed. So, in order to avoid pinning too much in memory at // once, we process each right-hand side operand one at a time and throw away that operand's // result. // TODO(bazel-team): Consider keeping just the name / label of the right-hand side results // instead of the potentially heavy-weight instances of type T. This would let us process all // right-hand side operands in parallel without worrying about memory usage. QueryTaskFuture<Set<T>> rollingResultFuture = QueryUtil.evalAll(env, context, operands.get(0)); for (int i = 1; i < operands.size(); i++) { final int index = i; Function<Set<T>, QueryTaskFuture<Set<T>>> evalOperandAndIntersectAsyncFunction = new Function<Set<T>, QueryTaskFuture<Set<T>>>() { @Override public QueryTaskFuture<Set<T>> apply(final Set<T> rollingResult) { final QueryTaskFuture<Set<T>> rhsOperandValueFuture = QueryUtil.evalAll(env, context, operands.get(index)); return env.whenSucceedsCall( rhsOperandValueFuture, new QueryTaskCallable<Set<T>>() { @Override public Set<T> call() throws QueryException, InterruptedException { rollingResult.retainAll(rhsOperandValueFuture.getIfSuccessful()); return rollingResult; } }); } }; rollingResultFuture = env.transformAsync(rollingResultFuture, evalOperandAndIntersectAsyncFunction); } final QueryTaskFuture<Set<T>> resultFuture = rollingResultFuture; return env.whenSucceedsCall( resultFuture, new QueryTaskCallable<Void>() { @Override public Void call() throws QueryException, InterruptedException { callback.process(resultFuture.getIfSuccessful()); return null; } }); } @Override public void collectTargetPatterns(Collection<String> literals) { for (QueryExpression subExpression : operands) { subExpression.collectTargetPatterns(literals); } } @Override public QueryExpression getMapped(QueryExpressionMapper mapper) { return mapper.map(this); } @Override public String toString() { StringBuilder result = new StringBuilder(); for (int i = 1; i < operands.size(); i++) { result.append("("); } result.append(operands.get(0)); for (int i = 1; i < operands.size(); i++) { result.append(" " + operator.getPrettyName() + " " + operands.get(i) + ")"); } return result.toString(); } }