/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.execution.steps;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicBoolean;
import org.diqube.execution.ExecutablePlanStep;
import org.diqube.execution.consumers.AbstractThreadedOverwritingRowIdConsumer;
import org.diqube.execution.consumers.DoneConsumer;
import org.diqube.execution.consumers.GenericConsumer;
import org.diqube.execution.consumers.OverwritingRowIdConsumer;
import org.diqube.execution.exception.ExecutablePlanBuildException;
import org.diqube.executionenv.ExecutionEnvironment;
import org.diqube.executionenv.VersionedExecutionEnvironment;
import org.diqube.queries.QueryRegistry;
import com.google.common.collect.Sets;
/**
* A logical AND on two row ID steps and works based on {@link OverwritingRowIdConsumer}s.
*
* TODO #2 STAT if estimated that one source provides lots of rowIds, while the other won't we should first execute the
* latter and then execute the second with applying the AND right in that step.
*
* This is similar to {@link RowIdAndStep}, but works on {@link OverwritingRowIdConsumer}s.
*
* <p>
* Input: Exactly two {@link OverwritingRowIdConsumer}s <br>
* Output: {@link OverwritingRowIdConsumer}.
*
* @author Bastian Gloeckle
*/
public class OverwritingRowIdAndStep extends AbstractThreadedExecutablePlanStep {
private AtomicBoolean leftSourceIsDone = new AtomicBoolean(false);
private Set<Long> leftRowIds = new ConcurrentSkipListSet<>();
private AtomicBoolean rightSourceIsDone = new AtomicBoolean(false);
private Set<Long> rightRowIds = new ConcurrentSkipListSet<>();
private ExecutionEnvironment latestEnv = null;
private Object latestEnvSync = new Object();
private AbstractThreadedOverwritingRowIdConsumer leftRowIdConsumer =
new AbstractThreadedOverwritingRowIdConsumer(this) {
@Override
public void allSourcesAreDone() {
OverwritingRowIdAndStep.this.leftSourceIsDone.set(true);
}
@Override
protected void doConsume(ExecutionEnvironment env, Long[] rowIds) {
for (long rowId : rowIds)
OverwritingRowIdAndStep.this.leftRowIds.add(rowId);
synchronized (latestEnvSync) {
if (latestEnv == null || !(env instanceof VersionedExecutionEnvironment)
|| ((latestEnv instanceof VersionedExecutionEnvironment) && ((VersionedExecutionEnvironment) env)
.getVersion() > ((VersionedExecutionEnvironment) latestEnv).getVersion()))
latestEnv = env;
}
}
};
private AbstractThreadedOverwritingRowIdConsumer rightRowIdConsumer =
new AbstractThreadedOverwritingRowIdConsumer(this) {
@Override
public void allSourcesAreDone() {
OverwritingRowIdAndStep.this.rightSourceIsDone.set(true);
}
@Override
protected void doConsume(ExecutionEnvironment env, Long[] rowIds) {
for (long rowId : rowIds)
OverwritingRowIdAndStep.this.rightRowIds.add(rowId);
synchronized (latestEnvSync) {
if (latestEnv == null || !(env instanceof VersionedExecutionEnvironment)
|| ((latestEnv instanceof VersionedExecutionEnvironment) && ((VersionedExecutionEnvironment) env)
.getVersion() > ((VersionedExecutionEnvironment) latestEnv).getVersion()))
latestEnv = env;
}
}
};
public OverwritingRowIdAndStep(int stepId, QueryRegistry queryRegistry) {
super(stepId, queryRegistry);
}
@Override
protected void execute() {
int leftRowIdSize = leftRowIds.size();
int rightRowIdSize = rightRowIds.size();
Set<Long> res = Sets.intersection(leftRowIds, rightRowIds);
Long[] resArray = res.toArray(new Long[res.size()]);
ExecutionEnvironment activeEnv;
synchronized (latestEnvSync) {
activeEnv = latestEnv;
}
if (activeEnv != null)
forEachOutputConsumerOfType(OverwritingRowIdConsumer.class, c -> c.consume(activeEnv, resArray));
if (leftSourceIsDone.get() && rightSourceIsDone.get() && leftRowIds.size() == leftRowIdSize
&& rightRowIds.size() == rightRowIdSize) {
forEachOutputConsumerOfType(GenericConsumer.class, c -> c.sourceIsDone());
doneProcessing();
}
}
@Override
protected void validateOutputConsumer(GenericConsumer consumer) throws IllegalArgumentException {
if (!(consumer instanceof DoneConsumer) && !(consumer instanceof OverwritingRowIdConsumer))
throw new IllegalArgumentException("Only OverwritingRowIdConsumer supported.");
}
@Override
protected List<GenericConsumer> inputConsumers() {
return Arrays.asList(new GenericConsumer[] { leftRowIdConsumer, rightRowIdConsumer });
}
@Override
protected String getAdditionalToStringDetails() {
return null;
}
@Override
public void wireOneInputConsumerToOutputOf(Class<? extends GenericConsumer> type, ExecutablePlanStep sourceStep)
throws ExecutablePlanBuildException {
if (type.equals(OverwritingRowIdConsumer.class)) {
if (leftRowIdConsumer.getNumberOfTimesWired() == 0)
sourceStep.addOutputConsumer(leftRowIdConsumer);
else if (rightRowIdConsumer.getNumberOfTimesWired() == 0)
sourceStep.addOutputConsumer(rightRowIdConsumer);
else
throw new ExecutablePlanBuildException(
"Could not wire additional input, because all inputs are wired already.");
} else
throw new ExecutablePlanBuildException("Could not wire input as only OverwritingRowIdConsumer are supported.");
}
}