/**
* 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.sql.optimizer.plan;
import com.foundationdb.ais.model.IndexColumn;
import com.foundationdb.sql.optimizer.plan.Sort.OrderByExpression;
import com.foundationdb.util.Strings;
import java.util.*;
public abstract class IndexScan extends BaseScan implements IndexIntersectionNode<ConditionExpression,IndexScan>, JoinTreeScan
{
public static enum OrderEffectiveness {
NONE, PARTIAL_GROUPED, GROUPED, SORTED, FOR_MIN_MAX
}
private TableSource rootMostTable, rootMostInnerTable, leafMostInnerTable, leafMostTable;
private boolean covering;
// The cost of just the scan of this index, not counting lookups, flattening, etc
private CostEstimate scanCostEstimate;
public IndexScan(TableSource table) {
rootMostTable = rootMostInnerTable = leafMostInnerTable = leafMostTable = table;
}
public IndexScan(TableSource rootMostTable,
TableSource rootMostInnerTable,
TableSource leafMostInnerTable,
TableSource leafMostTable) {
this.rootMostTable = rootMostTable;
this.rootMostInnerTable = rootMostInnerTable;
this.leafMostInnerTable = leafMostInnerTable;
this.leafMostTable = leafMostTable;
}
public TableSource getRootMostTable() {
return rootMostTable;
}
public TableSource getRootMostInnerTable() {
return rootMostInnerTable;
}
public TableSource getLeafMostInnerTable() {
return leafMostInnerTable;
}
@Override
public TableSource getLeafMostTable() {
return leafMostTable;
}
/** Return tables included in the index, leafmost to rootmost. */
public List<TableSource> getTables() {
List<TableSource> tables = new ArrayList<>();
TableSource table = leafMostTable;
while (true) {
tables.add(table);
if (table == rootMostTable) break;
table = table.getParentTable();
}
return tables;
}
public boolean isCovering() {
return covering;
}
public void setCovering(boolean covering) {
this.covering = covering;
}
public CostEstimate getScanCostEstimate() {
return scanCostEstimate;
}
public void setScanCostEstimate(CostEstimate scanCostEstimate) {
this.scanCostEstimate = scanCostEstimate;
}
@Override
public boolean accept(PlanVisitor v) {
if (v.visitEnter(this)) {
if (v instanceof ExpressionRewriteVisitor) {
visitComparands((ExpressionRewriteVisitor)v);
}
else if (v instanceof ExpressionVisitor) {
visitComparands((ExpressionVisitor)v);
}
// Don't visit any tables here; they are done by a lookup when that's needed.
}
return v.visitLeave(this);
}
@Override
protected boolean maintainInDuplicateMap() {
return true;
}
@Override
protected void deepCopy(DuplicateMap map) {
super.deepCopy(map);
}
@Override
public int getPeggedCount() {
return getNEquality();
}
public abstract List<OrderByExpression> getOrdering();
public abstract OrderEffectiveness getOrderEffectiveness();
public abstract List<ExpressionNode> getColumns();
public abstract List<IndexColumn> getIndexColumns();
public abstract int getNKeyColumns();
public abstract boolean usesAllColumns();
public abstract void setUsesAllColumns(boolean usesAllColumns);
public abstract List<ExpressionNode> getEqualityComparands();
public abstract List<ConditionExpression> getConditions();
public abstract boolean hasConditions();
public abstract ExpressionNode getLowComparand();
public abstract boolean isLowInclusive();
public abstract ExpressionNode getHighComparand();
public abstract boolean isHighInclusive();
public abstract boolean isSpatial();
/**
* The index of the first column that may have more than one result
* NOTE: by default Unions are included in this count.
*/
public abstract int getNEquality();
/**
* The number of unions that come after the true equalities.
*/
public abstract int getNUnions();
/**
* By Default a union (i.e. UNION of [% = 30, % = 50, % = 90]) is considered
* to be an equality, and thus can be turned into UnionOrdered. If there is an OrderBy or MIN on that column,
* we need to compare the union column and not skip over it. Setting this to false means that the union column
* is not included in NEquality.
*/
public abstract void setIncludeUnionAsEquality(boolean sortColumn);
public abstract boolean isAscendingAt(int index);
public abstract boolean isRecoverableAt(int index);
public static final boolean INCLUDE_SCAN_COST_IN_SUMMARY = false;
@Override
public String summaryString(SummaryConfiguration configuration) {
return summaryString(-1, configuration);
}
public String summaryString(boolean prettyFormat, SummaryConfiguration configuration) {
return summaryString(prettyFormat ? 0 : -1, configuration);
}
private String summaryString(int indentation, SummaryConfiguration configuration) {
StringBuilder sb = new StringBuilder();
buildSummaryString(sb, indentation, true, configuration);
return sb.toString();
}
protected void buildSummaryString(StringBuilder str, int indentation, boolean full,
SummaryConfiguration configuration) {
str.append(super.summaryString(configuration));
str.append("(");
if (configuration.includeIndexTableNames) {
str.append(" on [");
for (TableSource table : getTables()) {
str.append(table.getName());
str.append(", ");
}
str.replace(str.length() - 2, str.length(), "] ");
}
str.append(summarizeIndex(indentation, configuration));
if (indentation < 0) {
str.append(", ");
}
else if (indentation == 0) {
str.append(Strings.NL);
indent(str, 1).append("-> ");
}
if (full && covering)
str.append("covering/");
if (full && getOrderEffectiveness() != null)
str.append(getOrderEffectiveness());
if (full && getOrdering() != null) {
boolean anyReverse = false, allReverse = true;
for (int i = 0; i < getOrdering().size(); i++) {
if (getOrdering().get(i).isAscending() != isAscendingAt(i))
anyReverse = true;
else
allReverse = false;
}
if (anyReverse) {
if (allReverse)
str.append("/reverse");
else {
for (int i = 0; i < getOrdering().size(); i++) {
str.append((i == 0) ? "/" : ",");
str.append(getOrdering().get(i).isAscending() ? "ASC" : "DESC");
}
}
}
}
describeEqualityComparands(str);
if (isSpatial()) {
if (getLowComparand() != null) {
str.append(", @");
if (getHighComparand() == null)
str.append("@");
str.append(getLowComparand());
}
}
else {
if (getLowComparand() != null) {
str.append(", ");
str.append((isLowInclusive()) ? ">=" : ">");
str.append(getLowComparand());
}
if (getHighComparand() != null) {
str.append(", ");
str.append((isHighInclusive()) ? "<=" : "<");
str.append(getHighComparand());
}
}
describeConditionRange(str);
if (full && getCostEstimate() != null) {
str.append(", ");
if (INCLUDE_SCAN_COST_IN_SUMMARY &&
!getCostEstimate().equals(scanCostEstimate)) {
str.append(scanCostEstimate);
str.append(" -> ");
}
str.append(getCostEstimate());
}
str.append(")");
}
protected static StringBuilder indent(int indentation) {
return indent(new StringBuilder(), indentation);
}
protected static StringBuilder indent(StringBuilder str, int indentation) {
while (indentation --> 0) {
str.append(" ");
}
return str;
}
protected abstract String summarizeIndex(int indentation, SummaryConfiguration configuration);
protected void describeConditionRange(StringBuilder output) {}
protected void describeEqualityComparands(StringBuilder output) {}
}