/**
* 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.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import org.diqube.execution.ExecutablePlanStep;
import org.diqube.execution.consumers.AbstractThreadedRowIdConsumer;
import org.diqube.execution.consumers.DoneConsumer;
import org.diqube.execution.consumers.GenericConsumer;
import org.diqube.execution.consumers.RowIdConsumer;
import org.diqube.execution.exception.ExecutablePlanBuildException;
import org.diqube.queries.QueryRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A logical OR on two row ID steps.
*
* <p>
* Input: Exactly two {@link RowIdConsumer}s. <br>
* Output: {@link RowIdConsumer}.
*
* @author Bastian Gloeckle
*/
public class RowIdOrStep extends AbstractThreadedExecutablePlanStep {
private static final Logger logger = LoggerFactory.getLogger(RowIdOrStep.class);
private AtomicBoolean leftSourceIsEmpty = new AtomicBoolean(false);
private ConcurrentLinkedDeque<Long> leftRowIds = new ConcurrentLinkedDeque<>();
private AtomicBoolean rightSourceIsEmpty = new AtomicBoolean(false);
private ConcurrentLinkedDeque<Long> rightRowIds = new ConcurrentLinkedDeque<>();
private Set<Long> rowIdsSeenAlready = new HashSet<>();
private AbstractThreadedRowIdConsumer leftRowIdConsumer = new AbstractThreadedRowIdConsumer(this) {
@Override
public void allSourcesAreDone() {
RowIdOrStep.this.leftSourceIsEmpty.set(true);
}
@Override
protected void doConsume(Long[] rowIds) {
for (long rowId : rowIds)
RowIdOrStep.this.leftRowIds.add(rowId);
}
};
private AbstractThreadedRowIdConsumer rightRowIdConsumer = new AbstractThreadedRowIdConsumer(this) {
@Override
public void allSourcesAreDone() {
RowIdOrStep.this.rightSourceIsEmpty.set(true);
}
@Override
protected void doConsume(Long[] rowIds) {
for (long rowId : rowIds)
RowIdOrStep.this.rightRowIds.add(rowId);
}
};
public RowIdOrStep(int stepId, QueryRegistry queryRegistry) {
super(stepId, queryRegistry);
}
@Override
protected void execute() {
List<Long> newRowIds = new ArrayList<>();
Long rowId;
while ((rowId = leftRowIds.poll()) != null)
if (!rowIdsSeenAlready.contains(rowId))
newRowIds.add(rowId);
while ((rowId = rightRowIds.poll()) != null)
if (!rowIdsSeenAlready.contains(rowId))
newRowIds.add(rowId);
if (newRowIds.size() > 0) {
rowIdsSeenAlready.addAll(newRowIds);
Long[] rowIdArray = newRowIds.stream().toArray(l -> new Long[l]);
forEachOutputConsumerOfType(RowIdConsumer.class, c -> c.consume(rowIdArray));
logger.trace("Reported {} new matching rows", rowIdArray.length);
}
if (leftSourceIsEmpty.get() && rightSourceIsEmpty.get() && leftRowIds.isEmpty() && rightRowIds.isEmpty()) {
forEachOutputConsumerOfType(GenericConsumer.class, c -> c.sourceIsDone());
doneProcessing();
}
}
@Override
protected void validateOutputConsumer(GenericConsumer consumer) throws IllegalArgumentException {
if (!(consumer instanceof DoneConsumer) && !(consumer instanceof RowIdConsumer))
throw new IllegalArgumentException("Only RowIdConsumer 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(RowIdConsumer.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 RowIdConsumers are supported, but " + type.getSimpleName() + " requested.");
}
}