提交 1e25c068 authored 作者: Thomas Mueller's avatar Thomas Mueller

Experimental feature to support nested joins.

上级 81de9e71
......@@ -1229,7 +1229,7 @@ public class Parser {
if (readIf("ON")) {
on = readExpression();
}
newTop.addJoin(top, true, on);
newTop.addJoin(top, true, fromOuter, on);
top = newTop;
last = newTop;
} else if (readIf("LEFT")) {
......@@ -1241,10 +1241,10 @@ public class Parser {
if (readIf("ON")) {
on = readExpression();
}
top.addJoin(join, true, on);
top.addJoin(join, true, fromOuter, on);
last = join;
} else if (readIf("FULL")) {
throw this.getSyntaxError();
throw getSyntaxError();
} else if (readIf("INNER")) {
read("JOIN");
TableFilter join = readTableFilter(fromOuter);
......@@ -1253,7 +1253,11 @@ public class Parser {
if (readIf("ON")) {
on = readExpression();
}
top.addJoin(join, fromOuter, on);
if (SysProperties.NESTED_JOINS) {
top.addJoin(join, false, fromOuter, on);
} else {
top.addJoin(join, fromOuter, fromOuter, on);
}
last = join;
} else if (readIf("JOIN")) {
TableFilter join = readTableFilter(fromOuter);
......@@ -1262,12 +1266,20 @@ public class Parser {
if (readIf("ON")) {
on = readExpression();
}
top.addJoin(join, fromOuter, on);
if (SysProperties.NESTED_JOINS) {
top.addJoin(join, false, fromOuter, on);
} else {
top.addJoin(join, fromOuter, fromOuter, on);
}
last = join;
} else if (readIf("CROSS")) {
read("JOIN");
TableFilter join = readTableFilter(fromOuter);
top.addJoin(join, fromOuter, null);
if (SysProperties.NESTED_JOINS) {
top.addJoin(join, false, fromOuter, null);
} else {
top.addJoin(join, fromOuter, fromOuter, null);
}
last = join;
} else if (readIf("NATURAL")) {
read("JOIN");
......@@ -1296,7 +1308,11 @@ public class Parser {
}
}
}
top.addJoin(join, fromOuter, on);
if (SysProperties.NESTED_JOINS) {
top.addJoin(join, false, fromOuter, on);
} else {
top.addJoin(join, fromOuter, fromOuter, on);
}
last = join;
} else {
break;
......@@ -1543,6 +1559,12 @@ public class Parser {
command.addTableFilter(top, true);
boolean isOuter = false;
while (true) {
for (TableFilter n = top.getNestedJoin(); n != null; n = n.getNestedJoin()) {
command.addTableFilter(n, false);
for (TableFilter j = n.getJoin(); j != null; j = j.getJoin()) {
command.addTableFilter(j, false);
}
}
TableFilter join = top.getJoin();
if (join == null) {
break;
......@@ -1810,7 +1832,7 @@ public class Parser {
int idx = filters.indexOf(rightFilter);
if (idx >= 0) {
filters.remove(idx);
leftFilter.addJoin(rightFilter, true, r);
leftFilter.addJoin(rightFilter, true, false, r);
} else {
rightFilter.mapAndAddFilter(r);
}
......
......@@ -232,7 +232,7 @@ public class Optimizer {
TableFilter[] f2 = bestPlan.getFilters();
topFilter = f2[0];
for (int i = 0; i < f2.length - 1; i++) {
f2[i].addJoin(f2[i + 1], false, null);
f2[i].addJoin(f2[i + 1], false, false, null);
}
for (TableFilter f : f2) {
PlanItem item = bestPlan.getItem(f);
......
......@@ -845,9 +845,15 @@ public class Select extends Query {
optimizer.optimize();
topTableFilter = optimizer.getTopFilter();
double planCost = optimizer.getCost();
setEvaluatableRecursive(topTableFilter);
TableFilter f = topTableFilter;
while (f != null) {
topTableFilter.prepare();
return planCost;
}
private void setEvaluatableRecursive(TableFilter f) {
for (; f != null; f = f.getJoin()) {
f.setEvaluatable(f, true);
if (condition != null) {
condition.setEvaluatable(f, true);
......@@ -879,10 +885,11 @@ public class Select extends Query {
for (Expression e : expressions) {
e.setEvaluatable(f, true);
}
f = f.getJoin();
TableFilter n = f.getNestedJoin();
if (n != null) {
setEvaluatableRecursive(n);
}
}
topTableFilter.prepare();
return planCost;
}
public String getPlanSQL() {
......
......@@ -385,6 +385,12 @@ public class SysProperties {
* The minimum write delay that causes commits to be delayed.
*/
public static final int MIN_WRITE_DELAY = getIntSetting("h2.minWriteDelay", 5);
/**
* System property <code>h2.nestedJoins</code> (default: false).<br />
* Whether nested joins should be supported.
*/
public static final boolean NESTED_JOINS = getBooleanSetting("h2.nestedJoins", false);
/**
* System property <code>h2.nioLoadMapped</code> (default: false).<br />
......
......@@ -21,6 +21,7 @@ public class PlanItem {
private Index index;
private PlanItem joinPlan;
private PlanItem nestedJoinPlan;
void setIndex(Index index) {
this.index = index;
......@@ -33,9 +34,17 @@ public class PlanItem {
PlanItem getJoinPlan() {
return joinPlan;
}
PlanItem getNestedJoinPlan() {
return nestedJoinPlan;
}
void setJoinPlan(PlanItem joinPlan) {
this.joinPlan = joinPlan;
}
void setNestedJoinPlan(PlanItem nestedJoinPlan) {
this.nestedJoinPlan = nestedJoinPlan;
}
}
......@@ -70,8 +70,22 @@ public class TableFilter implements ColumnResolver {
private SearchRow currentSearchRow;
private Row current;
private int state;
/**
* The joined table (if there is one).
*/
private TableFilter join;
private boolean outerJoin;
/**
* Whether this table is an outer join.
*/
private boolean joinOuter;
/**
* The nested joined table (if there is one).
*/
private TableFilter nestedJoin;
private ArrayList<Column> naturalJoinColumns;
private boolean foundOne;
private Expression fullCondition;
......@@ -153,6 +167,14 @@ public class TableFilter implements ColumnResolver {
// x (x.a=10); y (x.b=y.b) - see issue 113
item.cost -= item.cost * indexConditions.size() / 100 / level;
}
if (nestedJoin != null) {
setEvaluatable(nestedJoin);
item.setNestedJoinPlan(nestedJoin.getBestPlanItem(s, level));
int todoNestJoinCostWrong;
// TODO optimizer: calculate cost of a join: should use separate
// expected row number and lookup cost
item.cost += item.cost * item.getNestedJoinPlan().cost;
}
if (join != null) {
setEvaluatable(join);
item.setJoinPlan(join.getBestPlanItem(s, level));
......@@ -170,6 +192,10 @@ public class TableFilter implements ColumnResolver {
if (e != null) {
e.setEvaluatable(this, true);
}
TableFilter n = join.getNestedJoin();
if (n != null) {
setEvaluatable(n);
}
join = join.getJoin();
} while (join != null);
}
......@@ -181,6 +207,11 @@ public class TableFilter implements ColumnResolver {
*/
public void setPlanItem(PlanItem item) {
setIndex(item.getIndex());
if (nestedJoin != null) {
if (item.getNestedJoinPlan() != null) {
nestedJoin.setPlanItem(item.getNestedJoinPlan());
}
}
if (join != null) {
if (item.getJoinPlan() != null) {
join.setPlanItem(item.getJoinPlan());
......@@ -204,6 +235,12 @@ public class TableFilter implements ColumnResolver {
}
}
}
if (nestedJoin != null) {
if (SysProperties.CHECK && nestedJoin == this) {
DbException.throwInternalError("self join");
}
nestedJoin.prepare();
}
if (join != null) {
if (SysProperties.CHECK && join == this) {
DbException.throwInternalError("self join");
......@@ -226,6 +263,9 @@ public class TableFilter implements ColumnResolver {
public void startQuery(Session s) {
this.session = s;
scanCount = 0;
if (nestedJoin != null) {
nestedJoin.startQuery(s);
}
if (join != null) {
join.startQuery(s);
}
......@@ -235,6 +275,9 @@ public class TableFilter implements ColumnResolver {
* Reset to the current position.
*/
public void reset() {
if (nestedJoin != null) {
nestedJoin.reset();
}
if (join != null) {
join.reset();
}
......@@ -253,15 +296,35 @@ public class TableFilter implements ColumnResolver {
} else if (state == BEFORE_FIRST) {
cursor.find(session, indexConditions);
if (!cursor.isAlwaysFalse()) {
if (nestedJoin != null) {
nestedJoin.reset();
}
if (join != null) {
join.reset();
}
}
} else {
// state == FOUND || LAST_ROW
// state == FOUND || NULL_ROW
// the last row was ok - try next row of the join
if (join != null && join.next()) {
return true;
if (nestedJoin != null) {
if (join == null) {
if (nestedJoin.next()) {
return true;
}
} else {
while (true) {
if (nestedJoin.next()) {
if (join.next()) {
return true;
}
join.reset();
}
}
}
} else {
if (join != null && join.next()) {
return true;
}
}
}
while (true) {
......@@ -285,14 +348,18 @@ public class TableFilter implements ColumnResolver {
}
// if no more rows found, try the null row (for outer joins only)
if (state == AFTER_LAST) {
if (outerJoin && !foundOne) {
state = NULL_ROW;
current = table.getNullRow();
currentSearchRow = current;
if (joinOuter && !foundOne) {
setNullRow();
} else {
break;
}
}
if (state == FOUND && nestedJoin != null) {
nestedJoin.reset();
if (!nestedJoin.next()) {
continue;
}
}
if (!isOk(filterCondition)) {
continue;
}
......@@ -318,6 +385,16 @@ public class TableFilter implements ColumnResolver {
state = AFTER_LAST;
return false;
}
private void setNullRow() {
state = NULL_ROW;
current = table.getNullRow();
currentSearchRow = current;
if (nestedJoin != null) {
int todoRecurse;
nestedJoin.setNullRow();
}
}
private void checkTimeout() {
session.checkCanceled();
......@@ -407,26 +484,39 @@ public class TableFilter implements ColumnResolver {
* @param outer if this is an outer join
* @param on the join condition
*/
public void addJoin(TableFilter filter, boolean outer, Expression on) {
public void addJoin(TableFilter filter, boolean outer, boolean nested, Expression on) {
if (on != null) {
on.mapColumns(this, 0);
}
if (join == null) {
this.join = filter;
filter.outerJoin = outer;
if (outer) {
// convert all inner joins on the right hand side to outer joins
TableFilter f = filter.join;
while (f != null) {
f.outerJoin = true;
f = f.join;
}
if (nested && SysProperties.NESTED_JOINS) {
if (nestedJoin != null) {
throw DbException.throwInternalError();
}
nestedJoin = filter;
filter.joinOuter = outer;
if (on != null) {
filter.mapAndAddFilter(on);
}
} else {
join.addJoin(filter, outer, on);
if (join == null) {
join = filter;
filter.joinOuter = outer;
if (!SysProperties.NESTED_JOINS) {
if (outer) {
// convert all inner joins on the right hand side to outer joins
TableFilter f = filter.join;
while (f != null) {
f.joinOuter = true;
f = f.join;
}
}
}
if (on != null) {
filter.mapAndAddFilter(on);
}
} else {
join.addJoin(filter, outer, nested, on);
}
}
}
......@@ -439,6 +529,9 @@ public class TableFilter implements ColumnResolver {
on.mapColumns(this, 0);
addFilterCondition(on, true);
on.createIndexConditions(session, this);
if (nestedJoin != null) {
nestedJoin.mapAndAddFilter(on);
}
if (join != null) {
join.mapAndAddFilter(on);
}
......@@ -454,7 +547,7 @@ public class TableFilter implements ColumnResolver {
* @return true if it is
*/
public boolean isJoinOuter() {
return outerJoin;
return joinOuter;
}
/**
......@@ -466,12 +559,15 @@ public class TableFilter implements ColumnResolver {
public String getPlanSQL(boolean isJoin) {
StringBuilder buff = new StringBuilder();
if (isJoin) {
if (outerJoin) {
if (joinOuter) {
buff.append("LEFT OUTER JOIN ");
} else {
buff.append("INNER JOIN ");
}
}
if (nestedJoin != null) {
buff.append("(");
}
buff.append(table.getSQL());
if (alias != null) {
buff.append(' ').append(Parser.quoteIdentifier(alias));
......@@ -490,6 +586,11 @@ public class TableFilter implements ColumnResolver {
String plan = StringUtils.quoteRemarkSQL(planBuff.toString());
buff.append(plan).append(" */");
}
if (nestedJoin != null) {
buff.append('\n');
buff.append(nestedJoin.getPlanSQL(true));
buff.append(")");
}
if (isJoin) {
buff.append(" ON ");
if (joinCondition == null) {
......@@ -594,9 +695,12 @@ public class TableFilter implements ColumnResolver {
*/
void optimizeFullCondition(boolean fromOuterJoin) {
if (fullCondition != null) {
fullCondition.addFilterConditions(this, fromOuterJoin || outerJoin);
fullCondition.addFilterConditions(this, fromOuterJoin || joinOuter);
if (nestedJoin != null) {
nestedJoin.optimizeFullCondition(fromOuterJoin || joinOuter);
}
if (join != null) {
join.optimizeFullCondition(fromOuterJoin || outerJoin);
join.optimizeFullCondition(fromOuterJoin || joinOuter);
}
}
}
......@@ -615,6 +719,9 @@ public class TableFilter implements ColumnResolver {
if (joinCondition != null) {
joinCondition.setEvaluatable(filter, b);
}
if (nestedJoin != null) {
nestedJoin.setEvaluatable(filter, b);
}
if (join != null) {
join.setEvaluatable(filter, b);
}
......@@ -744,4 +851,8 @@ public class TableFilter implements ColumnResolver {
}
}
public TableFilter getNestedJoin() {
return nestedJoin;
}
}
......@@ -38,6 +38,7 @@ import org.h2.test.db.TestMultiConn;
import org.h2.test.db.TestMultiDimension;
import org.h2.test.db.TestMultiThread;
import org.h2.test.db.TestMultiThreadedKernel;
import org.h2.test.db.TestNestedJoins;
import org.h2.test.db.TestOpenClose;
import org.h2.test.db.TestOptimizations;
import org.h2.test.db.TestOutOfMemory;
......@@ -302,6 +303,7 @@ java org.h2.test.TestAll timer
// System.setProperty("h2.largeTransactions", "true");
// System.setProperty("h2.lobInDatabase", "true");
// System.setProperty("h2.analyzeAuto", "100");
// System.setProperty("h2.nestedJoins", "true");
int speedup;
// System.setProperty("h2.syncMethod", "");
......@@ -516,6 +518,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestMultiDimension().runTest(this);
new TestMultiThread().runTest(this);
new TestMultiThreadedKernel().runTest(this);
new TestNestedJoins().runTest(this);
new TestOpenClose().runTest(this);
new TestOptimizations().runTest(this);
new TestOutOfMemory().runTest(this);
......
/*
* Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.db;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.constant.SysProperties;
import org.h2.test.TestBase;
import org.h2.util.ScriptReader;
/**
* Tests nested joins and right outer joins.
*/
public class TestNestedJoins extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
System.setProperty("h2.nestedJoins", "true");
TestBase.createCaller().init().test();
}
public void test() throws Exception {
if (!SysProperties.NESTED_JOINS) {
return;
}
deleteDb("nestedJoins");
Connection conn = getConnection("nestedJoins");
Statement stat = conn.createStatement();
ResultSet rs;
String sql;
// not yet supported:
/*
stat.execute("drop table a, b, c");
stat.execute("create table a(x int)");
stat.execute("create table b(x int)");
stat.execute("create table c(x int, y int)");
stat.execute("insert into a values(1), (2)");
stat.execute("insert into b values(3)");
stat.execute("insert into c values(1, 3)");
stat.execute("insert into c values(4, 5)");
rs = stat.executeQuery("explain select * from a left outer join (b left outer join c on b.x = c.y) on a.x = c.x");
assertTrue(rs.next());
sql = cleanRemarks(conn, rs.getString(1));
assertEquals("", sql);
rs = stat.executeQuery("select * from a left outer join (b left outer join c on b.x = c.y) on a.x = c.x");
// expected result: 1 3 1 3; 2 null null null
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals("3", rs.getString(2));
assertEquals("1", rs.getString(3));
assertEquals("3", rs.getString(4));
assertTrue(rs.next());
assertEquals("2", rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3));
assertEquals(null, rs.getString(4));
assertFalse(rs.next());
*/
stat.execute("create table a(x int primary key)");
stat.execute("insert into a values(0), (1)");
stat.execute("create table b(x int primary key)");
stat.execute("insert into b values(0)");
stat.execute("create table c(x int primary key)");
rs = stat.executeQuery("select a.*, b.*, c.* from a left outer join (b inner join c on b.x = c.x) on a.x = b.x");
// expected result: 0, null, null; 1, null, null
assertTrue(rs.next());
assertEquals("0", rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3));
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3));
assertFalse(rs.next());
rs = stat.executeQuery("select * from a left outer join b on a.x = b.x inner join c on b.x = c.x");
// expected result: -
assertFalse(rs.next());
rs = stat.executeQuery("select * from a left outer join b on a.x = b.x left outer join c on b.x = c.x");
// expected result: 0 0 null; 1 null null
assertTrue(rs.next());
assertEquals("0", rs.getString(1));
assertEquals("0", rs.getString(2));
assertEquals(null, rs.getString(3));
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3));
assertFalse(rs.next());
rs = stat.executeQuery("select * from a left outer join (b inner join c on b.x = c.x) on a.x = b.x");
// expected result: 0 null null; 1 null null
assertTrue(rs.next());
assertEquals("0", rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3));
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3));
assertFalse(rs.next());
rs = stat.executeQuery("explain select * from a left outer join (b inner join c on c.x = 1) on a.x = b.x");
assertTrue(rs.next());
sql = cleanRemarks(conn, rs.getString(1));
assertEquals("SELECT A.X, B.X, C.X FROM PUBLIC.A LEFT OUTER JOIN (PUBLIC.B INNER JOIN PUBLIC.C ON (C.X = 1) AND (A.X = B.X)) ON A.X = B.X", sql);
stat.execute("create table test(id int primary key)");
stat.execute("insert into test values(0), (1), (2)");
rs = stat.executeQuery("select * from test a left outer join (test b inner join test c on b.id = c.id - 2) on a.id = b.id + 1");
// drop table test;
// create table test(id int primary key);
// insert into test values(0), (1), (2);
// select * from test a left outer join (test b inner join test c on b.id = c.id - 2) on a.id = b.id + 1;
// expected result: 0 null null; 1 0 2; 2 null null
assertTrue(rs.next());
assertEquals("0", rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3));
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals("0", rs.getString(2));
assertEquals("2", rs.getString(3));
assertTrue(rs.next());
assertEquals("2", rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3));
assertFalse(rs.next());
// while (rs.next()) {
// for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) {
// System.out.print(rs.getString(i + 1) + " ");
// }
// System.out.println();
// }
conn.close();
deleteDb("nestedJoins");
}
private String cleanRemarks(Connection conn, String sql) throws SQLException {
ScriptReader r = new ScriptReader(new StringReader(sql));
r.setSkipRemarks(true);
sql = r.readStatement();
sql = sql.replaceAll("\\n", " ");
// sql = sql.replaceAll("\\/\\*.*\\*\\/", "");
while (sql.indexOf(" ") >= 0) {
sql = sql.replaceAll(" ", " ");
}
return sql;
}
}
......@@ -2,23 +2,6 @@ Nested Outer Joins
-----------------
Example:
drop table a, b, c;
create table a(x int primary key);
insert into a values(0), (1);
create table b(x int primary key);
insert into b values(0);
create table c(x int primary key);
select * from a left outer join b on a.x = b.x inner join c on b.x = c.x;
select * from a left outer join b on a.x = b.x left outer join c on b.x = c.x;
select * from a left outer join (b inner join c on b.x = c.x) on a.x = b.x;
select a.x, b_x, c_x from a left outer join (select b.x b_x, c.x c_x from b inner join c on b.x = c.x) on a.x = b_x;
drop table test;
create table test(id int primary key);
insert into test values(0), (1), (2);
select * from test a left outer join (test b inner join test c on b.id = c.id - 2) on a.id = b.id + 1;
select a.id, b_id, c_id from test a left outer join (select b.id b_id, c.id c_id from test b inner join test c on b.id = c.id - 2) on a.id = b_id + 1;
create table a(x int);
create table b(x int);
create table c(x int, y int);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论