// 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.base.Preconditions; import com.google.common.collect.ImmutableList; 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.query2.engine.QueryEnvironment.ThreadSafeMutableSet; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; /** * A binary algebraic set operation. * *
 * expr ::= expr (INTERSECT expr)+
 *        | expr ('^' expr)+
 *        | expr (UNION expr)+
 *        | expr ('+' expr)+
 *        | expr (EXCEPT expr)+
 *        | expr ('-' expr)+
 * 
*/ public class BinaryOperatorExpression extends QueryExpression { private final Lexer.TokenKind operator; // ::= INTERSECT/CARET | UNION/PLUS | EXCEPT/MINUS private final ImmutableList operands; public BinaryOperatorExpression(Lexer.TokenKind operator, List operands) { Preconditions.checkState(operands.size() > 1); this.operator = operator; this.operands = ImmutableList.copyOf(operands); } public Lexer.TokenKind getOperator() { return operator; } public ImmutableList getOperands() { return operands; } @Override public QueryTaskFuture eval( QueryEnvironment env, QueryExpressionContext context, Callback 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. * *

N.B. {@code operands.size()} may be {@code 1}. */ private static QueryTaskFuture evalPlus( ImmutableList operands, QueryEnvironment env, QueryExpressionContext context, Callback callback) { ArrayList> 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 QueryTaskFuture evalMinus( final ImmutableList operands, final QueryEnvironment env, final QueryExpressionContext context, final Callback callback) { QueryTaskFuture> lhsValueFuture = QueryUtil.evalAll(env, context, operands.get(0)); Function, QueryTaskFuture> subtractAsyncFunction = lhsValue -> { final Set threadSafeLhsValue = lhsValue; Callback subtractionCallback = new Callback() { @Override public void process(Iterable partialResult) { for (T target : partialResult) { threadSafeLhsValue.remove(target); } } }; QueryTaskFuture rhsEvaluatedFuture = evalPlus(operands.subList(1, operands.size()), env, context, subtractionCallback); return env.whenSucceedsCall( rhsEvaluatedFuture, new QueryTaskCallable() { @Override public Void call() throws QueryException, InterruptedException { callback.process(threadSafeLhsValue); return null; } }); }; return env.transformAsync(lhsValueFuture, subtractAsyncFunction); } private QueryTaskFuture evalIntersect( final QueryEnvironment env, final QueryExpressionContext context, final Callback 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> rollingResultFuture = QueryUtil.evalAll(env, context, operands.get(0)); for (int i = 1; i < operands.size(); i++) { final int index = i; Function, QueryTaskFuture>> evalOperandAndIntersectAsyncFunction = rollingResult -> { final QueryTaskFuture> rhsOperandValueFuture = QueryUtil.evalAll(env, context, operands.get(index)); return env.whenSucceedsCall( rhsOperandValueFuture, new QueryTaskCallable>() { @Override public ThreadSafeMutableSet call() throws QueryException, InterruptedException { rollingResult.retainAll(rhsOperandValueFuture.getIfSuccessful()); return rollingResult; } }); }; rollingResultFuture = env.transformAsync(rollingResultFuture, evalOperandAndIntersectAsyncFunction); } final QueryTaskFuture> resultFuture = rollingResultFuture; return env.whenSucceedsCall( resultFuture, new QueryTaskCallable() { @Override public Void call() throws QueryException, InterruptedException { callback.process(resultFuture.getIfSuccessful()); return null; } }); } @Override public void collectTargetPatterns(Collection literals) { for (QueryExpression subExpression : operands) { subExpression.collectTargetPatterns(literals); } } @Override public T accept(QueryExpressionVisitor visitor, C context) { return visitor.visit(this, context); } @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(); } }