/**
* 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.ais.model.TableName;
import com.foundationdb.qp.row.HKey;
import com.foundationdb.qp.row.Row;
import com.foundationdb.server.api.dml.ColumnSelector;
import com.foundationdb.server.explain.*;
import com.foundationdb.util.ArgumentValidation;
import com.foundationdb.util.tap.InOutTap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
<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 static abstract 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;
}
}