/** * * * <h1>Overview</h1> * * GroupScan_Default scans a group in hkey order. * * <h1>Arguments</h1> * * <ul> * <li><b>GroupTable groupTable:</b> The group table to be scanned. * <li><b>Limit limit (DEPRECATED):</b> A limit on the number of rows to be returned. The limit is * specific to one Table. Deprecated because the result is not well-defined. In the case of a * branching group, a limit on one sibling has impliciations on the return of rows of other * siblings. * <li>IndexKeyRange indexKeyRange (DEPRECATED):</b> Specifies an index restriction for * hkey-equivalent indexes. Deprecated because hkey-equivalent indexes were used * automatically, sometimes reducing performance. Need to revisit the concept. * <ul> * <h1>Behavior</h1> * The rows of a group table are returned in hkey order. * <h1>Output</h1> * Nothing else to say. * <h1>Assumptions</h1> * None. * <h1>Performance</h1> * GroupScan_Default does a complete scan of a group table, relying on nothing but * sequential access. * <h1>Memory Requirements</h1> * None. */ class GroupScan_Default extends Operator { // Object interface @Override public String toString() { return getClass().getSimpleName() + '(' + cursorCreator + ')'; } // Operator interface @Override protected Cursor cursor(QueryContext context, QueryBindingsCursor bindingsCursor) { return new Execution(context, bindingsCursor, cursorCreator); } // GroupScan_Default interface public GroupScan_Default(GroupCursorCreator cursorCreator) { ArgumentValidation.notNull("groupTable", cursorCreator); this.cursorCreator = cursorCreator; } // Class state private static final InOutTap TAP_OPEN = OPERATOR_TAP.createSubsidiaryTap("operator: GroupScan_Default open"); private static final InOutTap TAP_NEXT = OPERATOR_TAP.createSubsidiaryTap("operator: GroupScan_Default next"); private static final Logger LOG = LoggerFactory.getLogger(GroupScan_Default.class); // Object state private final GroupCursorCreator cursorCreator; @Override public CompoundExplainer getExplainer(ExplainContext context) { Attributes att = new Attributes(); att.put(Label.NAME, PrimitiveExplainer.getInstance(getName())); att.put(Label.SCAN_OPTION, PrimitiveExplainer.getInstance(cursorCreator.describeRange())); TableName rootName = cursorCreator.group().getRoot().getName(); att.put(Label.TABLE_SCHEMA, PrimitiveExplainer.getInstance(rootName.getSchemaName())); att.put(Label.TABLE_NAME, PrimitiveExplainer.getInstance(rootName.getTableName())); return new CompoundExplainer(Type.SCAN_OPERATOR, att); } // Inner classes private static class Execution extends LeafCursor implements Rebindable { // Cursor interface @Override public void open() { TAP_OPEN.in(); try { super.open(); cursor.open(); } finally { TAP_OPEN.out(); } } @Override public Row next() { if (TAP_NEXT_ENABLED) { TAP_NEXT.in(); } try { checkQueryCancelation(); Row row; if ((row = cursor.next()) == null) { setIdle(); } if (LOG_EXECUTION) { LOG.debug("GroupScan_Default: yield {}", row); } return row; } finally { if (TAP_NEXT_ENABLED) { TAP_NEXT.out(); } } } @Override public void close() { try { cursor.close(); } finally { super.close(); } } @Override public boolean isIdle() { return cursor.isIdle(); } @Override public boolean isActive() { return cursor.isActive(); } @Override public QueryBindings nextBindings() { QueryBindings bindings = super.nextBindings(); if (cursor instanceof BindingsAwareCursor) ((BindingsAwareCursor) cursor).rebind(bindings); return bindings; } @Override public void rebind(HKey hKey, boolean deep) { if (!canRebind) { throw new IllegalStateException("rebind not allowed for"); } cursor.rebind(hKey, deep); } // Execution interface Execution( QueryContext context, QueryBindingsCursor bindingsCursor, GroupCursorCreator cursorCreator) { super(context, bindingsCursor); this.cursor = cursorCreator.cursor(context); this.canRebind = (cursorCreator instanceof FullGroupCursorCreator); } // Object state private final GroupCursor cursor; private final boolean canRebind; } static interface GroupCursorCreator { GroupCursor cursor(QueryContext context); Group group(); String describeRange(); } private abstract static class AbstractGroupCursorCreator implements GroupCursorCreator { // GroupCursorCreator interface @Override public final Group group() { return targetGroup; } // for use by subclasses protected AbstractGroupCursorCreator(Group group) { this.targetGroup = group; } @Override public final String toString() { return describeRange() + " on " + targetGroup.getRoot().getName(); } // for overriding in subclasses private final Group targetGroup; } static class FullGroupCursorCreator extends AbstractGroupCursorCreator { // GroupCursorCreator interface @Override public GroupCursor cursor(QueryContext context) { return context.getStore(group().getRoot()).newGroupCursor(group()); } // FullGroupCursorCreator interface public FullGroupCursorCreator(Group group) { super(group); } // AbstractGroupCursorCreator interface @Override public String describeRange() { return "full scan"; } } static class PositionalGroupCursorCreator extends AbstractGroupCursorCreator { // GroupCursorCreator interface @Override public GroupCursor cursor(QueryContext context) { return new HKeyBoundCursor( context, context.getStore(group().getRoot()).newGroupCursor(group()), hKeyBindingPosition, deep, hKeyType, shortenUntil); } // PositionalGroupCursorCreator interface PositionalGroupCursorCreator( Group group, int hKeyBindingPosition, boolean deep, Table hKeyType, Table shortenUntil) { super(group); this.hKeyBindingPosition = hKeyBindingPosition; this.deep = deep; if ((shortenUntil == hKeyType) || shortenUntil.isDescendantOf(hKeyType)) { shortenUntil = null; hKeyType = null; } this.shortenUntil = shortenUntil; this.hKeyType = hKeyType; assert (hKeyType == null) == (shortenUntil == null) : hKeyType + " ~ " + shortenUntil; assert hKeyType == null || hKeyType.isDescendantOf(shortenUntil) : hKeyType + " is not a descendant of " + shortenUntil; } // AbstractGroupCursorCreator interface @Override public String describeRange() { return deep ? "deep hkey-bound scan" : "shallow hkey-bound scan"; } // object state private final int hKeyBindingPosition; private final boolean deep; private final Table shortenUntil; private final Table hKeyType; } private static class HKeyBoundCursor extends RowCursorImpl implements BindingsAwareCursor, GroupCursor { @Override public void open() { super.open(); HKey hKey = getHKeyFromBindings(); input.rebind(hKey, deep); input.open(); } @Override public Row next() { // If we've ever seen a row, just defer to input if (sawOne) { return input.next(); } Row result = input.next(); // If we saw a row, mark it as such and defer to input if (result != null) { sawOne = true; return result; } // Our search is at an end; return our answer if (atTable == null || atTable == stopSearchTable) { return null; } // Close the input, shorten our hkey, re-open and try again input.close(); assert atTable.getParentTable() != null : atTable; atTable = atTable.getParentTable(); HKey hkey = getHKeyFromBindings(); hkey.useSegments(atTable.getDepth() + 1); input.rebind(hkey, deep); input.open(); return next(); } @Override public void jump(Row row, ColumnSelector columnSelector) { input.jump(row, columnSelector); state = CursorLifecycle.CursorState.ACTIVE; } @Override public void close() { try { // input is closed before hopefully reopening in next() if (!input.isClosed()) { input.close(); } } finally { super.close(); } } @Override public void rebind(QueryBindings bindings) { this.bindings = bindings; } @Override public void rebind(HKey hKey, boolean deep) { throw new UnsupportedOperationException(); } HKeyBoundCursor( QueryContext context, GroupCursor input, int hKeyBindingPosition, boolean deep, Table hKeyType, Table shortenUntil) { this.context = context; this.input = input; this.hKeyBindingPosition = hKeyBindingPosition; this.deep = deep; this.atTable = hKeyType; this.stopSearchTable = shortenUntil; } private HKey getHKeyFromBindings() { return bindings.getHKey(hKeyBindingPosition); } private final QueryContext context; private final GroupCursor input; private final int hKeyBindingPosition; private final boolean deep; private Table atTable; private final Table stopSearchTable; private boolean sawOne = false; private QueryBindings bindings; } }
/** * * * <h1>Overview</h1> * * Provides a very simple, count-based limit. * * <h1>Arguments</h1> * * <ul> * <li><b>PhysicalOperator inputOperator:</b> The input operator, whose cursor will be limited * <li><b>int limit:</b> the number of rows to limit to; cannot be negative * </ul> * * <h1>Behavior</h1> * * Limit_Default's cursor returns at most <i>limit</i> rows from the input operator's cursor. When * the limit is reached, the input cursor is automatically closed, and all subsequent calls to * <i>next</i> will return <i>false</i>. * * <h1>Output</h1> * * Nothing else to say. * * <h1>Assumptions</h1> * * None. * * <h1>Performance</h1> * * Essentially free. * * <h1>Memory Requirements</h1> * * None. */ final class Limit_Default extends Operator { // Operator interface @Override protected Cursor cursor(QueryContext context, QueryBindingsCursor bindingsCursor) { return new Execution(context, inputOperator.cursor(context, bindingsCursor)); } // Plannable interface @Override public void findDerivedTypes(Set<RowType> derivedTypes) { inputOperator.findDerivedTypes(derivedTypes); } @Override public List<Operator> getInputOperators() { return Collections.singletonList(inputOperator); } @Override public String describePlan() { return super.describePlan(); } // Object interface @Override public String toString() { StringBuilder str = new StringBuilder(getClass().getSimpleName()); str.append("("); if (skip > 0) { str.append(String.format("skip=%d", skip)); } if ((limit >= 0) && (limit < Integer.MAX_VALUE)) { if (skip > 0) str.append(", "); str.append(String.format("limit=%d", limit)); } str.append(": "); str.append(inputOperator); str.append(")"); return str.toString(); } // Limit_Default interface Limit_Default(Operator inputOperator, int limit) { this(inputOperator, 0, false, limit, false); } Limit_Default( Operator inputOperator, int skip, boolean skipIsBinding, int limit, boolean limitIsBinding) { ArgumentValidation.isGTE("skip", skip, 0); ArgumentValidation.isGTE("limit", limit, 0); this.skip = skip; this.skipIsBinding = skipIsBinding; this.limit = limit; this.limitIsBinding = limitIsBinding; this.inputOperator = inputOperator; } public int skip() { return skip; } public boolean isSkipBinding() { return skipIsBinding; } public int limit() { return limit; } public boolean isLimitBinding() { return limitIsBinding; } // Class state private static final InOutTap TAP_OPEN = OPERATOR_TAP.createSubsidiaryTap("operator: Limit_Default open"); private static final InOutTap TAP_NEXT = OPERATOR_TAP.createSubsidiaryTap("operator: Limit_Default next"); private static final Logger LOG = LoggerFactory.getLogger(Limit_Default.class); // Object state private final int skip, limit; private final boolean skipIsBinding, limitIsBinding; private final Operator inputOperator; @Override public CompoundExplainer getExplainer(ExplainContext context) { Attributes atts = new Attributes(); atts.put(Label.NAME, PrimitiveExplainer.getInstance(getName())); atts.put(Label.LIMIT, PrimitiveExplainer.getInstance(limit)); atts.put(Label.INPUT_OPERATOR, inputOperator.getExplainer(context)); return new CompoundExplainer(Type.LIMIT_OPERATOR, atts); } // internal classes private class Execution extends ChainedCursor { // Cursor interface @Override public void open() { TAP_OPEN.in(); try { if (isSkipBinding()) { ValueSource value = bindings.getValue(skip()); if (!value.isNull()) this.skipLeft = value.getInt32(); } else { this.skipLeft = skip(); } if (skipLeft < 0) throw new NegativeLimitException("OFFSET", skipLeft); if (isLimitBinding()) { ValueSource value = bindings.getValue(limit()); if (value.isNull()) this.limitLeft = Integer.MAX_VALUE; else { TInstance type = MNumeric.INT.instance(true); TExecutionContext executionContext = new TExecutionContext(null, type, context); Value ivalue = new Value(MNumeric.INT.instance(true)); MNumeric.INT.fromObject(executionContext, value, ivalue); this.limitLeft = ivalue.getInt32(); } } else { this.limitLeft = limit(); } if (limitLeft < 0) throw new NegativeLimitException("LIMIT", limitLeft); super.open(); } finally { TAP_OPEN.out(); } } @Override public void close() { // Because the checks in open() may // prevent the cursor from being opened. if (!isClosed()) { super.close(); } } @Override public Row next() { if (TAP_NEXT_ENABLED) { TAP_NEXT.in(); } try { if (CURSOR_LIFECYCLE_ENABLED) { CursorLifecycle.checkIdleOrActive(this); } checkQueryCancelation(); Row row; while (skipLeft > 0) { if ((row = input.next()) == null) { skipLeft = 0; limitLeft = -1; setIdle(); if (LOG_EXECUTION) { LOG.debug("Limit_Default: skipLeft until complete yield null"); } return null; } skipLeft--; } if (limitLeft < 0) { setIdle(); if (LOG_EXECUTION) { LOG.debug("Limit_Default: limitLeft < 0, yield null"); } return null; } if (limitLeft == 0) { setIdle(); if (LOG_EXECUTION) { LOG.debug("Limit_Default: limitLeft == 0, yield null"); } return null; } if ((row = input.next()) == null) { limitLeft = -1; setIdle(); if (LOG_EXECUTION) { LOG.debug("Limit_Default: yield null"); } return null; } --limitLeft; if (LOG_EXECUTION) { LOG.debug("Limit_Default: yield {}", row); } return row; } finally { if (TAP_NEXT_ENABLED) { TAP_NEXT.out(); } } } // Execution interface Execution(QueryContext context, Cursor input) { super(context, input); } // object state private int skipLeft, limitLeft; } }
/** * * * <h1>Overview</h1> * * <p>Select_BloomFilter checks whether an input row, projected to a given set of expressions, is * present in a "filtering" set of rows. The implementation is optimized to avoid IO operations on * the filtering set of rows through the use of a bloom filter. * * <p> * * <h1>Arguments</h1> * * <p> * <li><b>Operator input:</b> Operator providing the input stream * <li><b>Operator onPositive:</b> Operator used to provide output rows * <li><b>List<BoundFieldExpression> fields:</b> expressions applied to the input row to obtain a * bloom filter hash key * <li><b>int bindingPosition:</b> Location in the query context of the bloom filter, which has been * loaded by the Using_BloomFilter operator. This bindingPosition is also used to hold a row * from the input stream that passes the filter and needs to be tested for existence using the * onPositive operator. * <li><b>boolean pipeline:</b> Whether to use bracketing cursors instead of rebinding. * <li><b>int depth:</b> Number of nested Maps, including this one. * * <p> * * <h1>Behavior</h1> * * <p>Each call of next operates as follows. A row is obtained from the input operator. The * expressions from fields are evaluated and the resulting values are used to probe the bloom * filter. If the filter returns false, this indicates that there is no matching row in the * filtering set of rows and null is returned. If the filter returns true, then the onPositive * operator is used to locate the matching row. If a row is located then the input row (not the * row from onPositive) is returned, otherwise null is returned. * * <p> * * <h1>Output</h1> * * <p>A subset of rows from the input stream. * * <p> * * <h1>Assumptions</h1> * * <p>None. * * <p> * * <h1>Performance</h1> * * <p>This operator should generate very little IO activity, although bloom filters are * probabilistic. * * <p> * * <h1>Memory Requirements</h1> * * <p>This operator relies on the bloom filter created by Using_BloomFilter. */ class Select_BloomFilter extends Operator { // Object interface @Override public String toString() { return getClass().getSimpleName(); } // Operator interface @Override public void findDerivedTypes(Set<RowType> derivedTypes) { input.findDerivedTypes(derivedTypes); onPositive.findDerivedTypes(derivedTypes); } @Override protected Cursor cursor(QueryContext context, QueryBindingsCursor bindingsCursor) { if (!pipeline) { return new Execution<>(context, bindingsCursor, tFields, newExpressionsAdapter); } else { assert (tFields != null); Cursor inputCursor = input.cursor(context, bindingsCursor); QueryBindingsCursor toBindings = new FilterBindingsCursor( context, inputCursor, bindingPosition, depth, tFields, newExpressionsAdapter); Cursor checkCursor = onPositive.cursor(context, toBindings); return new RecoverRowsCursor(context, checkCursor, bindingPosition, depth); } } @Override public List<Operator> getInputOperators() { return Arrays.asList(input, onPositive); } @Override public String describePlan() { return String.format("%s\n%s", describePlan(input), describePlan(onPositive)); } // Select_BloomFilter interface public Select_BloomFilter( Operator input, Operator onPositive, List<? extends TPreparedExpression> tFields, List<AkCollator> collators, int bindingPosition, boolean pipeline, int depth) { ArgumentValidation.notNull("input", input); ArgumentValidation.notNull("onPositive", onPositive); ArgumentValidation.notNull("Fields", tFields); int size = tFields.size(); ArgumentValidation.isGT("fields.size()", size, 0); ArgumentValidation.isGTE("bindingPosition", bindingPosition, 0); ArgumentValidation.isGT("depth", depth, 0); this.input = input; this.onPositive = onPositive; this.bindingPosition = bindingPosition; this.pipeline = pipeline; this.depth = depth; this.tFields = tFields; this.collators = collators; } // For use by this class private AkCollator collator(int f) { return collators == null ? null : collators.get(f); } // Class state private static final InOutTap TAP_OPEN = OPERATOR_TAP.createSubsidiaryTap("operator: Select_BloomFilter open"); private static final InOutTap TAP_NEXT = OPERATOR_TAP.createSubsidiaryTap("operator: Select_BloomFilter next"); private static final InOutTap TAP_CHECK = OPERATOR_TAP.createSubsidiaryTap("operator: Select_BloomFilter check"); private static final Logger LOG = LoggerFactory.getLogger(Select_BloomFilter.class); // Object state private final Operator input; private final Operator onPositive; private final int bindingPosition, depth; private final boolean pipeline; private final List<? extends TPreparedExpression> tFields; private final List<AkCollator> collators; @Override public CompoundExplainer getExplainer(ExplainContext context) { Attributes atts = new Attributes(); atts.put(Label.NAME, PrimitiveExplainer.getInstance(getName())); atts.put(Label.BINDING_POSITION, PrimitiveExplainer.getInstance(bindingPosition)); atts.put(Label.INPUT_OPERATOR, input.getExplainer(context)); atts.put(Label.INPUT_OPERATOR, onPositive.getExplainer(context)); for (TPreparedExpression field : tFields) { atts.put(Label.EXPRESSIONS, field.getExplainer(context)); } atts.put(Label.PIPELINE, PrimitiveExplainer.getInstance(pipeline)); return new CompoundExplainer(Type.BLOOM_FILTER, atts); } // Inner classes private interface ExpressionAdapter<EXPR, EVAL> { EVAL evaluate(EXPR expression, QueryContext contex); int hash(StoreAdapter adapter, EVAL evaluation, Row row, AkCollator collator); } private static ExpressionAdapter<TPreparedExpression, TEvaluatableExpression> newExpressionsAdapter = new ExpressionAdapter<TPreparedExpression, TEvaluatableExpression>() { @Override public TEvaluatableExpression evaluate( TPreparedExpression expression, QueryContext contex) { TEvaluatableExpression eval = expression.build(); eval.with(contex); return eval; } @Override public int hash( StoreAdapter adapter, TEvaluatableExpression evaluation, Row row, AkCollator collator) { evaluation.with(row); evaluation.evaluate(); return ValueSources.hash(evaluation.resultValue(), collator); } }; private class Execution<E> extends OperatorCursor { // Cursor interface @Override public void open() { TAP_OPEN.in(); try { CursorLifecycle.checkIdle(this); filter = bindings.getBloomFilter(bindingPosition); bindings.setBloomFilter(bindingPosition, null); inputCursor.open(); idle = false; } finally { TAP_OPEN.out(); } } @Override public Row next() { if (TAP_NEXT_ENABLED) { TAP_NEXT.in(); } try { if (CURSOR_LIFECYCLE_ENABLED) { CursorLifecycle.checkIdleOrActive(this); } Row row; do { row = inputCursor.next(); if (row == null) { close(); } else if (!filter.maybePresent(hashProjectedRow(row)) || !rowReallyHasMatch(row)) { row = null; } } while (!idle && row == null); if (LOG_EXECUTION) { LOG.debug("Select_BloomFilter: yield {}", row); } return row; } finally { if (TAP_NEXT_ENABLED) { TAP_NEXT.out(); } } } @Override public void close() { CursorLifecycle.checkIdleOrActive(this); if (!idle) { inputCursor.close(); idle = true; } } @Override public void destroy() { if (!destroyed) { close(); inputCursor.destroy(); onPositiveCursor.destroy(); filter = null; destroyed = true; } } @Override public boolean isIdle() { return !destroyed && idle; } @Override public boolean isActive() { return !destroyed && !idle; } @Override public boolean isDestroyed() { return destroyed; } @Override public void openBindings() { inputCursor.openBindings(); } @Override public QueryBindings nextBindings() { bindings = inputCursor.nextBindings(); return bindings; } @Override public void closeBindings() { inputCursor.closeBindings(); } @Override public void cancelBindings(QueryBindings bindings) { inputCursor.cancelBindings(bindings); idle = true; } // Execution interface <EXPR> Execution( QueryContext context, QueryBindingsCursor bindingsCursor, List<? extends EXPR> expressions, ExpressionAdapter<EXPR, E> adapter) { super(context); this.inputCursor = input.cursor(context, bindingsCursor); this.onPositiveBindingsCursor = new SingletonQueryBindingsCursor(null); this.onPositiveCursor = onPositive.cursor(context, onPositiveBindingsCursor); this.adapter = adapter; for (EXPR field : expressions) { E eval = adapter.evaluate(field, context); fieldEvals.add(eval); } } // For use by this class private int hashProjectedRow(Row row) { int hash = 0; for (int f = 0; f < fieldEvals.size(); f++) { E fieldEval = fieldEvals.get(f); hash = hash ^ adapter.hash(adapter(), fieldEval, row, collator(f)); } return hash; } private boolean rowReallyHasMatch(Row row) { // bindingPosition is used to hold onto a row for use during the evaluation of expressions // during onPositiveCursor.open(). This is somewhat sleazy, but the alternative is to // complicate the Select_BloomFilter API, requiring the caller to specify another binding // position. // It is safe to reuse the binding position in this way because the filter is extracted and // stored // in a field during open(), while the use of the binding position for use in the onPositive // lookup // occurs during next(). if (LOG_EXECUTION) { LOG.debug("Select_BloomFilter: candidate {}", row); } TAP_CHECK.in(); try { bindings.setRow(bindingPosition, row); onPositiveBindingsCursor.reset(bindings); onPositiveCursor.openTopLevel(); try { return onPositiveCursor.next() != null; } finally { onPositiveCursor.closeTopLevel(); bindings.setRow(bindingPosition, null); } } finally { TAP_CHECK.out(); } } // Object state private final Cursor inputCursor; private final Cursor onPositiveCursor; private final SingletonQueryBindingsCursor onPositiveBindingsCursor; private QueryBindings bindings; private BloomFilter filter; private final List<E> fieldEvals = new ArrayList<>(); private final ExpressionAdapter<?, E> adapter; private boolean idle = true; private boolean destroyed = false; } // Turn input rows that match the filter into bindings for the onPositive plan. private class FilterBindingsCursor extends Map_NestedLoops.RowToBindingsCursor { private final StoreAdapter storeAdapter; private final List<TEvaluatableExpression> fieldEvals = new ArrayList<>(); private final ExpressionAdapter<TPreparedExpression, TEvaluatableExpression> expressionAdapter; public FilterBindingsCursor( QueryContext context, Cursor input, int bindingPosition, int depth, List<? extends TPreparedExpression> expressions, ExpressionAdapter<TPreparedExpression, TEvaluatableExpression> expressionAdapter) { super(input, bindingPosition, depth); this.storeAdapter = context.getStore(); this.expressionAdapter = expressionAdapter; for (TPreparedExpression field : expressions) { TEvaluatableExpression eval = expressionAdapter.evaluate(field, context); fieldEvals.add(eval); } } @Override protected Row nextInputRow() { BloomFilter filter = baseBindings.getBloomFilter(bindingPosition); while (true) { Row row = input.next(); if (row == null) { return row; } if (filter.maybePresent(hashProjectedRow(row))) { if (ExecutionBase.LOG_EXECUTION) { LOG.debug("Select_BloomFilter: candidate {}", row); } return row; } } } private int hashProjectedRow(Row row) { int hash = 0; for (int f = 0; f < fieldEvals.size(); f++) { TEvaluatableExpression fieldEval = fieldEvals.get(f); hash = hash ^ expressionAdapter.hash(storeAdapter, fieldEval, row, collator(f)); } return hash; } } // If any context at our depth has a non-empty rowset from // onPositive, it passed, so let it through. private static class RecoverRowsCursor extends Map_NestedLoops.CollapseBindingsCursor { private final int bindingPosition; public RecoverRowsCursor(QueryContext context, Cursor input, int bindingPosition, int depth) { super(context, input, depth); this.bindingPosition = bindingPosition; } @Override public Row next() { if (TAP_NEXT_ENABLED) { TAP_NEXT.in(); } try { if (CURSOR_LIFECYCLE_ENABLED) { CursorLifecycle.checkIdleOrActive(this); } checkQueryCancelation(); Row row = null; while (true) { QueryBindings bindings = input.nextBindings(); if (bindings == null) { openBindings = null; break; } if (bindings.getDepth() < depth) { pendingBindings = bindings; openBindings = null; break; } assert (bindings.getDepth() == depth); input.open(); inputOpenBindings = bindings; row = input.next(); input.close(); inputOpenBindings = null; if (row != null) { row = bindings.getRow(bindingPosition); break; } } if (LOG_EXECUTION) { LOG.debug("Select_BloomFilter: yield {}", row); } return row; } finally { if (TAP_NEXT_ENABLED) { TAP_NEXT.out(); } } } } }