/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program 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 com.foundationdb.qp.operator; import com.foundationdb.qp.row.Row; import com.foundationdb.server.api.dml.ColumnSelector; import java.util.ArrayDeque; import java.util.Queue; /** An {@link OperatorCursor} that opens a single {@link BindingsAwareCursor} * for each {@link QueryBindings} with lookahead. */ public abstract class LookaheadLeafCursor<C extends BindingsAwareCursor> extends OperatorCursor { // Cursor interface @Override public void open() { super.open(); if (currentCursor != null) { currentCursor.open(); } else if (pendingCursor != null) { currentCursor = pendingCursor; pendingCursor = null; } else { // At the very beginning, the pipeline isn't started. currentCursor = openACursor(currentBindings, false); } while (!cursorPool.isEmpty() && !bindingsExhausted) { QueryBindings bindings = bindingsCursor.nextBindings(); if (bindings == null) { bindingsExhausted = true; break; } C cursor = null; if (bindings.getDepth() == currentBindings.getDepth()) { cursor = openACursor(bindings, true); } pendingBindings.add(new BindingsAndCursor<C>(bindings, cursor)); } } @Override public Row next() { if (CURSOR_LIFECYCLE_ENABLED) { CursorLifecycle.checkIdleOrActive(this); } checkQueryCancelation(); Row row = currentCursor.next(); if (row == null) { currentCursor.setIdle(); } return row; } @Override public void jump(Row row, ColumnSelector columnSelector) { if (CURSOR_LIFECYCLE_ENABLED) { CursorLifecycle.checkIdleOrActive(this); } currentCursor.jump(row, columnSelector); state = CursorLifecycle.CursorState.ACTIVE; } @Override public void close() { try { resetActiveCursors(); } finally { super.close(); } } @Override public boolean isIdle() { return currentCursor == null ? super.isIdle() : currentCursor.isIdle(); } @Override public boolean isActive() { return currentCursor == null ? super.isActive() : currentCursor.isActive(); } @Override public boolean isClosed() { return currentCursor == null ? super.isClosed() : currentCursor.isClosed(); } @Override public void openBindings() { recyclePending(); bindingsCursor.openBindings(); bindingsExhausted = false; currentCursor = pendingCursor = null; } @Override public QueryBindings nextBindings() { CursorLifecycle.checkClosed(this); resetActiveCursors(); BindingsAndCursor<C> bandc = pendingBindings.poll(); if (bandc != null) { currentBindings = bandc.bindings; pendingCursor = bandc.cursor; } else { currentBindings = bindingsCursor.nextBindings(); if (currentBindings == null) { bindingsExhausted = true; } } return currentBindings; } @Override public void closeBindings() { bindingsCursor.closeBindings(); recyclePending(); } @Override public void cancelBindings(QueryBindings bindings) { CursorLifecycle.checkClosed(this); if ((currentBindings != null) && currentBindings.isAncestor(bindings)) { resetActiveCursors(); currentBindings = null; } while (true) { BindingsAndCursor<C> bandc = pendingBindings.peek(); if (bandc == null) break; if (!bandc.bindings.isAncestor(bindings)) break; bandc = pendingBindings.remove(); if (bandc.cursor != null) { try { bandc.cursor.close(); cursorPool.add(bandc.cursor); } finally { bandc.cursor = null; } } } bindingsCursor.cancelBindings(bindings); } // LookaheadLeafCursor interface LookaheadLeafCursor(QueryContext context, QueryBindingsCursor bindingsCursor, StoreAdapter adapter, int quantum) { super(context); this.bindingsCursor = bindingsCursor; this.pendingBindings = new ArrayDeque<>(quantum+1); this.cursorPool = new ArrayDeque<>(quantum); for (int i = 0; i < quantum; i++) { C cursor = newCursor(context, adapter); cursorPool.add(cursor); } } // Implemented by subclass protected abstract C newCursor(QueryContext context, StoreAdapter adapter); // Inner classes protected static final class BindingsAndCursor<C extends BindingsAwareCursor> { QueryBindings bindings; C cursor; /** * @param cursor An Open cursor */ BindingsAndCursor(QueryBindings bindings, C cursor) { this.bindings = bindings; this.cursor = cursor; } } // For use by this class protected void resetActiveCursors() { if (currentCursor != null) { try { currentCursor.close(); cursorPool.add(currentCursor); } finally { currentCursor = null; } } if (pendingCursor != null) { try { pendingCursor.close(); cursorPool.add(pendingCursor); } finally { pendingCursor = null; } } } protected void recyclePending() { while (true) { BindingsAndCursor<C> bandc = pendingBindings.poll(); if (bandc == null) break; if (bandc.cursor != null) { try { bandc.cursor.close(); cursorPool.add(bandc.cursor); } finally { bandc.cursor = null; } } } } protected C openACursor(QueryBindings bindings, boolean lookahead) { C cursor = cursorPool.remove(); cursor.rebind(bindings); cursor.open(); return cursor; } // Object state protected final QueryBindingsCursor bindingsCursor; protected final Queue<BindingsAndCursor<C>> pendingBindings; protected final Queue<C> cursorPool; protected QueryBindings currentBindings; protected C pendingCursor, currentCursor; protected boolean bindingsExhausted;// destroyed; }