aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/query2/engine/BinaryOperatorExpression.java
blob: cd042ea2352a45e830b40e74dc1864d85d86c14a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// 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.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 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<ThreadSafeMutableSet<T>> lhsValueFuture =
        QueryUtil.evalAll(env, context, operands.get(0));
    Function<ThreadSafeMutableSet<T>, QueryTaskFuture<Void>> subtractAsyncFunction =
        new Function<ThreadSafeMutableSet<T>, QueryTaskFuture<Void>>() {
      @Override
      public QueryTaskFuture<Void> apply(ThreadSafeMutableSet<T> lhsValue) {
        final Set<T> threadSafeLhsValue = 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, subtractAsyncFunction);
  }

  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<ThreadSafeMutableSet<T>> rollingResultFuture =
        QueryUtil.evalAll(env, context, operands.get(0));
    for (int i = 1; i < operands.size(); i++) {
      final int index = i;
      Function<ThreadSafeMutableSet<T>, QueryTaskFuture<ThreadSafeMutableSet<T>>>
          evalOperandAndIntersectAsyncFunction =
          new Function<ThreadSafeMutableSet<T>, QueryTaskFuture<ThreadSafeMutableSet<T>>>() {
            @Override
            public QueryTaskFuture<ThreadSafeMutableSet<T>> apply(
                final ThreadSafeMutableSet<T> rollingResult) {
              final QueryTaskFuture<ThreadSafeMutableSet<T>> rhsOperandValueFuture =
                  QueryUtil.evalAll(env, context, operands.get(index));
              return env.whenSucceedsCall(
                  rhsOperandValueFuture,
                  new QueryTaskCallable<ThreadSafeMutableSet<T>>() {
                    @Override
                    public ThreadSafeMutableSet<T> call()
                        throws QueryException, InterruptedException {
                      rollingResult.retainAll(rhsOperandValueFuture.getIfSuccessful());
                      return rollingResult;
                    }
                  });
            }
      };
      rollingResultFuture =
          env.transformAsync(rollingResultFuture, evalOperandAndIntersectAsyncFunction);
    }
    final QueryTaskFuture<ThreadSafeMutableSet<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();
  }
}