/* * 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.facebook.presto.operator; import com.facebook.presto.operator.LookupSource.OuterPositionIterator; import com.facebook.presto.spi.Page; import com.facebook.presto.spi.PageBuilder; import com.facebook.presto.spi.type.Type; import com.facebook.presto.sql.planner.plan.PlanNodeId; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; import java.util.List; import static com.google.common.base.Preconditions.checkState; import static io.airlift.concurrent.MoreFutures.tryGetFutureValue; import static java.util.Objects.requireNonNull; public class LookupOuterOperator implements Operator { public static class LookupOuterOperatorFactory implements OperatorFactory { private enum State { NOT_CREATED, CREATED, CLOSED } private final int operatorId; private final PlanNodeId planNodeId; private final ListenableFuture<OuterPositionIterator> outerPositionsFuture; private final List<Type> types; private final List<Type> probeOutputTypes; private final List<Type> buildOutputTypes; private final Runnable onOperatorClose; private State state = State.NOT_CREATED; public LookupOuterOperatorFactory( int operatorId, PlanNodeId planNodeId, ListenableFuture<OuterPositionIterator> outerPositionsFuture, List<Type> probeOutputTypes, List<Type> buildOutputTypes, Runnable onOperatorClose) { this.operatorId = operatorId; this.planNodeId = requireNonNull(planNodeId, "planNodeId is null"); this.outerPositionsFuture = requireNonNull(outerPositionsFuture, "outerPositionsFuture is null"); this.probeOutputTypes = ImmutableList.copyOf(requireNonNull(probeOutputTypes, "probeOutputTypes is null")); this.buildOutputTypes = ImmutableList.copyOf(requireNonNull(buildOutputTypes, "buildOutputTypes is null")); this.onOperatorClose = requireNonNull(onOperatorClose, "referenceCount is null"); this.types = ImmutableList.<Type>builder() .addAll(probeOutputTypes) .addAll(buildOutputTypes) .build(); } public int getOperatorId() { return operatorId; } @Override public List<Type> getTypes() { return types; } @Override public Operator createOperator(DriverContext driverContext) { checkState(state == State.NOT_CREATED, "Only one outer operator can be created"); state = State.CREATED; OperatorContext operatorContext = driverContext.addOperatorContext(operatorId, planNodeId, LookupOuterOperator.class.getSimpleName()); return new LookupOuterOperator(operatorContext, outerPositionsFuture, probeOutputTypes, buildOutputTypes, onOperatorClose); } @Override public void close() { if (state == State.CLOSED) { return; } state = State.CLOSED; onOperatorClose.run(); } @Override public OperatorFactory duplicate() { throw new UnsupportedOperationException("Source operator factories can not be duplicated"); } } private final OperatorContext operatorContext; private final ListenableFuture<OuterPositionIterator> outerPositionsFuture; private final List<Type> types; private final List<Type> probeOutputTypes; private final Runnable onClose; private final PageBuilder pageBuilder; private OuterPositionIterator outerPositions; private boolean closed; public LookupOuterOperator( OperatorContext operatorContext, ListenableFuture<OuterPositionIterator> outerPositionsFuture, List<Type> probeOutputTypes, List<Type> buildOutputTypes, Runnable onClose) { this.operatorContext = requireNonNull(operatorContext, "operatorContext is null"); this.outerPositionsFuture = requireNonNull(outerPositionsFuture, "outerPositionsFuture is null"); this.types = ImmutableList.<Type>builder() .addAll(requireNonNull(probeOutputTypes, "probeOutputTypes is null")) .addAll(requireNonNull(buildOutputTypes, "buildOutputTypes is null")) .build(); this.probeOutputTypes = ImmutableList.copyOf(probeOutputTypes); this.pageBuilder = new PageBuilder(types); this.onClose = requireNonNull(onClose, "onClose is null"); } @Override public OperatorContext getOperatorContext() { return operatorContext; } @Override public List<Type> getTypes() { return types; } @Override public ListenableFuture<?> isBlocked() { return outerPositionsFuture; } @Override public void finish() { // this is a source operator, so we can just terminate the output now close(); } @Override public boolean isFinished() { return closed; } @Override public boolean needsInput() { return false; } @Override public void addInput(Page page) { throw new UnsupportedOperationException(); } @Override public Page getOutput() { if (outerPositions == null) { outerPositions = tryGetFutureValue(outerPositionsFuture).orElse(null); if (outerPositions == null) { return null; } } boolean outputPositionsFinished = false; while (!pageBuilder.isFull()) { // write build columns outputPositionsFinished = !outerPositions.appendToNext(pageBuilder, probeOutputTypes.size()); if (outputPositionsFinished) { break; } pageBuilder.declarePosition(); // write nulls into probe columns // todo use RLE blocks for (int probeChannel = 0; probeChannel < probeOutputTypes.size(); probeChannel++) { pageBuilder.getBlockBuilder(probeChannel).appendNull(); } } // only flush full pages unless we are done Page page = null; if (pageBuilder.isFull() || (outputPositionsFinished && !pageBuilder.isEmpty())) { page = pageBuilder.build(); pageBuilder.reset(); } if (outputPositionsFinished) { close(); } return page; } @Override public void close() { if (closed) { return; } closed = true; pageBuilder.reset(); onClose.run(); } }