/**
* 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.qp.rowtype.IndexRowType;
import com.foundationdb.qp.rowtype.RowType;
import com.foundationdb.server.explain.*;
import com.foundationdb.server.types.aksql.aktypes.AkBool;
import com.foundationdb.server.types.texpressions.TEvaluatableExpression;
import com.foundationdb.server.types.texpressions.TPreparedExpression;
import com.foundationdb.util.ArgumentValidation;
import com.foundationdb.util.tap.InOutTap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
<h1>Overview</h1>
Select_HKeyOrdered passes on selected rows from the input stream to the output stream. A row is subject to elimination
if and only if it's type is a specified type (predicateType), or a descendent of this type.
<h1>Arguments</h1>
<li><b>Operator inputOperator:</b> Operator providing the input stream.
<li><b>RowType predicateRowType:</b> Type of row to which the selection predicate is applied.
<li><b>Expression predicate:</b> Selection predicate.
<h1>Behavior</h1>
The handling of a row depends on its RowType:
If the row's type matches predicateRowType: The predicate is evaluated. The row is written to the output stream
if and only if the predicate evaluates to true.
If the row's type is a descendent type of predicateRowType: The row is written to the output stream if and only if
the predicate evaluated to true for the ancestor of type predicateRowType. (E.g., if a Customer is rejected,
then all of its Orders and Items will be rejected too.)
All other rows are written to the output stream unconditionally.
<h1>Output</h1>
A subset of the rows from the input stream.
<h1>Assumptions</h1>
Input is hkey-ordered with respect to predicateRowType. E.g., in a COI schema, with prediateRowType = Order,
Orders and Items are assumed to be in hkey-order. The order of one Order relative to another is not significant, nor
is the order of Customers.
<h1>Performance</h1>
Project_Default does no IO. For each input row, the type is checked and each output field is computed.
<h1>Memory Requirements</h1>
One row of type predicateRowType.
*/
class Select_HKeyOrdered extends Operator
{
// Object interface
@Override
public String toString()
{
return String.format("%s(%s, %s)", getClass().getSimpleName(), predicateRowType,
pPredicate.toString());
}
// Operator interface
@Override
public void findDerivedTypes(Set<RowType> derivedTypes)
{
inputOperator.findDerivedTypes(derivedTypes);
}
@Override
protected Cursor cursor(QueryContext context, QueryBindingsCursor bindingsCursor)
{
return new Execution(context, inputOperator.cursor(context, bindingsCursor));
}
@Override
public List<Operator> getInputOperators()
{
List<Operator> result = new ArrayList<>(1);
result.add(inputOperator);
return result;
}
@Override
public String describePlan()
{
return describePlan(inputOperator);
}
// Select_HKeyOrdered interface
public Select_HKeyOrdered(Operator inputOperator, RowType predicateRowType, TPreparedExpression pPredicate)
{
ArgumentValidation.notNull("predicateRowType", predicateRowType);
this.inputOperator = inputOperator;
this.predicateRowType = predicateRowType;
this.groupScanInput = !(predicateRowType instanceof IndexRowType);
this.pPredicate = pPredicate;
ArgumentValidation.notNull("predicate", pPredicate);
if (pPredicate.resultType().typeClass() != AkBool.INSTANCE)
throw new IllegalArgumentException("predicate must return type " + AkBool.INSTANCE);
}
// Class state
private static final InOutTap TAP_OPEN = OPERATOR_TAP.createSubsidiaryTap("operator: Select_HKeyOrdered open");
private static final InOutTap TAP_NEXT = OPERATOR_TAP.createSubsidiaryTap("operator: Select_HKeyOrdered next");
private static final Logger LOG = LoggerFactory.getLogger(Select_HKeyOrdered.class);
// Object state
private final Operator inputOperator;
private final RowType predicateRowType;
private final boolean groupScanInput;
private final TPreparedExpression pPredicate;
@Override
public CompoundExplainer getExplainer(ExplainContext context)
{
Attributes att = new Attributes();
att.put(Label.NAME, PrimitiveExplainer.getInstance(getName()));
att.put(Label.INPUT_OPERATOR, inputOperator.getExplainer(context));
att.put(Label.PREDICATE, pPredicate.getExplainer(context));
return new CompoundExplainer(Type.SELECT_HKEY, att);
}
// Inner classes
private class Execution extends ChainedCursor
{
// Cursor interface
@Override
public void open()
{
TAP_OPEN.in();
try {
super.open();
pEvaluation.with(context);
pEvaluation.with(bindings);
} finally {
TAP_OPEN.out();
}
}
@Override
public Row next()
{
if (TAP_NEXT_ENABLED) {
TAP_NEXT.in();
}
try {
if (CURSOR_LIFECYCLE_ENABLED) {
CursorLifecycle.checkIdleOrActive(this);
}
checkQueryCancelation();
Row row = null;
Row inputRow = input.next();
while (row == null && inputRow != null) {
if (inputRow.rowType() == predicateRowType) {
pEvaluation.with(inputRow);
pEvaluation.evaluate();
if (pEvaluation.resultValue().getBoolean(false)) {
// New row of predicateRowType
if (groupScanInput) {
selectedRow = inputRow;
}
row = inputRow;
}
} else if (predicateRowType.ancestorOf(inputRow.rowType())) {
// Row's type is a descendent of predicateRowType.
if (selectedRow != null && selectedRow.ancestorOf(inputRow)) {
row = inputRow;
} else {
selectedRow = null;
}
} else {
row = inputRow;
}
if (row == null) {
inputRow = input.next();
}
}
if (LOG_EXECUTION) {
LOG.debug("Select_HKeyOrdered: yield {}", row);
}
return row;
} finally {
if (TAP_NEXT_ENABLED) {
TAP_NEXT.out();
}
}
}
@Override
public void close()
{
super.close();
selectedRow = null;
}
// Execution interface
Execution(QueryContext context, Cursor input)
{
super(context, input);
this.pEvaluation = pPredicate.build();
}
// Object state
private Row selectedRow; // The last input row with type = predicateRowType.
private final TEvaluatableExpression pEvaluation;
}
}