提交 dc5f9f44 authored 作者: Thomas Mueller's avatar Thomas Mueller

Experimental feature to support nested joins.

上级 48b9d209
...@@ -6,13 +6,20 @@ ...@@ -6,13 +6,20 @@
*/ */
package org.h2.test.db; package org.h2.test.db;
import java.io.File;
import java.io.StringReader; import java.io.StringReader;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
import org.h2.store.fs.FileSystem;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.New;
import org.h2.util.ScriptReader; import org.h2.util.ScriptReader;
/** /**
...@@ -20,6 +27,8 @@ import org.h2.util.ScriptReader; ...@@ -20,6 +27,8 @@ import org.h2.util.ScriptReader;
*/ */
public class TestNestedJoins extends TestBase { public class TestNestedJoins extends TestBase {
ArrayList<Statement> dbs = New.arrayList();
/** /**
* Run just this test. * Run just this test.
* *
...@@ -27,24 +36,285 @@ public class TestNestedJoins extends TestBase { ...@@ -27,24 +36,285 @@ public class TestNestedJoins extends TestBase {
*/ */
public static void main(String... a) throws Exception { public static void main(String... a) throws Exception {
System.setProperty("h2.nestedJoins", "true"); System.setProperty("h2.nestedJoins", "true");
TestBase.createCaller().init().test(); TestBase test = TestBase.createCaller().init();
test.config.traceTest = true;
test.test();
} }
public void test() throws Exception { public void test() throws Exception {
if (!SysProperties.NESTED_JOINS) { if (!SysProperties.NESTED_JOINS) {
return; return;
} }
deleteDb("nestedJoins");
testCases();
testRandom();
deleteDb("nestedJoins");
}
private void testRandom() throws Exception {
Connection conn = getConnection("nestedJoins");
dbs.add(conn.createStatement());
try {
Class.forName("org.postgresql.Driver");
Connection c2 = DriverManager.getConnection("jdbc:postgresql:test", "sa", "sa");
dbs.add(c2.createStatement());
} catch (Exception e) {
// database not installed - ok
}
// Derby doesn't work currently
deleteDerby();
try {
Class.forName("org.apache.derby.jdbc.EmbeddedDriver");
Connection c2 = DriverManager.getConnection("jdbc:derby:" + getBaseDir() + "/derby/test;create=true", "sa", "sa");
dbs.add(c2.createStatement());
} catch (Exception e) {
// database not installed - ok
}
// if (dbs.size() == 0) {
// return;
// }
String shortest = null;
Throwable shortestEx = null;
for (int i = 0; i < 10; i++) {
try {
execute("drop table t" + i);
} catch (Exception e) {
// ignore
}
String sql = "create table t" + i + "(x int)";
trace(sql + ";");
execute(sql);
if (i >= 4) {
for (int j = 0; j < i; j++) {
sql = "insert into t" + i + " values(" + j + ")";
trace(sql + ";");
execute(sql);
}
}
}
// the first 4 tables: all combinations
for (int i = 0; i < 16; i++) {
for (int j = 0; j < 4; j++) {
if ((i & (1 << j)) != 0) {
String sql = "insert into t" + j + " values(" + i + ")";
trace(sql + ";");
execute(sql);
}
}
}
Random random = new Random(1);
for (int i=0; i<10000; i++) {
StringBuilder buff = new StringBuilder();
int t = 1 + random.nextInt(9);
buff.append("select ");
for (int j = 0; j < t; j++) {
if (j > 0) {
buff.append(", ");
}
buff.append("t" + j + ".x ");
}
buff.append("from ");
appendRandomJoin(random, buff, 0, t - 1);
String sql = buff.toString();
try {
execute(sql);
} catch (Throwable e) {
if (e instanceof SQLException) {
SQLException se = (SQLException) e;
trace(sql);
int todoFail;
int test;
System.out.println(se);
System.out.println(" " + sql);
}
if (e != null) {
if (shortest == null || sql.length() < shortest.length()) {
shortest = sql;
shortestEx = e;
}
}
}
}
if (shortest != null) {
shortestEx.printStackTrace();
fail(shortest + " " + shortestEx);
}
for (int i = 0; i < 10; i++) {
try {
execute("drop table t" + i);
} catch (Exception e) {
// ignore
}
}
for (Statement s : dbs) {
s.getConnection().close();
}
deleteDerby();
deleteDb("nestedJoins"); deleteDb("nestedJoins");
}
private void deleteDerby() {
try {
new File("derby.log").delete();
try {
DriverManager.getConnection("jdbc:derby:" + getBaseDir() + "/derby/test;shutdown=true", "sa", "sa");
} catch (Exception e) {
// ignore
}
FileSystem.getInstance(getBaseDir()).deleteRecursive(getBaseDir() + "/derby", false);
} catch (Exception e) {
e.printStackTrace();
// database not installed - ok
}
}
private void appendRandomJoin(Random random, StringBuilder buff, int min, int max) {
if (min == max) {
buff.append("t" + min);
return;
}
buff.append("(");
int m = min + random.nextInt(max - min);
int left = min + (m == min ? 0 : random.nextInt(m - min));
appendRandomJoin(random, buff, min, m);
switch (random.nextInt(3)) {
case 0:
buff.append(" inner join ");
break;
case 1:
buff.append(" left outer join ");
break;
case 2:
buff.append(" right outer join ");
break;
}
m++;
int right = m + (m == max ? 0 : random.nextInt(max - m));
appendRandomJoin(random, buff, m, max);
buff.append(" on t" + left + ".x = t" + right + ".x ");
buff.append(")");
}
private void execute(String sql) throws SQLException {
String expected = null;
SQLException e = null;
for (Statement s : dbs) {
try {
boolean result = s.execute(sql);
if (result) {
String data = getResult(s.getResultSet());
if (expected == null) {
expected = data;
} else {
assertEquals(sql, expected, data);
}
}
} catch (SQLException e2) {
// ignore now, throw at the end
e = e2;
}
}
if (e != null) {
throw e;
}
}
private String getResult(ResultSet rs) throws SQLException {
ArrayList<String> list = New.arrayList();
while (rs.next()) {
StringBuilder buff = new StringBuilder();
for (int i=0; i<rs.getMetaData().getColumnCount(); i++) {
if (i > 0) {
buff.append(" ");
}
buff.append(rs.getString(i + 1));
}
list.add(buff.toString());
}
Collections.sort(list);
return list.toString();
}
private void testCases() throws Exception {
Connection conn = getConnection("nestedJoins"); Connection conn = getConnection("nestedJoins");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
ResultSet rs; ResultSet rs;
String sql; String sql;
// not yet supported:
/* /*
create table t1(a int, b int);
create table t2(a int, b int);
create table t3(a int, b int);
create table t4(a int, b int);
insert into t1 values(1,1), (2,2), (3,3);
insert into t2 values(1,1), (2,2);
insert into t3 values(1,1), (3,3);
insert into t4 values(1,1), (2,2), (3,3), (4,4);
select distinct t1.a, t2.a, t3.a from t1
right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a;
drop table t1, t2, t3, t4;
*/
stat.execute("create table t1(a int, b int)");
stat.execute("create table t2(a int, b int)");
stat.execute("create table t3(a int, b int)");
stat.execute("create table t4(a int, b int)");
stat.execute("insert into t1 values(1,1), (2,2), (3,3)");
stat.execute("insert into t2 values(1,1), (2,2)");
stat.execute("insert into t3 values(1,1), (3,3)");
stat.execute("insert into t4 values(1,1), (2,2), (3,3), (4,4)");
rs = stat.executeQuery("explain select distinct t1.a, t2.a, t3.a from t1 right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a");
assertTrue(rs.next());
sql = cleanRemarks(conn, rs.getString(1));
assertEquals("SELECT DISTINCT T1.A, T2.A, T3.A FROM PUBLIC.T2 LEFT OUTER JOIN ( PUBLIC.T3 LEFT OUTER JOIN ( PUBLIC.T1 ) ON T1.B = T3.A ) ON T2.B = T1.A", sql);
rs = stat.executeQuery("select distinct t1.a, t2.a, t3.a from t1 right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a");
// expected: 1 1 1; null 2 null
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals("1", rs.getString(2));
assertEquals("1", rs.getString(3));
assertTrue(rs.next());
assertEquals(null, rs.getString(1));
assertEquals("2", rs.getString(2));
assertEquals(null, rs.getString(3));
assertFalse(rs.next());
stat.execute("drop table t1, t2, t3, t4");
/*
create table a(x int);
create table b(x int);
create table c(x int);
insert into a values(1);
insert into b values(1);
insert into c values(1), (2);
select a.x, b.x, c.x from a inner join b on a.x = b.x
right outer join c on c.x = a.x;
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)");
stat.execute("insert into a values(1)");
stat.execute("insert into b values(1)");
stat.execute("insert into c values(1), (2)");
rs = stat.executeQuery("explain select a.x, b.x, c.x from a inner join b on a.x = b.x right outer join c on c.x = a.x");
assertTrue(rs.next());
sql = cleanRemarks(conn, rs.getString(1));
assertEquals("SELECT A.X, B.X, C.X FROM PUBLIC.C LEFT OUTER JOIN ( PUBLIC.A INNER JOIN PUBLIC.B ON A.X = B.X ) ON C.X = A.X", sql);
rs = stat.executeQuery("select a.x, b.x, c.x from a inner join b on a.x = b.x right outer join c on c.x = a.x");
// expected result: 1 1 1; null null 2
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals("1", rs.getString(2));
assertEquals("1", rs.getString(3));
assertTrue(rs.next());
assertEquals(null, rs.getString(1));
assertEquals(null, rs.getString(2));
assertEquals("2", rs.getString(3));
assertFalse(rs.next());
stat.execute("drop table a, b, c"); stat.execute("drop table a, b, c");
stat.execute("create table a(x int)"); stat.execute("create table a(x int)");
stat.execute("create table b(x int)"); stat.execute("create table b(x int)");
stat.execute("create table c(x int, y int)"); stat.execute("create table c(x int, y int)");
...@@ -52,12 +322,23 @@ public class TestNestedJoins extends TestBase { ...@@ -52,12 +322,23 @@ public class TestNestedJoins extends TestBase {
stat.execute("insert into b values(3)"); stat.execute("insert into b values(3)");
stat.execute("insert into c values(1, 3)"); stat.execute("insert into c values(1, 3)");
stat.execute("insert into c values(4, 5)"); 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"); 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()); assertTrue(rs.next());
sql = cleanRemarks(conn, rs.getString(1)); sql = cleanRemarks(conn, rs.getString(1));
assertEquals("", sql); assertEquals("SELECT A.X, B.X, C.X, C.Y FROM PUBLIC.A " +
"LEFT OUTER JOIN ( PUBLIC.B " +
rs = stat.executeQuery("select * from a left outer join (b left outer join c on b.x = c.y) on a.x = c.x"); "LEFT OUTER JOIN PUBLIC.C " +
"ON B.X = C.Y ) " +
"ON A.X = C.X", 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 // expected result: 1 3 1 3; 2 null null null
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals("1", rs.getString(1)); assertEquals("1", rs.getString(1));
...@@ -70,15 +351,18 @@ public class TestNestedJoins extends TestBase { ...@@ -70,15 +351,18 @@ public class TestNestedJoins extends TestBase {
assertEquals(null, rs.getString(3)); assertEquals(null, rs.getString(3));
assertEquals(null, rs.getString(4)); assertEquals(null, rs.getString(4));
assertFalse(rs.next()); assertFalse(rs.next());
*/ stat.execute("drop table a, b, c");
stat.execute("create table a(x int primary key)"); stat.execute("create table a(x int primary key)");
stat.execute("insert into a values(0), (1)"); stat.execute("insert into a values(0), (1)");
stat.execute("create table b(x int primary key)"); stat.execute("create table b(x int primary key)");
stat.execute("insert into b values(0)"); stat.execute("insert into b values(0)");
stat.execute("create table c(x int primary key)"); stat.execute("create table c(x int primary key)");
rs = stat.executeQuery("select a.*, b.*, c.* from a " +
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"); "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 // expected result: 0, null, null; 1, null, null
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals("0", rs.getString(1)); assertEquals("0", rs.getString(1));
...@@ -89,12 +373,14 @@ public class TestNestedJoins extends TestBase { ...@@ -89,12 +373,14 @@ public class TestNestedJoins extends TestBase {
assertEquals(null, rs.getString(2)); assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3)); assertEquals(null, rs.getString(3));
assertFalse(rs.next()); assertFalse(rs.next());
rs = stat.executeQuery("select * from a " +
rs = stat.executeQuery("select * from a left outer join b on a.x = b.x inner join c on b.x = c.x"); "left outer join b on a.x = b.x " +
"inner join c on b.x = c.x");
// expected result: - // expected result: -
assertFalse(rs.next()); assertFalse(rs.next());
rs = stat.executeQuery("select * from a " +
rs = stat.executeQuery("select * from a left outer join b on a.x = b.x left outer join c on b.x = c.x"); "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 // expected result: 0 0 null; 1 null null
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals("0", rs.getString(1)); assertEquals("0", rs.getString(1));
...@@ -106,7 +392,9 @@ public class TestNestedJoins extends TestBase { ...@@ -106,7 +392,9 @@ public class TestNestedJoins extends TestBase {
assertEquals(null, rs.getString(3)); assertEquals(null, rs.getString(3));
assertFalse(rs.next()); 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"); 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 // expected result: 0 null null; 1 null null
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals("0", rs.getString(1)); assertEquals("0", rs.getString(1));
...@@ -117,19 +405,26 @@ public class TestNestedJoins extends TestBase { ...@@ -117,19 +405,26 @@ public class TestNestedJoins extends TestBase {
assertEquals(null, rs.getString(2)); assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3)); assertEquals(null, rs.getString(3));
assertFalse(rs.next()); assertFalse(rs.next());
rs = stat.executeQuery("explain select * from a " +
rs = stat.executeQuery("explain select * from a left outer join (b inner join c on c.x = 1) on a.x = b.x"); "left outer join (b " +
"inner join c on c.x = 1) on a.x = b.x");
assertTrue(rs.next()); assertTrue(rs.next());
sql = cleanRemarks(conn, rs.getString(1)); 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); assertEquals("SELECT A.X, B.X, C.X FROM PUBLIC.A " +
"LEFT OUTER JOIN ( PUBLIC.B " +
"INNER JOIN PUBLIC.C ON C.X = 1 ) ON A.X = B.X", sql);
stat.execute("drop table a, b, c");
stat.execute("create table test(id int primary key)"); stat.execute("create table test(id int primary key)");
stat.execute("insert into test values(0), (1), (2)"); 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"); 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; // drop table test;
// create table test(id int primary key); // create table test(id int primary key);
// insert into test values(0), (1), (2); // 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 * 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 // expected result: 0 null null; 1 0 2; 2 null null
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals("0", rs.getString(1)); assertEquals("0", rs.getString(1));
...@@ -144,6 +439,40 @@ public class TestNestedJoins extends TestBase { ...@@ -144,6 +439,40 @@ public class TestNestedJoins extends TestBase {
assertEquals(null, rs.getString(2)); assertEquals(null, rs.getString(2));
assertEquals(null, rs.getString(3)); assertEquals(null, rs.getString(3));
assertFalse(rs.next()); assertFalse(rs.next());
stat.execute("drop table test");
stat.execute("create table a(pk int, val varchar(255))");
stat.execute("create table b(pk int, val varchar(255))");
stat.execute("create table base(pk int, deleted int)");
stat.execute("insert into base values(1, 0)");
stat.execute("insert into base values(2, 1)");
stat.execute("insert into base values(3, 0)");
stat.execute("insert into a values(1, 'a')");
stat.execute("insert into b values(2, 'a')");
stat.execute("insert into b values(3, 'a')");
rs = stat.executeQuery("explain select a.pk, a_base.pk, b.pk, b_base.pk from a " +
"inner join base a_base on a.pk = a_base.pk " +
"left outer join (b inner join base b_base " +
"on b.pk = b_base.pk and b_base.deleted = 0) on 1=1");
assertTrue(rs.next());
sql = cleanRemarks(conn, rs.getString(1));
assertEquals("SELECT A.PK, A_BASE.PK, B.PK, B_BASE.PK FROM PUBLIC.BASE A_BASE " +
"LEFT OUTER JOIN ( PUBLIC.B " +
"INNER JOIN PUBLIC.BASE B_BASE " +
"ON (B_BASE.DELETED = 0) AND (B.PK = B_BASE.PK) ) " +
"ON TRUE INNER JOIN PUBLIC.A ON 1=1 WHERE A.PK = A_BASE.PK", sql);
rs = stat.executeQuery("select a.pk, a_base.pk, b.pk, b_base.pk from a " +
"inner join base a_base on a.pk = a_base.pk " +
"left outer join (b inner join base b_base " +
"on b.pk = b_base.pk and b_base.deleted = 0) on 1=1");
// expected: 1 1 3 3
assertTrue(rs.next());
assertEquals("1", rs.getString(1));
assertEquals("1", rs.getString(2));
assertEquals("3", rs.getString(3));
assertEquals("3", rs.getString(3));
assertFalse(rs.next());
stat.execute("drop table a, b, base");
// while (rs.next()) { // while (rs.next()) {
// for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) { // for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论