/* This file is part of VoltDB.
* Copyright (C) 2008-2014 VoltDB Inc.
*
* 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 VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb.planner;
import java.util.ArrayList;
import org.voltdb.catalog.Index;
import org.voltdb.compiler.DeterminismMode;
import org.voltdb.planner.parseinfo.StmtTableScan;
import org.voltdb.plannodes.AbstractPlanNode;
import org.voltdb.plannodes.IndexScanPlanNode;
import org.voltdb.plannodes.SeqScanPlanNode;
import org.voltdb.types.IndexType;
import org.voltdb.types.SortDirectionType;
import org.voltdb.utils.CatalogUtil;
/// An end-stage plan rewriter that replaces a plan that uses sequential scans
/// with a slightly less efficient one that uses deterministic (unique)
/// index scans in their place.
/// This optimization is intended for use where a non-deterministic result
/// can lead to unreliability -- where a query might be feeding information
/// to a database write within the same transaction.
/// Unlike the other MicroOptimization classes, this is not actually a
/// performance optimization. It is instead "optimizing for reliability".
public class ScanDeterminizer {
/**
* Only applies when stronger determinism is needed.
*/
public static void apply(CompiledPlan plan, DeterminismMode detMode)
{
if (detMode == DeterminismMode.FASTER) {
return;
}
if (plan.hasDeterministicStatement()) {
return;
}
AbstractPlanNode planGraph = plan.rootPlanGraph;
if (planGraph.isOrderDeterministic()) {
return;
}
AbstractPlanNode root = plan.rootPlanGraph;
root = recursivelyApply(root);
plan.rootPlanGraph = root;
}
static private AbstractPlanNode recursivelyApply(AbstractPlanNode plan)
{
assert(plan != null);
// depth first:
// Find Sequential Scan node.
// Replace with any unique tree index scan if possible.
ArrayList<AbstractPlanNode> children = new ArrayList<AbstractPlanNode>();
for (int i = 0; i < plan.getChildCount(); i++) {
children.add(plan.getChild(i));
}
for (AbstractPlanNode child : children) {
// TODO this will break when children feed multiple parents
AbstractPlanNode newChild = recursivelyApply(child);
// Do a graft into the (parent) plan only if a replacement for a child was found.
if (newChild == child) {
continue;
}
boolean replaced = plan.replaceChild(child, newChild);
assert(replaced);
}
// skip the meat if this isn't a scan node
if ( ! (plan instanceof SeqScanPlanNode)) {
return plan;
}
SeqScanPlanNode scanNode = (SeqScanPlanNode) plan;
if (scanNode.isSubQuery()) {
// This is a sub-query and can't have indexes
return plan;
}
// got here? we're got ourselves a sequential scan over a real table
assert (scanNode.getChildCount() == 0);
StmtTableScan tableScan = scanNode.getTableScan();
assert(tableScan != null);
Index indexToScan = null;
// Pick the narrowest index from all of the unique tree indexes.
// note: This is not the same as picking the narrowest key in c++,
// which is probably what you want if it turns out this optimization
// does anything for performance at all.
for (Index index : tableScan.getIndexes()) {
// skip non-unique indexes
if (index.getUnique() == false) {
continue;
}
// skip hash indexes
else if (index.getType() != IndexType.BALANCED_TREE.getValue()) {
continue;
}
else {
if (indexToScan == null) {
indexToScan = index;
}
else {
if (CatalogUtil.getCatalogIndexSize(indexToScan) > CatalogUtil.getCatalogIndexSize(index)) {
indexToScan = index;
}
}
}
}
if (indexToScan == null) {
return plan;
}
// make an index node from the scan node
IndexScanPlanNode indexScanNode = new IndexScanPlanNode(scanNode, null, indexToScan, SortDirectionType.ASC);
indexScanNode.setForDeterminismOnly();
return indexScanNode;
}
}