/**
* 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.ais.model.Group;
import com.foundationdb.ais.model.Table;
import com.foundationdb.qp.row.HKey;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.rowtype.HKeyRowType;
import com.foundationdb.qp.rowtype.IndexRowType;
import com.foundationdb.qp.rowtype.RowType;
import com.foundationdb.qp.rowtype.Schema;
import com.foundationdb.qp.rowtype.TableRowType;
import com.foundationdb.server.explain.*;
import com.foundationdb.server.explain.std.LookUpOperatorExplainer;
import com.foundationdb.util.ArgumentValidation;
import com.foundationdb.util.tap.InOutTap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
<h1>Overview</h1>
AncestorLookup_Nested locates ancestors of both group rows and index rows. Ancestors are located
using the hkey in a row of the current query's QueryContext.
One expected usage is to locate the group row corresponding to an
index row. For example, an index on customer.name yields index rows
which AncestorLookup_Nested can then use to locate customer
rows. (The ancestor relationship is reflexive, e.g. customer is
considered to be an ancestor of customer.)
Another expected usage is to locate ancestors higher in the group. For
example, given either an item row or an item index row,
AncestorLookup_Nested can be used to find the corresponding order and
customer.
AncestorLookup_Nested always locates 0-1 row per ancestor type.
<h1>Arguments</h1>
<ul>
<li><b>GroupTable groupTable:</b> The group table containing the
ancestors of interest.
<li><b>RowType rowType:</b> Ancestors will be located for input rows
of this type.
<li><b>List<RowType> ancestorTypes:</b> Ancestor types to be located.
<li><b>int inputBindingPosition:</b> Indicates input row's position in the query context. The hkey
of this row will be used to locate ancestors.
<li><b>int lookaheadQuantum:</b> Number of cursors to try to keep open by looking
ahead in bindings stream.
</ul>
rowType may be an index row type or a group row type. For a group row
type, rowType must not be one of the ancestorTypes. For an index row
type, rowType may be one of the ancestorTypes.
The groupTable, rowType, and all ancestorTypes must belong to the same
group.
Each ancestorType must be an ancestor of the rowType (or, if rowType
is an index type, then an ancestor of the index's table's type).
<h1>Behavior</h1>
When this operator's cursor is opened, the row at position inputBindingPosition in the
query context is accessed. The hkey from this row is obtained. For each ancestor type, the
hkey is shortened if necessary, and the groupTable is then searched for
a record with that exact hkey. All the retrieved records are written
to the output stream in hkey order (ancestors before descendents), as
is the input row if keepInput is true.
<h1>Output</h1>
Nothing else to say.
<h1>Assumptions</h1>
None.
<h1>Performance</h1>
For each input row, AncestorLookup_Nested does one random access for
each ancestor type.
<h1>Memory Requirements</h1>
AncestorLookup_Nested stores in memory up to (ancestorTypes.size() +
1) rows.
*/
class AncestorLookup_Nested extends Operator
{
// Object interface
@Override
public String toString()
{
return String.format("%s(%s -> %s)", getClass().getSimpleName(), rowType, ancestors);
}
// Operator interface
@Override
public void findDerivedTypes(Set<RowType> derivedTypes)
{
}
@Override
protected Cursor cursor(QueryContext context, QueryBindingsCursor bindingsCursor)
{
if (lookaheadQuantum <= 1) {
return new Execution(context, bindingsCursor);
}
else {
// Convert to number of AncestorCursors, which hold multiple underlying
// cursors, rounding up.
int ncursors = ancestors.size();
int quantum = (lookaheadQuantum + ncursors - 1) / ncursors;
return new LookaheadExecution(context, bindingsCursor,
context.getStore(ancestors.get(0)),
quantum);
}
}
@Override
public String describePlan()
{
return toString();
}
// AncestorLookup_Nested interface
public AncestorLookup_Nested(Group group,
RowType rowType,
Collection<TableRowType> ancestorTypes,
int inputBindingPosition,
int lookaheadQuantum)
{
validateArguments(group, rowType, ancestorTypes, inputBindingPosition);
this.group = group;
this.rowType = rowType;
this.inputBindingPosition = inputBindingPosition;
this.lookaheadQuantum = lookaheadQuantum;
// Sort ancestor types by depth
this.ancestors = new ArrayList<>(ancestorTypes.size());
for (TableRowType ancestorType : ancestorTypes) {
this.ancestors.add(ancestorType.table());
}
if (this.ancestors.size() > 1) {
Collections.sort(this.ancestors,
new Comparator<Table>()
{
@Override
public int compare(Table x, Table y)
{
return x.getDepth() - y.getDepth();
}
});
}
}
// For use by this class
private void validateArguments(Group group,
RowType rowType,
Collection<? extends RowType> ancestorTypes,
int inputBindingPosition)
{
ArgumentValidation.notNull("group", group);
ArgumentValidation.notNull("rowType", rowType);
ArgumentValidation.notNull("ancestorTypes", ancestorTypes);
ArgumentValidation.notEmpty("ancestorTypes", ancestorTypes);
ArgumentValidation.isTrue("inputBindingPosition >= 0", inputBindingPosition >= 0);
if (rowType instanceof IndexRowType) {
// Keeping index rows not supported
RowType tableRowType = ((IndexRowType) rowType).tableType();
// Each ancestorType must be an ancestor of rowType. ancestorType = tableRowType is OK only if the input
// is from an index. I.e., this operator can be used for an index lookup.
for (RowType ancestorType : ancestorTypes) {
ArgumentValidation.isTrue("ancestorType.ancestorOf(tableRowType)",
ancestorType.ancestorOf(tableRowType));
ArgumentValidation.isTrue("ancestorType.table().getGroup() == tableRowType.table().getGroup()",
ancestorType.table().getGroup() == tableRowType.table().getGroup());
}
} else if (rowType instanceof TableRowType) {
// Each ancestorType must be an ancestor of rowType. ancestorType = tableRowType is OK only if the input
// is from an index. I.e., this operator can be used for an index lookup.
for (RowType ancestorType : ancestorTypes) {
ArgumentValidation.isTrue("ancestorType != tableRowType",
ancestorType != rowType);
ArgumentValidation.isTrue("ancestorType.ancestorOf(tableRowType)",
ancestorType.ancestorOf(rowType));
ArgumentValidation.isTrue("ancestorType.table().getGroup() == tableRowType.table().getGroup()",
ancestorType.table().getGroup() == rowType.table().getGroup());
}
} else if (rowType instanceof HKeyRowType) {
} else {
ArgumentValidation.isTrue("invalid rowType", false);
}
}
// Class state
private static final Logger LOG = LoggerFactory.getLogger(AncestorLookup_Nested.class);
private static final InOutTap TAP_OPEN = OPERATOR_TAP.createSubsidiaryTap("operator: AncestorLookup_Nested open");
private static final InOutTap TAP_NEXT = OPERATOR_TAP.createSubsidiaryTap("operator: AncestorLookup_Nested next");
// Object state
private final Group group;
private final RowType rowType;
private final List<Table> ancestors;
private final int inputBindingPosition;
private final int lookaheadQuantum;
@Override
public CompoundExplainer getExplainer(ExplainContext context)
{
Attributes atts = new Attributes();
atts.put(Label.BINDING_POSITION, PrimitiveExplainer.getInstance(inputBindingPosition));
for (Table table : ancestors) {
atts.put(Label.OUTPUT_TYPE, ((Schema)rowType.schema()).tableRowType(table).getExplainer(context));
}
atts.put(Label.PIPELINE, PrimitiveExplainer.getInstance(lookaheadQuantum));
return new LookUpOperatorExplainer(getName(), atts, rowType, false, null, context);
}
// Inner classes
private class Execution extends LeafCursor
{
// Cursor interface
@Override
public void open()
{
TAP_OPEN.in();
try {
super.open();
Row rowFromBindings = bindings.getRow(inputBindingPosition);
if (LOG_EXECUTION) {
LOG.debug("AncestorLookup_Nested: open using {}", rowFromBindings);
}
findAncestors(rowFromBindings);
} 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 = pending.poll();
if (LOG_EXECUTION) {
LOG.debug("AncestorLookup: {}", row);
}
if (row == null) {
setIdle();
}
return row;
} finally {
if (TAP_NEXT_ENABLED) {
TAP_NEXT.out();
}
}
}
@Override
public void close()
{
super.close();
pending.clear();
assert ancestorCursor.isClosed() : "Failed to close ancestorCursor";
}
// Execution interface
Execution(QueryContext context, QueryBindingsCursor bindingsCursor)
{
super(context, bindingsCursor);
this.pending = new ArrayDeque<>(ancestors.size() + 1);
this.ancestorCursor = adapter().newGroupCursor(group);
}
// For use by this class
private void findAncestors(Row row)
{
assert pending.isEmpty();
for (int i = 0; i < ancestors.size(); i++) {
Row ancestorRow = readAncestorRow(row.ancestorHKey(ancestors.get(i)));
if (ancestorRow != null) {
pending.add(ancestorRow);
}
}
}
private Row readAncestorRow(HKey hKey)
{
Row row;
ancestorCursor.rebind(hKey, false);
ancestorCursor.open();
try {
row = ancestorCursor.next();
// Retrieved row might not actually be what we were looking for -- not all ancestors are present,
// (there are orphan rows).
if (row != null && !hKey.equals(row.hKey())) {
row = null;
}
} finally {
ancestorCursor.close();
}
return row;
}
// Object state
private final GroupCursor ancestorCursor;
private final Queue<Row> pending;
}
private class AncestorCursor extends RowCursorImpl implements BindingsAwareCursor
{
// BindingsAwareCursor interface
@Override
public void open() {
super.open();
Row rowFromBindings = bindings.getRow(inputBindingPosition);
assert rowFromBindings.rowType() == rowType : rowFromBindings;
try {
for (int i = 0; i < hKeys.length; i++) {
hKeys[i] = rowFromBindings.ancestorHKey(ancestors.get(i));
cursors[i].rebind(hKeys[i], false);
cursors[i].open();
}
} catch (Exception e) {
try {
for (GroupCursor c : cursors) {
try {
if (!c.isClosed()) {
c.close();
}
} catch (Exception innerE) {
LOG.warn("Failed to close cursor", e);
}
}
} catch (Exception innerE2) {
LOG.warn("Failed to close nested cursors", e);
} finally {
throw e;
}
}
cursorIndex = 0;
}
@Override
public Row next() {
Row row = null;
while ((row == null) && (cursorIndex < cursors.length)) {
row = cursors[cursorIndex].next();
cursors[cursorIndex].setIdle();
if (row != null && !hKeys[cursorIndex].equals(row.hKey())) {
row = null;
}
cursorIndex++;
}
return row;
}
@Override
public void close() {
try {
for (GroupCursor cursor : cursors) {
cursor.close();
}
} finally {
super.close();
}
}
@Override
public boolean isIdle() {
return ((cursorIndex >= cursors.length) || cursors[cursorIndex].isIdle());
}
@Override
public boolean isActive() {
return ((cursors != null ) && (cursorIndex < cursors.length) && cursors[cursorIndex].isActive());
}
@Override
public void rebind(QueryBindings bindings) {
this.bindings = bindings;
}
// AncestorCursor interface
public AncestorCursor(StoreAdapter adapter) {
hKeys = new HKey[ancestors.size()];
cursors = new GroupCursor[hKeys.length];
for (int i = 0; i < cursors.length; i++) {
cursors[i] = adapter.newGroupCursor(group);
}
}
// Object state
private final HKey[] hKeys;
private final GroupCursor[] cursors;
private QueryBindings bindings;
private int cursorIndex;
}
private class LookaheadExecution extends LookaheadLeafCursor<AncestorCursor>
{
// Cursor interface
@Override
public void open() {
TAP_OPEN.in();
try {
super.open();
} finally {
TAP_OPEN.out();
}
}
@Override
public Row next() {
if (TAP_NEXT_ENABLED) {
TAP_NEXT.in();
}
try {
Row row = super.next();
if (LOG_EXECUTION) {
LOG.debug("AncestorLookup_Nested: yield {}", row);
}
return row;
} finally {
if (TAP_NEXT_ENABLED) {
TAP_NEXT.out();
}
}
}
// LookaheadLeafCursor interface
@Override
protected AncestorCursor newCursor(QueryContext context, StoreAdapter adapter) {
return new AncestorCursor(adapter);
}
@Override
protected AncestorCursor openACursor(QueryBindings bindings, boolean lookahead) {
if (LOG_EXECUTION) {
LOG.debug("AncestorLookup_Nested: open{} using {}", lookahead ? " lookahead" : "", bindings.getRow(inputBindingPosition));
}
return super.openACursor(bindings, lookahead);
}
// LookaheadExecution interface
LookaheadExecution(QueryContext context, QueryBindingsCursor bindingsCursor,
StoreAdapter adapter, int quantum) {
super(context, bindingsCursor, adapter, quantum);
}
}
}