提交 d6f7374e authored 作者: Sergi Vladykin's avatar Sergi Vladykin

Merge pull request #221 from svladykin/order

OptimizerHints dropped in favor of SET FORCE_JOIN_ORDER
...@@ -13,6 +13,8 @@ import java.math.BigInteger; ...@@ -13,6 +13,8 @@ import java.math.BigInteger;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.text.Collator; import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
...@@ -177,6 +179,14 @@ public class Parser { ...@@ -177,6 +179,14 @@ public class Parser {
CURRENT_TIME = 23, ROWNUM = 24; CURRENT_TIME = 23, ROWNUM = 24;
private static final int SPATIAL_INTERSECTS = 25; private static final int SPATIAL_INTERSECTS = 25;
private static final Comparator<TableFilter> TABLE_FILTER_COMPARATOR =
new Comparator<TableFilter>() {
@Override
public int compare(TableFilter o1, TableFilter o2) {
return o1 == o2 ? 0 : compareTableFilters(o1, o2);
}
};
private final Database database; private final Database database;
private final Session session; private final Session session;
/** /**
...@@ -208,6 +218,7 @@ public class Parser { ...@@ -208,6 +218,7 @@ public class Parser {
private boolean rightsChecked; private boolean rightsChecked;
private boolean recompileAlways; private boolean recompileAlways;
private ArrayList<Parameter> indexedParameterList; private ArrayList<Parameter> indexedParameterList;
private int orderInFrom;
public Parser(Session session) { public Parser(Session session) {
this.database = session.getDatabase(); this.database = session.getDatabase();
...@@ -704,7 +715,7 @@ public class Parser { ...@@ -704,7 +715,7 @@ public class Parser {
Update command = new Update(session); Update command = new Update(session);
currentPrepared = command; currentPrepared = command;
int start = lastParseIndex; int start = lastParseIndex;
TableFilter filter = readSimpleTableFilter(); TableFilter filter = readSimpleTableFilter(0);
command.setTableFilter(filter); command.setTableFilter(filter);
read("SET"); read("SET");
if (readIf("(")) { if (readIf("(")) {
...@@ -760,7 +771,7 @@ public class Parser { ...@@ -760,7 +771,7 @@ public class Parser {
return command; return command;
} }
private TableFilter readSimpleTableFilter() { private TableFilter readSimpleTableFilter(int orderInFrom) {
Table table = readTableOrView(); Table table = readTableOrView();
String alias = null; String alias = null;
if (readIf("AS")) { if (readIf("AS")) {
...@@ -772,7 +783,7 @@ public class Parser { ...@@ -772,7 +783,7 @@ public class Parser {
} }
} }
return new TableFilter(session, table, alias, rightsChecked, return new TableFilter(session, table, alias, rightsChecked,
currentSelect); currentSelect, orderInFrom);
} }
private Delete parseDelete() { private Delete parseDelete() {
...@@ -784,7 +795,7 @@ public class Parser { ...@@ -784,7 +795,7 @@ public class Parser {
currentPrepared = command; currentPrepared = command;
int start = lastParseIndex; int start = lastParseIndex;
readIf("FROM"); readIf("FROM");
TableFilter filter = readSimpleTableFilter(); TableFilter filter = readSimpleTableFilter(0);
command.setTableFilter(filter); command.setTableFilter(filter);
if (readIf("WHERE")) { if (readIf("WHERE")) {
Expression condition = readExpression(); Expression condition = readExpression();
...@@ -1186,7 +1197,7 @@ public class Parser { ...@@ -1186,7 +1197,7 @@ public class Parser {
return top; return top;
} }
} else if (readIf("VALUES")) { } else if (readIf("VALUES")) {
table = parseValuesTable().getTable(); table = parseValuesTable(0).getTable();
} else { } else {
String tableName = readIdentifierWithSchema(null); String tableName = readIdentifierWithSchema(null);
Schema schema = getSchema(); Schema schema = getSchema();
...@@ -1236,7 +1247,7 @@ public class Parser { ...@@ -1236,7 +1247,7 @@ public class Parser {
} }
alias = readFromAlias(alias); alias = readFromAlias(alias);
return new TableFilter(session, table, alias, rightsChecked, return new TableFilter(session, table, alias, rightsChecked,
currentSelect); currentSelect, orderInFrom++);
} }
private String readFromAlias(String alias) { private String readFromAlias(String alias) {
...@@ -1610,7 +1621,7 @@ public class Parser { ...@@ -1610,7 +1621,7 @@ public class Parser {
private TableFilter getNested(TableFilter n) { private TableFilter getNested(TableFilter n) {
String joinTable = Constants.PREFIX_JOIN + parseIndex; String joinTable = Constants.PREFIX_JOIN + parseIndex;
TableFilter top = new TableFilter(session, getDualTable(true), TableFilter top = new TableFilter(session, getDualTable(true),
joinTable, rightsChecked, currentSelect); joinTable, rightsChecked, currentSelect, n.getOrderInFrom());
top.addJoin(n, false, true, null); top.addJoin(n, false, true, null);
return top; return top;
} }
...@@ -1873,6 +1884,38 @@ public class Parser { ...@@ -1873,6 +1884,38 @@ public class Parser {
TableFilter filter = readTableFilter(false); TableFilter filter = readTableFilter(false);
parseJoinTableFilter(filter, command); parseJoinTableFilter(filter, command);
} while (readIf(",")); } while (readIf(","));
// Parser can reorder joined table filters, need to explicitly sort them to
// get the order as it was in the original query.
if (session.isForceJoinOrder()) {
sortTableFilters(command.getTopFilters());
}
}
private static void sortTableFilters(ArrayList<TableFilter> filters) {
if (filters.size() < 2) {
return;
}
// Most probably we are already sorted correctly.
boolean sorted = true;
TableFilter prev = filters.get(0);
for (int i = 1; i < filters.size(); i++) {
TableFilter next = filters.get(i);
if (compareTableFilters(prev, next) > 0) {
sorted = false;
break;
}
prev = next;
}
// If not, then sort manually.
if (!sorted) {
Collections.sort(filters, TABLE_FILTER_COMPARATOR);
}
}
private static int compareTableFilters(TableFilter o1, TableFilter o2) {
assert o1.getOrderInFrom() != o2.getOrderInFrom();
return o1.getOrderInFrom() > o2.getOrderInFrom() ? 1 : -1;
} }
private void parseJoinTableFilter(TableFilter top, final Select command) { private void parseJoinTableFilter(TableFilter top, final Select command) {
...@@ -1976,7 +2019,7 @@ public class Parser { ...@@ -1976,7 +2019,7 @@ public class Parser {
// SYSTEM_RANGE(1,1) // SYSTEM_RANGE(1,1)
Table dual = getDualTable(false); Table dual = getDualTable(false);
TableFilter filter = new TableFilter(session, dual, null, TableFilter filter = new TableFilter(session, dual, null,
rightsChecked, currentSelect); rightsChecked, currentSelect, 0);
command.addTableFilter(filter, true); command.addTableFilter(filter, true);
} else { } else {
parseSelectSimpleFromPart(command); parseSelectSimpleFromPart(command);
...@@ -4312,7 +4355,7 @@ public class Parser { ...@@ -4312,7 +4355,7 @@ public class Parser {
private Select parseValues() { private Select parseValues() {
Select command = new Select(session); Select command = new Select(session);
currentSelect = command; currentSelect = command;
TableFilter filter = parseValuesTable(); TableFilter filter = parseValuesTable(0);
ArrayList<Expression> list = New.arrayList(); ArrayList<Expression> list = New.arrayList();
list.add(new Wildcard(null, null)); list.add(new Wildcard(null, null));
command.setExpressions(list); command.setExpressions(list);
...@@ -4321,7 +4364,7 @@ public class Parser { ...@@ -4321,7 +4364,7 @@ public class Parser {
return command; return command;
} }
private TableFilter parseValuesTable() { private TableFilter parseValuesTable(int orderInFrom) {
Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN); Schema mainSchema = database.getSchema(Constants.SCHEMA_MAIN);
TableFunction tf = (TableFunction) Function.getFunction(database, TableFunction tf = (TableFunction) Function.getFunction(database,
"TABLE"); "TABLE");
...@@ -4397,7 +4440,7 @@ public class Parser { ...@@ -4397,7 +4440,7 @@ public class Parser {
tf.doneWithParameters(); tf.doneWithParameters();
Table table = new FunctionTable(mainSchema, session, tf, tf); Table table = new FunctionTable(mainSchema, session, tf, tf);
TableFilter filter = new TableFilter(session, table, null, TableFilter filter = new TableFilter(session, table, null,
rightsChecked, currentSelect); rightsChecked, currentSelect, orderInFrom);
return filter; return filter;
} }
......
...@@ -175,7 +175,7 @@ public class AlterTableAddConstraint extends SchemaCommand { ...@@ -175,7 +175,7 @@ public class AlterTableAddConstraint extends SchemaCommand {
int id = getObjectId(); int id = getObjectId();
String name = generateConstraintName(table); String name = generateConstraintName(table);
ConstraintCheck check = new ConstraintCheck(getSchema(), id, name, table); ConstraintCheck check = new ConstraintCheck(getSchema(), id, name, table);
TableFilter filter = new TableFilter(session, table, null, false, null); TableFilter filter = new TableFilter(session, table, null, false, null, 0);
checkExpression.mapColumns(filter, 0); checkExpression.mapColumns(filter, 0);
checkExpression = checkExpression.optimize(session); checkExpression = checkExpression.optimize(session);
check.setExpression(checkExpression); check.setExpression(checkExpression);
......
...@@ -53,16 +53,6 @@ class Optimizer { ...@@ -53,16 +53,6 @@ class Optimizer {
this.session = session; this.session = session;
} }
/**
* Whether join reordering is enabled (it can be disabled by hint).
*
* @return {@code true} if yes
*/
private static boolean isJoinReorderingEnabled() {
OptimizerHints hints = OptimizerHints.get();
return hints == null || hints.getJoinReorderEnabled();
}
/** /**
* How many filter to calculate using brute force. The remaining filters are * How many filter to calculate using brute force. The remaining filters are
* selected using a greedy algorithm which has a runtime of (1 + 2 + ... + * selected using a greedy algorithm which has a runtime of (1 + 2 + ... +
...@@ -85,7 +75,7 @@ class Optimizer { ...@@ -85,7 +75,7 @@ class Optimizer {
private void calculateBestPlan() { private void calculateBestPlan() {
cost = -1; cost = -1;
if (filters.length == 1 || !isJoinReorderingEnabled()) { if (filters.length == 1 || session.isForceJoinOrder()) {
testPlan(filters); testPlan(filters);
} else { } else {
start = System.currentTimeMillis(); start = System.currentTimeMillis();
...@@ -242,7 +232,7 @@ class Optimizer { ...@@ -242,7 +232,7 @@ class Optimizer {
/** /**
* Calculate the best query plan to use. * Calculate the best query plan to use.
* *
* @param parse If we do not need to really get the best plan because it is view a parsing stage. * @param parse If we do not need to really get the best plan because it is a view parsing stage.
*/ */
void optimize(boolean parse) { void optimize(boolean parse) {
if (parse) { if (parse) {
......
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.command.dml;
/**
* Thread local hints for H2 query optimizer. All the ongoing queries in the
* current thread will run with respect to these hints, so if they are needed
* only for a single operation it is preferable to setup and drop them in
* try-finally block.
*
* Currently works only in embedded mode.
*
* @author Sergi Vladykin
*/
public class OptimizerHints {
private static final ThreadLocal<OptimizerHints> HINTS =
new ThreadLocal<OptimizerHints>();
private boolean joinReorderEnabled = true;
/**
* Set thread local hints or {@code null} to drop any existing hints.
*
* @param hints the hints
*/
public static void set(OptimizerHints hints) {
if (hints != null) {
HINTS.set(hints);
} else {
HINTS.remove();
}
}
/**
* Get the current thread local hints or {@code null} if none.
*
* @return the hints
*/
public static OptimizerHints get() {
return HINTS.get();
}
/**
* Set whether reordering of tables (or anything else in the {@code FROM}
* clause) is enabled. By default is {@code true}.
*
* @param joinReorderEnabled Flag value.
*/
public void setJoinReorderEnabled(boolean joinReorderEnabled) {
this.joinReorderEnabled = joinReorderEnabled;
}
public boolean getJoinReorderEnabled() {
return joinReorderEnabled;
}
}
...@@ -506,6 +506,15 @@ public class Set extends Prepared { ...@@ -506,6 +506,15 @@ public class Set extends Prepared {
session.setJoinBatchEnabled(value == 1); session.setJoinBatchEnabled(value == 1);
break; break;
} }
case SetTypes.FORCE_JOIN_ORDER: {
int value = getIntValue();
if (value != 0 && value != 1) {
throw DbException.getInvalidValueException("FORCE_JOIN_ORDER",
getIntValue());
}
session.setForceJoinOrder(value == 1);
break;
}
default: default:
DbException.throwInternalError("type="+type); DbException.throwInternalError("type="+type);
} }
......
...@@ -233,6 +233,11 @@ public class SetTypes { ...@@ -233,6 +233,11 @@ public class SetTypes {
*/ */
public static final int BATCH_JOINS = 44; public static final int BATCH_JOINS = 44;
/**
* The type of SET FORCE_JOIN_ORDER statement.
*/
public static final int FORCE_JOIN_ORDER = 45;
private static final ArrayList<String> TYPES = New.arrayList(); private static final ArrayList<String> TYPES = New.arrayList();
private SetTypes() { private SetTypes() {
...@@ -286,6 +291,7 @@ public class SetTypes { ...@@ -286,6 +291,7 @@ public class SetTypes {
list.add(QUERY_STATISTICS_MAX_ENTRIES, "QUERY_STATISTICS_MAX_ENTRIES"); list.add(QUERY_STATISTICS_MAX_ENTRIES, "QUERY_STATISTICS_MAX_ENTRIES");
list.add(ROW_FACTORY, "ROW_FACTORY"); list.add(ROW_FACTORY, "ROW_FACTORY");
list.add(BATCH_JOINS, "BATCH_JOINS"); list.add(BATCH_JOINS, "BATCH_JOINS");
list.add(FORCE_JOIN_ORDER, "FORCE_JOIN_ORDER");
} }
/** /**
......
...@@ -120,6 +120,7 @@ public class Session extends SessionWithState { ...@@ -120,6 +120,7 @@ public class Session extends SessionWithState {
private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache; private volatile SmallLRUCache<Object, ViewIndex> viewIndexCache;
private HashMap<Object, ViewIndex> subQueryIndexCache; private HashMap<Object, ViewIndex> subQueryIndexCache;
private boolean joinBatchEnabled; private boolean joinBatchEnabled;
private boolean forceJoinOrder;
/** /**
* Temporary LOBs from result sets. Those are kept for some time. The * Temporary LOBs from result sets. Those are kept for some time. The
...@@ -153,6 +154,14 @@ public class Session extends SessionWithState { ...@@ -153,6 +154,14 @@ public class Session extends SessionWithState {
this.currentSchemaName = Constants.SCHEMA_MAIN; this.currentSchemaName = Constants.SCHEMA_MAIN;
} }
public void setForceJoinOrder(boolean forceJoinOrder) {
this.forceJoinOrder = forceJoinOrder;
}
public boolean isForceJoinOrder() {
return forceJoinOrder;
}
public void setJoinBatchEnabled(boolean joinBatchEnabled) { public void setJoinBatchEnabled(boolean joinBatchEnabled) {
this.joinBatchEnabled = joinBatchEnabled; this.joinBatchEnabled = joinBatchEnabled;
} }
......
...@@ -399,7 +399,7 @@ public class Column { ...@@ -399,7 +399,7 @@ public class Column {
*/ */
public void prepareExpression(Session session) { public void prepareExpression(Session session) {
if (defaultExpression != null) { if (defaultExpression != null) {
computeTableFilter = new TableFilter(session, table, null, false, null); computeTableFilter = new TableFilter(session, table, null, false, null, 0);
defaultExpression.mapColumns(computeTableFilter, 0); defaultExpression.mapColumns(computeTableFilter, 0);
defaultExpression = defaultExpression.optimize(session); defaultExpression = defaultExpression.optimize(session);
} }
......
...@@ -112,6 +112,7 @@ public class TableFilter implements ColumnResolver { ...@@ -112,6 +112,7 @@ public class TableFilter implements ColumnResolver {
private boolean foundOne; private boolean foundOne;
private Expression fullCondition; private Expression fullCondition;
private final int hashCode; private final int hashCode;
private final int orderInFrom;
/** /**
* Create a new table filter object. * Create a new table filter object.
...@@ -121,9 +122,10 @@ public class TableFilter implements ColumnResolver { ...@@ -121,9 +122,10 @@ public class TableFilter implements ColumnResolver {
* @param alias the alias name * @param alias the alias name
* @param rightsChecked true if rights are already checked * @param rightsChecked true if rights are already checked
* @param select the select statement * @param select the select statement
* @param orderInFrom Original order number of this table filter in FROM clause.
*/ */
public TableFilter(Session session, Table table, String alias, public TableFilter(Session session, Table table, String alias,
boolean rightsChecked, Select select) { boolean rightsChecked, Select select, int orderInFrom) {
this.session = session; this.session = session;
this.table = table; this.table = table;
this.alias = alias; this.alias = alias;
...@@ -133,6 +135,11 @@ public class TableFilter implements ColumnResolver { ...@@ -133,6 +135,11 @@ public class TableFilter implements ColumnResolver {
session.getUser().checkRight(table, Right.SELECT); session.getUser().checkRight(table, Right.SELECT);
} }
hashCode = session.nextObjectId(); hashCode = session.nextObjectId();
this.orderInFrom = orderInFrom;
}
public int getOrderInFrom() {
return orderInFrom;
} }
public IndexCursor getIndexCursor() { public IndexCursor getIndexCursor() {
......
...@@ -47,6 +47,7 @@ import org.h2.test.db.TestMultiThreadedKernel; ...@@ -47,6 +47,7 @@ import org.h2.test.db.TestMultiThreadedKernel;
import org.h2.test.db.TestOpenClose; import org.h2.test.db.TestOpenClose;
import org.h2.test.db.TestOptimizations; import org.h2.test.db.TestOptimizations;
import org.h2.test.db.TestCompatibilityOracle; import org.h2.test.db.TestCompatibilityOracle;
import org.h2.test.db.TestOptimizerHints;
import org.h2.test.db.TestOutOfMemory; import org.h2.test.db.TestOutOfMemory;
import org.h2.test.db.TestPowerOff; import org.h2.test.db.TestPowerOff;
import org.h2.test.db.TestQueryCache; import org.h2.test.db.TestQueryCache;
...@@ -683,6 +684,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -683,6 +684,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestMultiThreadedKernel()); addTest(new TestMultiThreadedKernel());
addTest(new TestOpenClose()); addTest(new TestOpenClose());
addTest(new TestOptimizations()); addTest(new TestOptimizations());
addTest(new TestOptimizerHints());
addTest(new TestOutOfMemory()); addTest(new TestOutOfMemory());
addTest(new TestReadOnly()); addTest(new TestReadOnly());
addTest(new TestRecursiveQueries()); addTest(new TestRecursiveQueries());
......
...@@ -9,11 +9,12 @@ import java.sql.Connection; ...@@ -9,11 +9,12 @@ import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.command.dml.OptimizerHints; import java.util.Arrays;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.StatementBuilder;
/** /**
* Test for optimizer hints. * Test for optimizer hint SET FORCE_JOIN_ORDER.
* *
* @author Sergi Vladykin * @author Sergi Vladykin
*/ */
...@@ -30,55 +31,128 @@ public class TestOptimizerHints extends TestBase { ...@@ -30,55 +31,128 @@ public class TestOptimizerHints extends TestBase {
@Override @Override
public void test() throws Exception { public void test() throws Exception {
if (config.networked) {
return;
}
deleteDb("testOptimizerHints"); deleteDb("testOptimizerHints");
Connection conn = getConnection("testOptimizerHints"); Connection conn = getConnection("testOptimizerHints;FORCE_JOIN_ORDER=1");
Statement s = conn.createStatement(); Statement s = conn.createStatement();
s.execute("create table t1(id int)"); s.execute("create table t1(id int unique)");
s.execute("create table t2(id int, ref_id int)"); s.execute("create table t2(id int unique, t1_id int)");
s.execute("create table t3(id int unique)");
s.execute("insert into t1 values(1),(2),(3)"); s.execute("create table t4(id int unique, t2_id int, t3_id int)");
s.execute("insert into t2 values(1,2),(2,3),(3,4),(4,6),(5,1),(6,4)");
String plan;
s.execute("create unique index idx1_id on t1(id)");
s.execute("create index idx2_id on t2(id)"); plan = plan(s, "select * from t1, t2 where t1.id = t2.t1_id");
s.execute("create index idx2_ref_id on t2(ref_id)"); assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T2"));
enableJoinReordering(false); plan = plan(s, "select * from t2, t1 where t1.id = t2.t1_id");
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T1"));
try {
String plan; plan = plan(s, "select * from t2, t1 where t1.id = 1");
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T1"));
plan = plan(s, "select * from t1, t2 where t1.id = t2.ref_id");
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T2")); plan = plan(s, "select * from t2, t1 where t1.id = t2.t1_id and t2.id = 1");
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T1"));
plan = plan(s, "select * from t2, t1 where t1.id = t2.ref_id");
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T1")); plan = plan(s, "select * from t1, t2 where t1.id = t2.t1_id and t2.id = 1");
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T2"));
plan = plan(s, "select * from t2, t1 where t1.id = 1");
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T1")); checkPlanComma(s, "t1", "t2", "t3", "t4");
checkPlanComma(s, "t4", "t2", "t3", "t1");
plan = plan(s, "select * from t2, t1 where t1.id = t2.ref_id and t2.id = 1"); checkPlanComma(s, "t2", "t1", "t3", "t4");
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T1")); checkPlanComma(s, "t1", "t4", "t3", "t2");
checkPlanComma(s, "t2", "t1", "t4", "t3");
checkPlanComma(s, "t4", "t3", "t2", "t1");
boolean on = false;
boolean left = false;
checkPlanJoin(s, on, left, "t1", "t2", "t3", "t4");
checkPlanJoin(s, on, left, "t4", "t2", "t3", "t1");
checkPlanJoin(s, on, left, "t2", "t1", "t3", "t4");
checkPlanJoin(s, on, left, "t1", "t4", "t3", "t2");
checkPlanJoin(s, on, left, "t2", "t1", "t4", "t3");
checkPlanJoin(s, on, left, "t4", "t3", "t2", "t1");
on = false;
left = true;
checkPlanJoin(s, on, left, "t1", "t2", "t3", "t4");
checkPlanJoin(s, on, left, "t4", "t2", "t3", "t1");
checkPlanJoin(s, on, left, "t2", "t1", "t3", "t4");
checkPlanJoin(s, on, left, "t1", "t4", "t3", "t2");
checkPlanJoin(s, on, left, "t2", "t1", "t4", "t3");
checkPlanJoin(s, on, left, "t4", "t3", "t2", "t1");
on = true;
left = false;
checkPlanJoin(s, on, left, "t1", "t2", "t3", "t4");
checkPlanJoin(s, on, left, "t4", "t2", "t3", "t1");
checkPlanJoin(s, on, left, "t2", "t1", "t3", "t4");
checkPlanJoin(s, on, left, "t1", "t4", "t3", "t2");
checkPlanJoin(s, on, left, "t2", "t1", "t4", "t3");
checkPlanJoin(s, on, left, "t4", "t3", "t2", "t1");
on = true;
left = true;
checkPlanJoin(s, on, left, "t1", "t2", "t3", "t4");
checkPlanJoin(s, on, left, "t4", "t2", "t3", "t1");
checkPlanJoin(s, on, left, "t2", "t1", "t3", "t4");
checkPlanJoin(s, on, left, "t1", "t4", "t3", "t2");
checkPlanJoin(s, on, left, "t2", "t1", "t4", "t3");
checkPlanJoin(s, on, left, "t4", "t3", "t2", "t1");
s.close();
conn.close();
deleteDb("testOptimizerHints");
}
plan = plan(s, "select * from t1, t2 where t1.id = t2.ref_id and t2.id = 1"); private void checkPlanComma(Statement s, String ... t) throws SQLException {
assertTrue(plan, plan.contains("INNER JOIN PUBLIC.T2")); StatementBuilder from = new StatementBuilder();
} finally { for (String table : t) {
enableJoinReordering(true); from.appendExceptFirst(", ");
from.append(table);
}
String plan = plan(s, "select 1 from " + from.toString() + " where t1.id = t2.t1_id "
+ "and t2.id = t4.t2_id and t3.id = t4.t3_id");
int prev = plan.indexOf("FROM PUBLIC." + t[0].toUpperCase());
for (int i = 1; i < t.length; i++) {
int next = plan.indexOf("INNER JOIN PUBLIC." + t[i].toUpperCase());
assertTrue("Wrong plan for : " + Arrays.toString(t) + "\n" + plan, next > prev);
prev = next;
} }
deleteDb("testOptimizerHints");
} }
/** private void checkPlanJoin(Statement s, boolean on, boolean left, String ... t) throws SQLException {
* @param enable Enabled. StatementBuilder from = new StatementBuilder();
*/ for (int i = 0; i < t.length; i++) {
private void enableJoinReordering(boolean enable) { if (i != 0) {
OptimizerHints hints = new OptimizerHints(); if (left) {
hints.setJoinReorderEnabled(enable); from.append(" left join ");
OptimizerHints.set(hints); } else {
from.append(" inner join ");
}
}
from.append(t[i]);
if (on && i != 0) {
from.append(" on 1=1 ");
}
}
String plan = plan(s, "select 1 from " + from.toString() + " where t1.id = t2.t1_id "
+ "and t2.id = t4.t2_id and t3.id = t4.t3_id");
int prev = plan.indexOf("FROM PUBLIC." + t[0].toUpperCase());
for (int i = 1; i < t.length; i++) {
int next = plan.indexOf(
(!left ? "INNER JOIN PUBLIC." : on ? "LEFT OUTER JOIN PUBLIC." : "PUBLIC.") +
t[i].toUpperCase());
if (prev > next) {
System.err.println(plan);
fail("Wrong plan for : " + Arrays.toString(t) + "\n" + plan);
}
prev = next;
}
} }
/** /**
......
...@@ -27,7 +27,6 @@ import java.util.concurrent.ThreadFactory; ...@@ -27,7 +27,6 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.h2.api.TableEngine; import org.h2.api.TableEngine;
import org.h2.command.ddl.CreateTableData; import org.h2.command.ddl.CreateTableData;
import org.h2.command.dml.OptimizerHints;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.Expression; import org.h2.expression.Expression;
...@@ -410,15 +409,9 @@ public class TestTableEngines extends TestBase { ...@@ -410,15 +409,9 @@ public class TestTableEngines extends TestBase {
} }
private void testBatchedJoin() throws SQLException { private void testBatchedJoin() throws SQLException {
if (config.networked) {
// networked test is disabled because it relies on OptimizerHints which is ThreadLocal
return;
}
deleteDb("testBatchedJoin"); deleteDb("testBatchedJoin");
Connection conn = getConnection("testBatchedJoin;OPTIMIZE_REUSE_RESULTS=0;BATCH_JOINS=1"); Connection conn = getConnection("testBatchedJoin;OPTIMIZE_REUSE_RESULTS=0;BATCH_JOINS=1");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
Session s = (Session) ((JdbcConnection) conn).getSession();
assertTrue(s.isJoinBatchEnabled());
setBatchingEnabled(stat, false); setBatchingEnabled(stat, false);
setBatchingEnabled(stat, true); setBatchingEnabled(stat, true);
...@@ -431,7 +424,7 @@ public class TestTableEngines extends TestBase { ...@@ -431,7 +424,7 @@ public class TestTableEngines extends TestBase {
} }
}); });
enableJoinReordering(false); forceJoinOrder(stat, true);
try { try {
doTestBatchedJoinSubQueryUnion(stat); doTestBatchedJoinSubQueryUnion(stat);
...@@ -469,22 +462,14 @@ public class TestTableEngines extends TestBase { ...@@ -469,22 +462,14 @@ public class TestTableEngines extends TestBase {
assertTrue(TreeSetIndex.lookupBatches.get() > 0); assertTrue(TreeSetIndex.lookupBatches.get() > 0);
} finally { } finally {
enableJoinReordering(true); forceJoinOrder(stat, false);
TreeSetIndex.exec.shutdownNow(); TreeSetIndex.exec.shutdownNow();
} }
deleteDb("testBatchedJoin"); deleteDb("testBatchedJoin");
} }
/** private void forceJoinOrder(Statement s, boolean force) throws SQLException {
* @param enable Enabled. s.executeUpdate("SET FORCE_JOIN_ORDER " + force);
*/
private void enableJoinReordering(boolean enable) {
OptimizerHints hints = null;
if (!enable) {
hints = new OptimizerHints();
hints.setJoinReorderEnabled(false);
}
OptimizerHints.set(hints);
} }
private void checkPlan(Statement stat, String sql) throws SQLException { private void checkPlan(Statement stat, String sql) throws SQLException {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论