/**
* 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.Column;
import com.foundationdb.ais.model.Index;
import com.foundationdb.ais.model.IndexColumn;
import com.foundationdb.ais.model.Table;
import com.foundationdb.qp.expression.IndexKeyRange;
import com.foundationdb.qp.row.Row;
import com.foundationdb.qp.rowtype.IndexRowType;
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;
import java.util.List;
/**
<h1>Overview</h1>
IndexScan_Default scans an index to locate index records whose keys
are inside a given range.
<h1>Arguments</h1>
<ul>
<li><b>IndexRowType indexType:</b> The index's type.
<li><b>IndexKeyRange indexKeyRange:</b> Describes the range of keys
to be visited. The values specified by the indexKeyRange should
restrict one or more of the leading fields of the index. If null,
then the entire index will be scanned.
<li><b>Ordering ordering:</b> Indicates whether keys should be visited
in ascending order or descending order.
<li><b>IndexScanSelector scanSelector:</b> On a group index, specify which
tables must be present for OUTER JOIN semantics.
<li><b>int lookaheadQuantum:</b> Number of cursors to try to keep open by looking
ahead in bindings stream.
</ul>
<h1>Behavior</h1>
If reverse = false, then the index is probed using the low end of the
indexKeyRange. Index records are written to the output stream as long
as they fall inside the indexKeyRange. When the first record outside
the indexKeyRange is located, the scan is closed.
If reverse = true, the initial probe is with the high end of the
indexKeyRange, and records are visited in descending key order.
innerJoinUntilRowType is the table until which a group index is
treated with INNER JOIN semantics (inclusive). For instance, let's say
you had a COI schema with group index (customer.name,
order.date). The group table has the following rows:
<table>
<tr><td>Row</td></tr>
<tr><td>c(1, Bob)</td></tr>
<tr><tr>c(2, Joe)</td></tr>
<tr><tr>o(10, 2, 01-01-2001)</td></tr>
<tr><tr>o(11, 3, null)</td></tr>
</table>
This corresponds to the following rows in the group index, which has
LEFT JOIN semantics:
<table>
<tr><td>Key</td><td>Value</td><td>Notes</td></tr>
<tr><td>Bob, null, hkey(c1)</td><td>depth(c)</td><td>null o.date is due to there not being any child orders</td></tr>
<tr><td>Joe, null, hkey(o10)</td><td>depth(o)</td><td>null o.date is due to o(10) having a null o.date</td></tr>
<tr><td>Joe, 01-01-2001, hkey(o11)</td><td>depth(o)</td><td></td></tr>
</table>
If we're executing a query which has a LEFT JOIN between c and o, we
would pass tableRowType(CUSTOMER) as the innerJoinUntilRowType,
and get all of those rows. If we were executing a query plan which had
an INNER JOIN between c and o, we would pass tableRowType(ORDER)
and get only the second two rows (with depth(o) ).
Notes:
<ul>
<li>it's possible to specify INNER only partially up the branch. For
instance, if our group index had been on (customer.name, order.date,
item.sku), passing tableRowType(ORDER) would be analogous to SQL
FROM c INNER JOIN o LEFT JOIN i.
<li>specifying the TableRowType corresponding to the group
index's rootmost table means the index will be scanned only with
LEFT JOIN semantics; all entries (within the key range) will be
returned.
<li>specifying a TableRowType not within the group index's
branch segment (i.e: rootward of the GI's rootmost table; or
leaftward of the GI's leafmost table; or in another branch or group)
will result in an IllegalArgumentException during the
PhysicalOperator's construction
</ul>
<h1>Output</h1>
Output contains index rows. Each row has an hkey of the index's table.
<h1>Assumptions</h1>
None.
<h1>Performance</h1>
IndexScan_Default does one random access followed by as many sequential accesses as are required to cover the indexKeyRange.
<h1>Memory Requirements</h1>
None.
*/
class IndexScan_Default extends Operator
{
// Object interface
@Override
public String toString()
{
StringBuilder str = new StringBuilder(getClass().getSimpleName());
str.append("(").append(index);
str.append(" ").append(indexKeyRange);
if (!ordering.allAscending()) {
str.append(" ").append(ordering);
}
str.append(scanSelector.describe());
str.append(")");
return str.toString();
}
// Operator interface
@Override
protected Cursor cursor(QueryContext context, QueryBindingsCursor bindingsCursor)
{
if (lookaheadQuantum <= 1) {
return new Execution(context, bindingsCursor);
}
else {
return new LookaheadExecution(context, bindingsCursor,
context.getStore(index.rootMostTable()),
lookaheadQuantum);
}
}
// IndexScan_Default interface
public IndexScan_Default(IndexRowType indexType,
IndexKeyRange indexKeyRange,
API.Ordering ordering,
IndexScanSelector scanSelector,
int lookaheadQuantum)
{
ArgumentValidation.notNull("indexType", indexType);
this.indexType = indexType;
this.index = indexType.index();
this.ordering = ordering;
this.indexKeyRange = indexKeyRange;
this.scanSelector = scanSelector;
this.lookaheadQuantum = lookaheadQuantum;
}
// Class state
private static final InOutTap TAP_OPEN = OPERATOR_TAP.createSubsidiaryTap("operator: IndexScan_Default open");
private static final InOutTap TAP_NEXT = OPERATOR_TAP.createSubsidiaryTap("operator: IndexScan_Default next");
private static final Logger LOG = LoggerFactory.getLogger(IndexScan_Default.class);
// Object state
private final IndexRowType indexType;
private final Index index;
private final API.Ordering ordering;
private final IndexKeyRange indexKeyRange;
private final IndexScanSelector scanSelector;
private final int lookaheadQuantum;
@Override
public CompoundExplainer getExplainer(ExplainContext context)
{
Attributes atts = new Attributes();
atts.put(Label.NAME, PrimitiveExplainer.getInstance(getName()));
atts.put(Label.INDEX, indexType.getExplainer(context));
for (IndexColumn indexColumn : index.getAllColumns()) {
Column column = indexColumn.getColumn();
atts.put(Label.TABLE_SCHEMA, PrimitiveExplainer.getInstance(column.getTable().getName().getSchemaName()));
atts.put(Label.TABLE_NAME, PrimitiveExplainer.getInstance(column.getTable().getName().getTableName()));
atts.put(Label.COLUMN_NAME, PrimitiveExplainer.getInstance(column.getName()));
}
if (index.isGroupIndex())
atts.put(Label.INDEX_KIND, PrimitiveExplainer.getInstance("GROUP"));
if (!indexKeyRange.unbounded()) {
List<Explainer> loExprs = null, hiExprs = null;
if (indexKeyRange.lo() != null) {
loExprs = indexKeyRange.lo().getExplainer(context).get().get(Label.EXPRESSIONS);
}
if (indexKeyRange.hi() != null) {
hiExprs = indexKeyRange.hi().getExplainer(context).get().get(Label.EXPRESSIONS);
}
if (indexKeyRange.spatialCoordsIndex() ||
indexKeyRange.spatialObjectIndex()) {
atts.put(Label.INDEX_SPATIAL_DIMENSIONS, PrimitiveExplainer.getInstance(index.spatialColumns()));
if (index.isGroupIndex()) {
atts.remove(Label.INDEX_KIND);
atts.put(Label.INDEX_KIND, PrimitiveExplainer.getInstance("SPATIAL GROUP"));
} else {
atts.put(Label.INDEX_KIND, PrimitiveExplainer.getInstance("SPATIAL"));
}
int nequals = indexKeyRange.boundColumns() - index.spatialColumns();
for (int i = 0; i < nequals; i++) {
atts.put(Label.EQUAL_COMPARAND, loExprs.get(i));
}
int nspatial = indexKeyRange.spatialCoordsIndex() ? 2 : 1;
loExprs = loExprs.subList(nequals, nequals+nspatial);
if (hiExprs != null) {
hiExprs = hiExprs.subList(nequals, nequals+nspatial);
}
atts.put(Label.LOW_COMPARAND, loExprs);
if (hiExprs != null) {
atts.put(Label.HIGH_COMPARAND, hiExprs);
}
}
else {
int boundColumns = indexKeyRange.boundColumns();
for (int i = 0; i < boundColumns; i++) {
boolean equals = ((i < boundColumns-1) ||
((loExprs != null) && (hiExprs != null) &&
indexKeyRange.loInclusive() && indexKeyRange.hiInclusive() &&
loExprs.get(i).equals(hiExprs.get(i))));
if (equals) {
atts.put(Label.EQUAL_COMPARAND, loExprs.get(i));
}
else {
if (loExprs != null) {
atts.put(Label.LOW_COMPARAND, loExprs.get(i));
atts.put(Label.LOW_COMPARAND, PrimitiveExplainer.getInstance(indexKeyRange.loInclusive()));
}
if (hiExprs != null) {
atts.put(Label.HIGH_COMPARAND, hiExprs.get(i));
atts.put(Label.HIGH_COMPARAND, PrimitiveExplainer.getInstance(indexKeyRange.hiInclusive()));
}
}
}
}
}
for (int i = 0; i < ordering.sortColumns(); i++) {
atts.put(Label.ORDERING, PrimitiveExplainer.getInstance(ordering.ascending(i) ? "ASC" : "DESC"));
}
atts.put(Label.PIPELINE, PrimitiveExplainer.getInstance(lookaheadQuantum));
if (context.hasExtraInfo(this))
atts.putAll(context.getExtraInfo(this).get());
return new CompoundExplainer(Type.SCAN_OPERATOR, atts);
}
// Inner classes
private class Execution extends LeafCursor
{
// 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 = cursor.next();
if (row == null) {
setIdle();
}
if (LOG_EXECUTION) {
LOG.debug("IndexScan_Default$Execution: yield {}", row);
}
return row;
} finally {
if (TAP_NEXT_ENABLED) {
TAP_NEXT.out();
}
}
}
@Override
public void jump(Row row, ColumnSelector columnSelector)
{
if (CURSOR_LIFECYCLE_ENABLED) {
CursorLifecycle.checkIdleOrActive(this);
}
cursor.jump(row, columnSelector);
state = CursorLifecycle.CursorState.ACTIVE;
}
@Override
public void close() {
try {
cursor.close();
} finally {
super.close();
}
}
@Override
public QueryBindings nextBindings() {
QueryBindings bindings = super.nextBindings();
if (cursor instanceof BindingsAwareCursor)
((BindingsAwareCursor)cursor).rebind(bindings);
return bindings;
}
// Execution interface
Execution(QueryContext context, QueryBindingsCursor bindingsCursor)
{
super(context, bindingsCursor);
Table table = index.rootMostTable();
this.cursor = adapter(table).newIndexCursor(context, indexType, indexKeyRange, ordering, scanSelector, false);
}
// Object state
private final RowCursor cursor;
}
private class LookaheadExecution extends LookaheadLeafCursor<BindingsAwareCursor>
{
// 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(IndexScan_Default.this.toString() + ": yield {}", row);
}
return row;
} finally {
if (TAP_NEXT_ENABLED) {
TAP_NEXT.out();
}
}
}
// LookaheadLeafCursor interface
@Override
protected BindingsAwareCursor newCursor(QueryContext context, StoreAdapter adapter) {
return (BindingsAwareCursor)adapter.newIndexCursor(context, indexType, indexKeyRange, ordering, scanSelector, true);
}
@Override
protected BindingsAwareCursor openACursor(QueryBindings bindings, boolean lookahead) {
if (LOG_EXECUTION) {
LOG.debug(IndexScan_Default.this.toString() + ": open{} for {}", lookahead ? " lookahead" : "", bindings);
}
return super.openACursor(bindings, lookahead);
}
// LookaheadExecution interface
LookaheadExecution(QueryContext context, QueryBindingsCursor bindingsCursor,
StoreAdapter adapter, int quantum) {
super(context, bindingsCursor, adapter, quantum);
}
@Override
public String toString() {
return "LookaheadExecution for " + IndexScan_Default.this.toString();
}
}
}