org.h2.table.Plan Maven / Gradle / Ivy
/*
* Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.table;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.h2.command.query.AllColumnsForPlan;
import org.h2.engine.SessionLocal;
import org.h2.expression.Expression;
import org.h2.expression.ExpressionVisitor;
import org.h2.message.Trace;
/**
* A possible query execution plan. The time required to execute a query depends
* on the order the tables are accessed.
*/
public class Plan {
private final TableFilter[] filters;
private final HashMap planItems = new HashMap<>();
private final Expression[] allConditions;
private final TableFilter[] allFilters;
/**
* Create a query plan with the given order.
*
* @param filters the tables of the query
* @param count the number of table items
* @param condition the condition in the WHERE clause
*/
public Plan(TableFilter[] filters, int count, Expression condition) {
this.filters = new TableFilter[count];
System.arraycopy(filters, 0, this.filters, 0, count);
final ArrayList allCond = new ArrayList<>();
final ArrayList all = new ArrayList<>();
if (condition != null) {
allCond.add(condition);
}
for (int i = 0; i < count; i++) {
TableFilter f = filters[i];
f.visit(f1 -> {
all.add(f1);
if (f1.getJoinCondition() != null) {
allCond.add(f1.getJoinCondition());
}
});
}
allConditions = allCond.toArray(new Expression[0]);
allFilters = all.toArray(new TableFilter[0]);
}
/**
* Get the plan item for the given table.
*
* @param filter the table
* @return the plan item
*/
public PlanItem getItem(TableFilter filter) {
return planItems.get(filter);
}
/**
* The the list of tables.
*
* @return the list of tables
*/
public TableFilter[] getFilters() {
return filters;
}
/**
* Remove all index conditions that can not be used.
*/
public void removeUnusableIndexConditions() {
for (int i = 0; i < allFilters.length; i++) {
TableFilter f = allFilters[i];
setEvaluatable(f, true);
if (i < allFilters.length - 1) {
// the last table doesn't need the optimization,
// otherwise the expression is calculated twice unnecessarily
// (not that bad but not optimal)
f.optimizeFullCondition();
}
f.removeUnusableIndexConditions();
}
for (TableFilter f : allFilters) {
setEvaluatable(f, false);
}
}
/**
* Calculate the cost of this query plan.
*
* @param session the session
* @param allColumnsSet calculates all columns on-demand
* @return the cost
*/
public double calculateCost(SessionLocal session, AllColumnsForPlan allColumnsSet) {
Trace t = session.getTrace();
if (t.isDebugEnabled()) {
t.debug("Plan : calculate cost for plan {0}", Arrays.toString(allFilters));
}
double cost = 1;
boolean invalidPlan = false;
for (int i = 0; i < allFilters.length; i++) {
TableFilter tableFilter = allFilters[i];
if (t.isDebugEnabled()) {
t.debug("Plan : for table filter {0}", tableFilter);
}
PlanItem item = tableFilter.getBestPlanItem(session, allFilters, i, allColumnsSet);
planItems.put(tableFilter, item);
if (t.isDebugEnabled()) {
t.debug("Plan : best plan item cost {0} index {1}",
item.cost, item.getIndex().getPlanSQL());
}
cost += cost * item.cost;
setEvaluatable(tableFilter, true);
Expression on = tableFilter.getJoinCondition();
if (on != null) {
if (!on.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) {
invalidPlan = true;
break;
}
}
}
if (invalidPlan) {
cost = Double.POSITIVE_INFINITY;
}
if (t.isDebugEnabled()) {
session.getTrace().debug("Plan : plan cost {0}", cost);
}
for (TableFilter f : allFilters) {
setEvaluatable(f, false);
}
return cost;
}
private void setEvaluatable(TableFilter filter, boolean b) {
filter.setEvaluatable(filter, b);
for (Expression e : allConditions) {
e.setEvaluatable(filter, b);
}
}
}