提交 026b35f4 authored 作者: Thomas Mueller's avatar Thomas Mueller

Support for null-safe equals. This includes the ANSI SQL standard syntax A IS…

Support for null-safe equals. This includes the ANSI SQL standard syntax A IS [NOT] DISTINCT FROM B as well as the shorter A IS [NOT] B.
上级 b47e8b61
...@@ -1550,6 +1550,7 @@ ID<>2 ...@@ -1550,6 +1550,7 @@ ID<>2
"Other Grammar","Condition Right Hand Side"," "Other Grammar","Condition Right Hand Side","
compare { { { ALL | ANY | SOME } ( select ) } | operand } compare { { { ALL | ANY | SOME } ( select ) } | operand }
| IS [ NOT ] NULL | IS [ NOT ] NULL
| IS [ NOT ] [ DISTINCT FROM ] operand
| BETWEEN operand AND operand | BETWEEN operand AND operand
| IN ( { select | expression [,...] } ) | IN ( { select | expression [,...] } )
| [ NOT ] LIKE operand [ ESCAPE string ] | [ NOT ] LIKE operand [ ESCAPE string ]
...@@ -1557,6 +1558,9 @@ compare { { { ALL | ANY | SOME } ( select ) } | operand } ...@@ -1557,6 +1558,9 @@ compare { { { ALL | ANY | SOME } ( select ) } | operand }
"," ","
The right hand side of a condition. The right hand side of a condition.
The conditions IS [ NOT ] and IS [ NOT ] DISTINCT FROM are NULL-safe, meaning
NULL is considered the same as NULL, and the condition never evaluates to NULL.
When comparing with LIKE, the wildcards characters are _ (any one character) When comparing with LIKE, the wildcards characters are _ (any one character)
and % (any characters). The database uses an index when comparing with LIKE and % (any characters). The database uses an index when comparing with LIKE
except if the operand starts with a wildcard. To search for the characters % and except if the operand starts with a wildcard. To search for the characters % and
......
...@@ -1778,14 +1778,23 @@ public class Parser { ...@@ -1778,14 +1778,23 @@ public class Parser {
Expression b = readConcat(); Expression b = readConcat();
r = new CompareLike(database.getCompareMode(), r, b, null, true); r = new CompareLike(database.getCompareMode(), r, b, null, true);
} else if (readIf("IS")) { } else if (readIf("IS")) {
int type;
if (readIf("NOT")) { if (readIf("NOT")) {
type = Comparison.IS_NOT_NULL; if (readIf("NULL")) {
r = new Comparison(session, Comparison.IS_NOT_NULL, r, null);
} else if (readIf("DISTINCT")) {
read("FROM");
r = new Comparison(session, Comparison.EQUAL_NULL_SAFE, r, readConcat());
} else { } else {
type = Comparison.IS_NULL; r = new Comparison(session, Comparison.NOT_EQUAL_NULL_SAFE, r, readConcat());
}
} else if (readIf("NULL")) {
r = new Comparison(session, Comparison.IS_NULL, r, null);
} else if (readIf("DISTINCT")) {
read("FROM");
r = new Comparison(session, Comparison.NOT_EQUAL_NULL_SAFE, r, readConcat());
} else {
r = new Comparison(session, Comparison.EQUAL_NULL_SAFE, r, readConcat());
} }
read("NULL");
r = new Comparison(session, type, r, null);
} else if (readIf("IN")) { } else if (readIf("IN")) {
read("("); read("(");
if (readIf(")")) { if (readIf(")")) {
......
...@@ -24,11 +24,23 @@ import org.h2.value.ValueNull; ...@@ -24,11 +24,23 @@ import org.h2.value.ValueNull;
*/ */
public class Comparison extends Condition { public class Comparison extends Condition {
/**
* This is a flag meaning the comparison is null safe (meaning never returns
* NULL even if one operand is NULL). Only EQUAL and NOT_EQUAL are supported
* currently.
*/
public static final int NULL_SAFE = 16;
/** /**
* The comparison type meaning = as in ID=1. * The comparison type meaning = as in ID=1.
*/ */
public static final int EQUAL = 0; public static final int EQUAL = 0;
/**
* The comparison type meaning ID IS 1 (ID IS NOT DISTINCT FROM 1).
*/
public static final int EQUAL_NULL_SAFE = EQUAL | NULL_SAFE;
/** /**
* The comparison type meaning &gt;= as in ID&gt;=1. * The comparison type meaning &gt;= as in ID&gt;=1.
*/ */
...@@ -54,6 +66,11 @@ public class Comparison extends Condition { ...@@ -54,6 +66,11 @@ public class Comparison extends Condition {
*/ */
public static final int NOT_EQUAL = 5; public static final int NOT_EQUAL = 5;
/**
* The comparison type meaning ID IS NOT 1 (ID IS DISTINCT FROM 1).
*/
public static final int NOT_EQUAL_NULL_SAFE = NOT_EQUAL | NULL_SAFE;
/** /**
* The comparison type meaning IS NULL as in NAME IS NULL. * The comparison type meaning IS NULL as in NAME IS NULL.
*/ */
...@@ -100,6 +117,9 @@ public class Comparison extends Condition { ...@@ -100,6 +117,9 @@ public class Comparison extends Condition {
case EQUAL: case EQUAL:
sql = left.getSQL() + " = " + right.getSQL(); sql = left.getSQL() + " = " + right.getSQL();
break; break;
case EQUAL_NULL_SAFE:
sql = left.getSQL() + " IS " + right.getSQL();
break;
case BIGGER_EQUAL: case BIGGER_EQUAL:
sql = left.getSQL() + " >= " + right.getSQL(); sql = left.getSQL() + " >= " + right.getSQL();
break; break;
...@@ -115,6 +135,9 @@ public class Comparison extends Condition { ...@@ -115,6 +135,9 @@ public class Comparison extends Condition {
case NOT_EQUAL: case NOT_EQUAL:
sql = left.getSQL() + " <> " + right.getSQL(); sql = left.getSQL() + " <> " + right.getSQL();
break; break;
case NOT_EQUAL_NULL_SAFE:
sql = left.getSQL() + " IS NOT " + right.getSQL();
break;
case IS_NULL: case IS_NULL:
sql = left.getSQL() + " IS NULL"; sql = left.getSQL() + " IS NULL";
break; break;
...@@ -143,8 +166,10 @@ public class Comparison extends Condition { ...@@ -143,8 +166,10 @@ public class Comparison extends Condition {
if (right.isConstant()) { if (right.isConstant()) {
Value r = right.getValue(session); Value r = right.getValue(session);
if (r == ValueNull.INSTANCE) { if (r == ValueNull.INSTANCE) {
if ((compareType & NULL_SAFE) == 0) {
return ValueExpression.getNull(); return ValueExpression.getNull();
} }
}
} else if (right instanceof Parameter) { } else if (right instanceof Parameter) {
((Parameter) right).setColumn(((ExpressionColumn) left).getColumn()); ((Parameter) right).setColumn(((ExpressionColumn) left).getColumn());
} }
...@@ -161,8 +186,10 @@ public class Comparison extends Condition { ...@@ -161,8 +186,10 @@ public class Comparison extends Condition {
if (left == ValueExpression.getNull() || right == ValueExpression.getNull()) { if (left == ValueExpression.getNull() || right == ValueExpression.getNull()) {
// TODO NULL handling: maybe issue a warning when comparing with // TODO NULL handling: maybe issue a warning when comparing with
// a NULL constants // a NULL constants
if ((compareType & NULL_SAFE) == 0) {
return ValueExpression.getNull(); return ValueExpression.getNull();
} }
}
if (left.isConstant() && right.isConstant()) { if (left.isConstant() && right.isConstant()) {
return ValueExpression.get(getValue(session)); return ValueExpression.get(getValue(session));
} }
...@@ -187,12 +214,16 @@ public class Comparison extends Condition { ...@@ -187,12 +214,16 @@ public class Comparison extends Condition {
return ValueBoolean.get(result); return ValueBoolean.get(result);
} }
if (l == ValueNull.INSTANCE) { if (l == ValueNull.INSTANCE) {
if ((compareType & NULL_SAFE) == 0) {
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
} }
}
Value r = right.getValue(session); Value r = right.getValue(session);
if (r == ValueNull.INSTANCE) { if (r == ValueNull.INSTANCE) {
if ((compareType & NULL_SAFE) == 0) {
return ValueNull.INSTANCE; return ValueNull.INSTANCE;
} }
}
int dataType = Value.getHigherOrder(left.getType(), right.getType()); int dataType = Value.getHigherOrder(left.getType(), right.getType());
l = l.convertTo(dataType); l = l.convertTo(dataType);
r = r.convertTo(dataType); r = r.convertTo(dataType);
...@@ -214,9 +245,11 @@ public class Comparison extends Condition { ...@@ -214,9 +245,11 @@ public class Comparison extends Condition {
boolean result; boolean result;
switch (compareType) { switch (compareType) {
case EQUAL: case EQUAL:
case EQUAL_NULL_SAFE:
result = database.areEqual(l, r); result = database.areEqual(l, r);
break; break;
case NOT_EQUAL: case NOT_EQUAL:
case NOT_EQUAL_NULL_SAFE:
result = !database.areEqual(l, r); result = !database.areEqual(l, r);
break; break;
case BIGGER_EQUAL: case BIGGER_EQUAL:
...@@ -240,7 +273,9 @@ public class Comparison extends Condition { ...@@ -240,7 +273,9 @@ public class Comparison extends Condition {
private int getReversedCompareType(int type) { private int getReversedCompareType(int type) {
switch (compareType) { switch (compareType) {
case EQUAL: case EQUAL:
case EQUAL_NULL_SAFE:
case NOT_EQUAL: case NOT_EQUAL:
case NOT_EQUAL_NULL_SAFE:
return type; return type;
case BIGGER_EQUAL: case BIGGER_EQUAL:
return SMALLER_EQUAL; return SMALLER_EQUAL;
...@@ -259,8 +294,12 @@ public class Comparison extends Condition { ...@@ -259,8 +294,12 @@ public class Comparison extends Condition {
switch (compareType) { switch (compareType) {
case EQUAL: case EQUAL:
return NOT_EQUAL; return NOT_EQUAL;
case EQUAL_NULL_SAFE:
return NOT_EQUAL_NULL_SAFE;
case NOT_EQUAL: case NOT_EQUAL:
return EQUAL; return EQUAL;
case NOT_EQUAL_NULL_SAFE:
return EQUAL_NULL_SAFE;
case BIGGER_EQUAL: case BIGGER_EQUAL:
return SMALLER; return SMALLER;
case BIGGER: case BIGGER:
...@@ -296,7 +335,7 @@ public class Comparison extends Condition { ...@@ -296,7 +335,7 @@ public class Comparison extends Condition {
switch (compareType) { switch (compareType) {
case IS_NULL: case IS_NULL:
if (SysProperties.OPTIMIZE_IS_NULL) { if (SysProperties.OPTIMIZE_IS_NULL) {
filter.addIndexCondition(IndexCondition.get(Comparison.EQUAL, l, ValueExpression.getNull())); filter.addIndexCondition(IndexCondition.get(Comparison.EQUAL_NULL_SAFE, l, ValueExpression.getNull()));
} }
} }
} }
...@@ -336,9 +375,11 @@ public class Comparison extends Condition { ...@@ -336,9 +375,11 @@ public class Comparison extends Condition {
boolean addIndex; boolean addIndex;
switch (compareType) { switch (compareType) {
case NOT_EQUAL: case NOT_EQUAL:
case NOT_EQUAL_NULL_SAFE:
addIndex = false; addIndex = false;
break; break;
case EQUAL: case EQUAL:
case EQUAL_NULL_SAFE:
case BIGGER: case BIGGER:
case BIGGER_EQUAL: case BIGGER_EQUAL:
case SMALLER_EQUAL: case SMALLER_EQUAL:
......
...@@ -171,6 +171,9 @@ public class IndexCondition { ...@@ -171,6 +171,9 @@ public class IndexCondition {
case Comparison.EQUAL: case Comparison.EQUAL:
buff.append(" = "); buff.append(" = ");
break; break;
case Comparison.EQUAL_NULL_SAFE:
buff.append(" IS ");
break;
case Comparison.BIGGER_EQUAL: case Comparison.BIGGER_EQUAL:
buff.append(" >= "); buff.append(" >= ");
break; break;
...@@ -216,6 +219,7 @@ public class IndexCondition { ...@@ -216,6 +219,7 @@ public class IndexCondition {
case Comparison.FALSE: case Comparison.FALSE:
return ALWAYS_FALSE; return ALWAYS_FALSE;
case Comparison.EQUAL: case Comparison.EQUAL:
case Comparison.EQUAL_NULL_SAFE:
return EQUALITY; return EQUALITY;
case Comparison.IN_LIST: case Comparison.IN_LIST:
case Comparison.IN_QUERY: case Comparison.IN_QUERY:
...@@ -262,6 +266,7 @@ public class IndexCondition { ...@@ -262,6 +266,7 @@ public class IndexCondition {
public boolean isStart() { public boolean isStart() {
switch (compareType) { switch (compareType) {
case Comparison.EQUAL: case Comparison.EQUAL:
case Comparison.EQUAL_NULL_SAFE:
case Comparison.BIGGER_EQUAL: case Comparison.BIGGER_EQUAL:
case Comparison.BIGGER: case Comparison.BIGGER:
return true; return true;
...@@ -279,6 +284,7 @@ public class IndexCondition { ...@@ -279,6 +284,7 @@ public class IndexCondition {
public boolean isEnd() { public boolean isEnd() {
switch (compareType) { switch (compareType) {
case Comparison.EQUAL: case Comparison.EQUAL:
case Comparison.EQUAL_NULL_SAFE:
case Comparison.SMALLER_EQUAL: case Comparison.SMALLER_EQUAL:
case Comparison.SMALLER: case Comparison.SMALLER:
return true; return true;
......
...@@ -91,20 +91,30 @@ public class LinkedIndex extends BaseIndex { ...@@ -91,20 +91,30 @@ public class LinkedIndex extends BaseIndex {
buff.appendOnlyFirst(" WHERE "); buff.appendOnlyFirst(" WHERE ");
buff.appendExceptFirst(" AND "); buff.appendExceptFirst(" AND ");
Column col = table.getColumn(i); Column col = table.getColumn(i);
buff.append(col.getSQL()).append(">="); buff.append(col.getSQL());
if (v == ValueNull.INSTANCE) {
buff.append(" IS NULL");
} else {
buff.append(">=");
addParameter(buff, col); addParameter(buff, col);
} }
} }
}
for (int i = 0; last != null && i < last.getColumnCount(); i++) { for (int i = 0; last != null && i < last.getColumnCount(); i++) {
Value v = last.getValue(i); Value v = last.getValue(i);
if (v != null) { if (v != null) {
buff.appendOnlyFirst(" WHERE "); buff.appendOnlyFirst(" WHERE ");
buff.appendExceptFirst(" AND "); buff.appendExceptFirst(" AND ");
Column col = table.getColumn(i); Column col = table.getColumn(i);
buff.append(col.getSQL()).append("<="); buff.append(col.getSQL());
if (v == ValueNull.INSTANCE) {
buff.append(" IS NULL");
} else {
buff.append("<=");
addParameter(buff, col); addParameter(buff, col);
} }
} }
}
String sql = buff.toString(); String sql = buff.toString();
synchronized (link.getConnection()) { synchronized (link.getConnection()) {
try { try {
...@@ -112,14 +122,14 @@ public class LinkedIndex extends BaseIndex { ...@@ -112,14 +122,14 @@ public class LinkedIndex extends BaseIndex {
int j = 0; int j = 0;
for (int i = 0; first != null && i < first.getColumnCount(); i++) { for (int i = 0; first != null && i < first.getColumnCount(); i++) {
Value v = first.getValue(i); Value v = first.getValue(i);
if (v != null) { if (v != null && v != ValueNull.INSTANCE) {
v.set(prep, j + 1); v.set(prep, j + 1);
j++; j++;
} }
} }
for (int i = 0; last != null && i < last.getColumnCount(); i++) { for (int i = 0; last != null && i < last.getColumnCount(); i++) {
Value v = last.getValue(i); Value v = last.getValue(i);
if (v != null) { if (v != null && v != ValueNull.INSTANCE) {
v.set(prep, j + 1); v.set(prep, j + 1);
j++; j++;
} }
......
...@@ -123,7 +123,7 @@ public class ViewIndex extends BaseIndex { ...@@ -123,7 +123,7 @@ public class ViewIndex extends BaseIndex {
int nextParamIndex = q.getParameters().size() + view.getParameterOffset(); int nextParamIndex = q.getParameters().size() + view.getParameterOffset();
if ((mask & IndexCondition.EQUALITY) != 0) { if ((mask & IndexCondition.EQUALITY) != 0) {
Parameter param = new Parameter(nextParamIndex); Parameter param = new Parameter(nextParamIndex);
q.addGlobalCondition(param, idx, Comparison.EQUAL); q.addGlobalCondition(param, idx, Comparison.EQUAL_NULL_SAFE);
} else { } else {
if ((mask & IndexCondition.START) != 0) { if ((mask & IndexCondition.START) != 0) {
Parameter param = new Parameter(nextParamIndex); Parameter param = new Parameter(nextParamIndex);
...@@ -226,7 +226,7 @@ public class ViewIndex extends BaseIndex { ...@@ -226,7 +226,7 @@ public class ViewIndex extends BaseIndex {
int mask = masks[idx]; int mask = masks[idx];
if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) { if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) {
Parameter param = new Parameter(firstIndexParam + i); Parameter param = new Parameter(firstIndexParam + i);
q.addGlobalCondition(param, idx, Comparison.EQUAL); q.addGlobalCondition(param, idx, Comparison.EQUAL_NULL_SAFE);
i++; i++;
} else { } else {
if ((mask & IndexCondition.START) == IndexCondition.START) { if ((mask & IndexCondition.START) == IndexCondition.START) {
......
...@@ -519,6 +519,7 @@ Boolean value or condition." ...@@ -519,6 +519,7 @@ Boolean value or condition."
"Other Grammar","Condition Right Hand Side"," "Other Grammar","Condition Right Hand Side","
compare { { { ALL | ANY | SOME } ( select ) } | operand } compare { { { ALL | ANY | SOME } ( select ) } | operand }
| IS [ NOT ] NULL | IS [ NOT ] NULL
| IS [ NOT ] [ DISTINCT FROM ] operand
| BETWEEN operand AND operand | BETWEEN operand AND operand
| IN ( { select | expression [,...] } ) | IN ( { select | expression [,...] } )
| [ NOT ] LIKE operand [ ESCAPE string ] | [ NOT ] LIKE operand [ ESCAPE string ]
......
/*
* 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.synth;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import org.h2.test.TestBase;
import org.h2.util.New;
/**
* Tests random compare operations.
*/
public class TestRandomCompare extends TestBase {
private ArrayList<Statement> dbs = New.arrayList();
private int aliasId;
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase test = TestBase.createCaller().init();
test.config.traceTest = true;
test.test();
}
public void test() throws Exception {
deleteDb("randomCompare");
testCases();
testRandom();
deleteDb("randomCompare");
}
private void testRandom() throws Exception {
Connection conn = getConnection("randomCompare");
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
}
String shortest = null;
Throwable shortestEx = null;
/*
drop table test;
create table test(x0 int, x1 int);
create index idx_test_x0 on test(x0);
insert into test values(null, null);
insert into test values(null, 1);
insert into test values(null, 2);
insert into test values(1, null);
insert into test values(1, 1);
insert into test values(1, 2);
insert into test values(2, null);
insert into test values(2, 1);
insert into test values(2, 2);
*/
try {
execute("drop table test");
} catch (Exception e) {
// ignore
}
String sql = "create table test(x0 int, x1 int)";
trace(sql + ";");
execute(sql);
sql = "create index idx_test_x0 on test(x0)";
trace(sql + ";");
execute(sql);
for (int x0 = 0; x0 < 3; x0++) {
for (int x1 = 0; x1 < 3; x1++) {
sql = "insert into test values(" + (x0 == 0 ? "null" : x0) + ", " + (x1 == 0 ? "null" : x1) + ")";
trace(sql + ";");
execute(sql);
}
}
Random random = new Random(1);
for (int i = 0; i < 1000; i++) {
StringBuilder buff = new StringBuilder();
appendRandomCompare(random, buff);
sql = buff.toString();
try {
execute(sql);
} catch (Throwable e) {
if (e instanceof SQLException) {
trace(sql);
fail(sql);
// SQLException se = (SQLException) e;
// 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();
}
deleteDb("randomCompare");
}
private void appendRandomCompare(Random random, StringBuilder buff) {
buff.append("select * from ");
int alias = aliasId++;
if (random.nextBoolean()) {
buff.append("(");
appendRandomCompare(random, buff);
buff.append(")");
} else {
buff.append("test");
}
buff.append(" as t").append(alias);
if (random.nextInt(10) == 0) {
return;
}
buff.append(" where ");
int count = 1 + random.nextInt(3);
for (int i = 0; i < count; i++) {
if (i > 0) {
buff.append(random.nextBoolean() ? " or " : " and ");
}
if (random.nextInt(10) == 0) {
buff.append("not ");
}
appendRandomValue(random, buff);
switch (random.nextInt(8)) {
case 0:
buff.append("=");
appendRandomValue(random, buff);
break;
case 1:
buff.append("<");
appendRandomValue(random, buff);
break;
case 2:
buff.append(">");
appendRandomValue(random, buff);
break;
case 3:
buff.append("<=");
appendRandomValue(random, buff);
break;
case 4:
buff.append(">=");
appendRandomValue(random, buff);
break;
case 5:
buff.append("<>");
appendRandomValue(random, buff);
break;
case 6:
buff.append(" is distinct from ");
appendRandomValue(random, buff);
break;
case 7:
buff.append(" is not distinct from ");
appendRandomValue(random, buff);
break;
}
}
}
private void appendRandomValue(Random random, StringBuilder buff) {
switch (random.nextInt(7)) {
case 0:
buff.append("null");
break;
case 1:
buff.append(1);
break;
case 2:
buff.append(2);
break;
case 3:
buff.append(3);
break;
case 4:
buff.append(-1);
break;
case 5:
buff.append("x0");
break;
case 6:
buff.append("x1");
break;
}
}
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("randomCompare");
Statement stat = conn.createStatement();
ResultSet rs;
/*
create table test(x int);
insert into test values(null);
select * from (select x from test
union all select x from test) where x is null;
select * from (select x from test) where x is null;
*/
stat.execute("create table test(x int)");
stat.execute("insert into test values(null)");
rs = stat.executeQuery("select * from (select x from test union all select x from test) where x is null");
assertTrue(rs.next());
rs = stat.executeQuery("select * from (select x from test) where x is null");
assertTrue(rs.next());
rs = stat.executeQuery("select * from (select x from test union all select x from test) where x is null");
assertTrue(rs.next());
assertTrue(rs.next());
Connection conn2 = DriverManager.getConnection("jdbc:h2:mem:temp");
conn2.createStatement().execute("create table test(x int) as select null");
stat.execute("drop table test");
stat.execute("create linked table test(null, 'jdbc:h2:mem:temp', null, null, 'TEST')");
rs = stat.executeQuery("select * from (select x from test) where x is null");
assertTrue(rs.next());
rs = stat.executeQuery("select * from (select x from test union all select x from test) where x is null");
assertTrue(rs.next());
assertTrue(rs.next());
conn2.close();
conn.close();
deleteDb("randomCompare");
}
}
...@@ -3086,8 +3086,8 @@ inner join test2 on test1.id=test2.id left ...@@ -3086,8 +3086,8 @@ inner join test2 on test1.id=test2.id left
outer join test3 on test2.id=test3.id outer join test3 on test2.id=test3.id
where test3.id is null; where test3.id is null;
> PLAN > PLAN
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- > --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> SELECT TEST1.ID, TEST2.ID, TEST3.ID FROM PUBLIC.TEST1 /* PUBLIC.TEST1.tableScan */ INNER JOIN PUBLIC.TEST2 /* PUBLIC.PRIMARY_KEY_4C: ID = TEST1.ID AND ID = TEST1.ID */ ON 1=1 /* WHERE TEST1.ID = TEST2.ID */ LEFT OUTER JOIN PUBLIC.TEST3 /* PUBLIC.PRIMARY_KEY_4C0: ID = TEST2.ID AND ID = NULL */ ON TEST2.ID = TEST3.ID WHERE (TEST3.ID IS NULL) AND (TEST1.ID = TEST2.ID) > SELECT TEST1.ID, TEST2.ID, TEST3.ID FROM PUBLIC.TEST1 /* PUBLIC.TEST1.tableScan */ INNER JOIN PUBLIC.TEST2 /* PUBLIC.PRIMARY_KEY_4C: ID = TEST1.ID AND ID = TEST1.ID */ ON 1=1 /* WHERE TEST1.ID = TEST2.ID */ LEFT OUTER JOIN PUBLIC.TEST3 /* PUBLIC.PRIMARY_KEY_4C0: ID = TEST2.ID AND ID IS NULL */ ON TEST2.ID = TEST3.ID WHERE (TEST3.ID IS NULL) AND (TEST1.ID = TEST2.ID)
> rows: 1 > rows: 1
insert into test1 select x from system_range(2, 1000); insert into test1 select x from system_range(2, 1000);
...@@ -3106,8 +3106,8 @@ inner join test2 on test1.id=test2.id ...@@ -3106,8 +3106,8 @@ inner join test2 on test1.id=test2.id
left outer join test3 on test2.id=test3.id left outer join test3 on test2.id=test3.id
where test3.id is null; where test3.id is null;
> PLAN > PLAN
> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- > -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> SELECT TEST1.ID, TEST2.ID, TEST3.ID FROM PUBLIC.TEST2 /* PUBLIC.TEST2.tableScan */ LEFT OUTER JOIN PUBLIC.TEST3 /* PUBLIC.PRIMARY_KEY_4C0: ID = TEST2.ID AND ID = NULL */ ON TEST2.ID = TEST3.ID INNER JOIN PUBLIC.TEST1 /* PUBLIC.PRIMARY_KEY_4: ID = TEST2.ID */ ON 1=1 WHERE (TEST3.ID IS NULL) AND (TEST1.ID = TEST2.ID) > SELECT TEST1.ID, TEST2.ID, TEST3.ID FROM PUBLIC.TEST2 /* PUBLIC.TEST2.tableScan */ LEFT OUTER JOIN PUBLIC.TEST3 /* PUBLIC.PRIMARY_KEY_4C0: ID = TEST2.ID AND ID IS NULL */ ON TEST2.ID = TEST3.ID INNER JOIN PUBLIC.TEST1 /* PUBLIC.PRIMARY_KEY_4: ID = TEST2.ID */ ON 1=1 WHERE (TEST3.ID IS NULL) AND (TEST1.ID = TEST2.ID)
> rows: 1 > rows: 1
SELECT TEST1.ID, TEST2.ID, TEST3.ID SELECT TEST1.ID, TEST2.ID, TEST3.ID
...@@ -3282,8 +3282,8 @@ call /* remark * / * /* ** // end */ 1; ...@@ -3282,8 +3282,8 @@ call /* remark * / * /* ** // end */ 1;
> rows: 1 > rows: 1
call (select x from dual where x is null); call (select x from dual where x is null);
> SELECT X FROM SYSTEM_RANGE(1, 1) /* PUBLIC.RANGE_INDEX: X = NULL */ /* scanCount: 1 */ WHERE X IS NULL > SELECT X FROM SYSTEM_RANGE(1, 1) /* PUBLIC.RANGE_INDEX: X IS NULL */ /* scanCount: 1 */ WHERE X IS NULL
> ------------------------------------------------------------------------------------------------------ > -------------------------------------------------------------------------------------------------------
> null > null
> rows: 1 > rows: 1
...@@ -6062,8 +6062,8 @@ SELECT * FROM V_UNION WHERE ID=1; ...@@ -6062,8 +6062,8 @@ SELECT * FROM V_UNION WHERE ID=1;
EXPLAIN SELECT * FROM V_UNION WHERE ID=1; EXPLAIN SELECT * FROM V_UNION WHERE ID=1;
> PLAN > PLAN
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- > ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> SELECT V_UNION.ID, V_UNION.NAME, V_UNION.CLASS FROM PUBLIC.V_UNION /* (SELECT CHILDREN.ID, CHILDREN.NAME, CHILDREN.CLASS FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID = ?1 ++/ WHERE CHILDREN.ID = ?1) UNION ALL (SELECT CHILDREN.ID, CHILDREN.NAME, CHILDREN.CLASS FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID = ?1 ++/ WHERE CHILDREN.ID = ?1): ID = 1 */ WHERE ID = 1 > SELECT V_UNION.ID, V_UNION.NAME, V_UNION.CLASS FROM PUBLIC.V_UNION /* (SELECT CHILDREN.ID, CHILDREN.NAME, CHILDREN.CLASS FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID IS ?1 ++/ WHERE CHILDREN.ID IS ?1) UNION ALL (SELECT CHILDREN.ID, CHILDREN.NAME, CHILDREN.CLASS FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID IS ?1 ++/ WHERE CHILDREN.ID IS ?1): ID = 1 */ WHERE ID = 1
> rows: 1 > rows: 1
CREATE VIEW V_EXCEPT AS SELECT * FROM CHILDREN EXCEPT SELECT * FROM CHILDREN WHERE ID=2; CREATE VIEW V_EXCEPT AS SELECT * FROM CHILDREN EXCEPT SELECT * FROM CHILDREN WHERE ID=2;
...@@ -6077,8 +6077,8 @@ SELECT * FROM V_EXCEPT WHERE ID=1; ...@@ -6077,8 +6077,8 @@ SELECT * FROM V_EXCEPT WHERE ID=1;
EXPLAIN SELECT * FROM V_EXCEPT WHERE ID=1; EXPLAIN SELECT * FROM V_EXCEPT WHERE ID=1;
> PLAN > PLAN
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- > ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> SELECT V_EXCEPT.ID, V_EXCEPT.NAME, V_EXCEPT.CLASS FROM PUBLIC.V_EXCEPT /* (SELECT CHILDREN.ID, CHILDREN.NAME, CHILDREN.CLASS FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID = ?1 ++/ WHERE CHILDREN.ID = ?1) EXCEPT (SELECT CHILDREN.ID, CHILDREN.NAME, CHILDREN.CLASS FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID = 2 ++/ WHERE ID = 2): ID = 1 */ WHERE ID = 1 > SELECT V_EXCEPT.ID, V_EXCEPT.NAME, V_EXCEPT.CLASS FROM PUBLIC.V_EXCEPT /* (SELECT CHILDREN.ID, CHILDREN.NAME, CHILDREN.CLASS FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID IS ?1 ++/ WHERE CHILDREN.ID IS ?1) EXCEPT (SELECT CHILDREN.ID, CHILDREN.NAME, CHILDREN.CLASS FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID = 2 ++/ WHERE ID = 2): ID = 1 */ WHERE ID = 1
> rows: 1 > rows: 1
CREATE VIEW V_INTERSECT AS SELECT ID, NAME FROM CHILDREN INTERSECT SELECT * FROM CLASSES; CREATE VIEW V_INTERSECT AS SELECT ID, NAME FROM CHILDREN INTERSECT SELECT * FROM CLASSES;
...@@ -6091,8 +6091,8 @@ SELECT * FROM V_INTERSECT WHERE ID=1; ...@@ -6091,8 +6091,8 @@ SELECT * FROM V_INTERSECT WHERE ID=1;
EXPLAIN SELECT * FROM V_INTERSECT WHERE ID=1; EXPLAIN SELECT * FROM V_INTERSECT WHERE ID=1;
> PLAN > PLAN
> ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- > ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> SELECT V_INTERSECT.ID, V_INTERSECT.NAME FROM PUBLIC.V_INTERSECT /* (SELECT ID, NAME FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID = ?1 ++/ WHERE ID = ?1) INTERSECT (SELECT CLASSES.ID, CLASSES.NAME FROM PUBLIC.CLASSES /++ PUBLIC.PRIMARY_KEY_5: ID = ?1 ++/ WHERE CLASSES.ID = ?1): ID = 1 */ WHERE ID = 1 > SELECT V_INTERSECT.ID, V_INTERSECT.NAME FROM PUBLIC.V_INTERSECT /* (SELECT ID, NAME FROM PUBLIC.CHILDREN /++ PUBLIC.PRIMARY_KEY_9: ID IS ?1 ++/ WHERE ID IS ?1) INTERSECT (SELECT CLASSES.ID, CLASSES.NAME FROM PUBLIC.CLASSES /++ PUBLIC.PRIMARY_KEY_5: ID IS ?1 ++/ WHERE CLASSES.ID IS ?1): ID = 1 */ WHERE ID = 1
> rows: 1 > rows: 1
DROP VIEW V_UNION; DROP VIEW V_UNION;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论