/**
* 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.rule.cost;
import com.foundationdb.ais.model.Join;
import com.foundationdb.ais.model.Table;
import com.foundationdb.qp.rowtype.*;
import com.foundationdb.sql.optimizer.plan.CostEstimate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.foundationdb.sql.optimizer.rule.cost.CostModelMeasurements.*;
public abstract class CostModel
{
protected abstract double treeScan(int rowWidth, long nRows);
public double indexScan(IndexRowType rowType, int nRows)
{
TreeStatistics treeStatistics = treeStatistics(rowType);
return treeScan(treeStatistics.rowWidth(), nRows);
}
public double fullIndexScan(IndexRowType rowType)
{
TreeStatistics treeStatistics = treeStatistics(rowType);
return treeScan(treeStatistics.rowWidth(), treeStatistics.rowCount());
}
public double fullGroupScan(TableRowType rootTableRowType)
{
// A group scan basically does no random access, even to the very first row (I think, at least as far as CPU
// costs are concerned). So for each table in the group, subtract the cost of a tree scan for 0 rows to account
// for this. This leaves just the sequential access costs.
long cost = 0;
for (TableRowType rowType : groupTableRowTypes(rootTableRowType)) {
TreeStatistics treeStatistics = statisticsMap.get(rowType.typeId());
cost +=
treeScan(treeStatistics.rowWidth(), treeStatistics.rowCount())
- treeScan(treeStatistics.rowWidth(), 0);
}
return cost;
}
public double partialGroupScan(TableRowType rowType, long rowCount)
{
TreeStatistics treeStatistics = statisticsMap.get(rowType.typeId());
return treeScan(treeStatistics.rowWidth(), rowCount)
- treeScan(treeStatistics.rowWidth(), 0);
}
public double ancestorLookup(List<TableRowType> ancestorTableTypes)
{
// Overhead of AncestorLookup_Default not measured
double cost = 0;
for (TableRowType ancestorTableType : ancestorTableTypes) {
cost += hKeyBoundGroupScanSingleRow(ancestorTableType);
}
return cost;
}
public double branchLookup(TableRowType branchRootType)
{
// Overhead of BranchLookup_Default not measured
// TODO: Add filtering by row type
return hKeyBoundGroupScanBranch(branchRootType);
}
public double sort(int nRows, boolean mixedMode)
{
return SORT_SETUP + SORT_PER_ROW * nRows * (mixedMode ? 1 : SORT_MIXED_MODE_FACTOR);
}
public double sortWithLimit(int nRows, int sortFields)
{
return nRows * SORT_LIMIT_PER_ROW * (1 + sortFields * SORT_LIMIT_PER_FIELD_FACTOR);
}
public double select(int nRows)
{
return nRows * (SELECT_PER_ROW + EXPRESSION_PER_FIELD);
}
public double project(int nFields, int nRows)
{
return nRows * (PROJECT_PER_ROW + nFields * EXPRESSION_PER_FIELD);
}
public double distinct(int nRows)
{
return nRows * DISTINCT_PER_ROW;
}
public double product(int nRows)
{
return nRows * PRODUCT_PER_ROW;
}
public double map(int nOuterRows, int nInnerRowsPerOuter)
{
return (nOuterRows * (nInnerRowsPerOuter + 1)) * MAP_PER_ROW;
}
public double flatten(int nRows)
{
return FLATTEN_OVERHEAD + nRows * FLATTEN_PER_ROW;
}
public double intersect(int nLeftRows, int nRightRows)
{
return (nLeftRows + nRightRows) * INTERSECT_PER_ROW;
}
public double union(int nLeftRows, int nRightRows)
{
return (nLeftRows + nRightRows) * UNION_PER_ROW;
}
public double hKeyUnion(int nLeftRows, int nRightRows)
{
return (nLeftRows + nRightRows) * HKEY_UNION_PER_ROW;
}
public double selectWithFilter(int inputRows, int filterRows, double selectivity)
{
return
filterRows * BLOOM_FILTER_LOAD_PER_ROW +
inputRows * (BLOOM_FILTER_SCAN_PER_ROW + selectivity * BLOOM_FILTER_SCAN_SELECTIVITY_COEFFICIENT);
}
public double loadHashTable(int nrows, int nJoinCols, int nCols) {
return (nrows * (HASH_TABLE_LOAD_PER_ROW +
((nJoinCols - 1) * HASH_TABLE_DIFF_PER_JOIN) +
(nCols * HASH_TABLE_COLUMN_COUNT_OFFSET)));
}
public double unloadHashTable(int nrows, int nJoinCols, int nCols) {
return (nrows * (HASH_TABLE_SCAN_PER_ROW +
((nJoinCols - 1) * HASH_TABLE_DIFF_PER_JOIN) +
(nCols * HASH_TABLE_COLUMN_COUNT_OFFSET)));
}
private double hKeyBoundGroupScanSingleRow(TableRowType rootTableRowType)
{
TreeStatistics treeStatistics = treeStatistics(rootTableRowType);
return treeScan(treeStatistics.rowWidth(), 1);
}
private double hKeyBoundGroupScanBranch(TableRowType rootTableRowType)
{
// Cost includes access to root
double cost = hKeyBoundGroupScanSingleRow(rootTableRowType);
// The rest of the cost is sequential access. It is proportional to the fullGroupScan -- divide that cost
// by the number of rows in the root table, assuming that each group has the same size.
TreeStatistics rootTableStatistics = statisticsMap.get(rootTableRowType.typeId());
cost += fullGroupScan(rootTableRowType) / rootTableStatistics.rowCount();
return cost;
}
private TreeStatistics treeStatistics(RowType rowType)
{
return statisticsMap.get(rowType.typeId());
}
private List<TableRowType> groupTableRowTypes(TableRowType rootTableRowType)
{
List<TableRowType> rowTypes = new ArrayList<>();
List<Table> groupTables = new ArrayList<>();
findGroupTables(rootTableRowType.table(), groupTables);
for (Table table : groupTables) {
rowTypes.add(schema.tableRowType(table));
}
return rowTypes;
}
private void findGroupTables(Table table, List<Table> groupTables)
{
groupTables.add(table);
for (Join join : table.getChildJoins()) {
findGroupTables(join.getChild(), groupTables);
}
}
/** Hook for testing. */
public CostEstimate adjustCostEstimate(CostEstimate costEstimate) {
return costEstimate;
}
protected CostModel(Schema schema, TableRowCounts tableRowCounts)
{
this.schema = schema;
this.tableRowCounts = tableRowCounts;
for (RowType rowType : schema.allTableTypes()) {
TableRowType tableRowType = (TableRowType)rowType;
TreeStatistics tableStatistics = TreeStatistics.forTable(tableRowType, tableRowCounts);
statisticsMap.put(tableRowType.typeId(), tableStatistics);
for (IndexRowType indexRowType : tableRowType.indexRowTypes()) {
TreeStatistics indexStatistics = TreeStatistics.forIndex(indexRowType, tableRowCounts);
statisticsMap.put(indexRowType.typeId(), indexStatistics);
}
}
for (IndexRowType indexRowType : schema.groupIndexRowTypes()) {
TreeStatistics indexStatistics = TreeStatistics.forIndex(indexRowType, tableRowCounts);
statisticsMap.put(indexRowType.typeId(), indexStatistics);
}
}
private final Schema schema;
private final TableRowCounts tableRowCounts;
private final Map<Integer, TreeStatistics> statisticsMap = new HashMap<>();
}