package edu.washington.escience.myria.parallel; import java.util.LinkedList; import java.util.List; import java.util.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import edu.washington.escience.myria.api.encoding.QueryConstruct; import edu.washington.escience.myria.api.encoding.QueryConstruct.ConstructArgs; /** * A {@link QueryPlan} node that signifies running each of its children serially. */ public final class DoWhile extends QueryPlan { /** The query plans in the body. */ private final List<QueryPlan> body; /** The relation to be scanned for the while loop continuation condition. */ private final String condition; /** Whether this while loop has ever been run. */ private boolean hasRun; /** * Construct a {@link QueryPlan} that runs the body tasks in sequence, and loops if the singleton boolean relation * specified by {@code condition} is {@code true}. * * @param body the tasks to be run. * @param condition the relation to be scanned for the while loop continuation condition. Must be a singleton boolean * relation. */ public DoWhile(final List<? extends QueryPlan> body, final String condition) { this.body = ImmutableList.copyOf(Objects.requireNonNull(body, "body")); this.condition = Objects.requireNonNull(condition, "condition"); hasRun = false; } /** * {@inheritdoc} * * Instantiating a {@link DoWhile} is a somewhat complex operation. * * 1. Every {@link DoWhile} tracks whether or not the loop has been entered in {@link #hasRun}. For all times but the * first, the {@link #condition} is checked and, if {@code false}, the {@link DoWhile} is simply removed from the * {@code planQ} and nothing is added. This signifies the loop termination. * * 2. When we want to execute the loop, we want to do two things: run the body, then gather the value of the * termination condition and store it at the {@link Server} in a query global variable. Thus we want to add the * sequence of operations {@code body, then [gatherCondition]} to the plan. * * @param planQ the queue of {@link QueryPlan} tasks * @param subQueryQ the queue of {@link SubQuery} tasks * @param args the {@link QueryConstruct#ConstructArgs} arguments needed to instantiate a query plan */ @Override public void instantiate( final LinkedList<QueryPlan> planQ, final LinkedList<SubQuery> subQueryQ, final ConstructArgs args) { QueryPlan checkTask = planQ.peekFirst(); Verify.verify( checkTask == this, "this %s should be the first object on the queue, not %s!", this, checkTask); if (hasRun) { Boolean b = (Boolean) args.getServer().getQueryGlobal(args.getQueryId(), condition); Preconditions.checkState( b != null, "Query %s: DoWhile Loop has run, but global variable for loop condition (%s) has not been set", condition); if (!b.booleanValue()) { planQ.removeFirst(); return; } for (QueryPlan q : body) { q.reset(); } } // Note that the setDoWhileCondition will actually end up being after the body in the planQ planQ.addFirst(QueryConstruct.setDoWhileCondition(condition)); planQ.addAll(0, body); hasRun = true; } @Override public void reset() { hasRun = false; for (QueryPlan q : body) { q.reset(); } } }