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

Issue 609: the spatial index did not support NULL.

上级 c7233861
...@@ -20,7 +20,8 @@ Change Log ...@@ -20,7 +20,8 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>Granting a schema is now supported. <ul><li>Issue 609: the spatial index did not support NULL.
</li><li>Granting a schema is now supported.
</li><li>Linked tables did not work when a function-based index is present (Oracle). </li><li>Linked tables did not work when a function-based index is present (Oracle).
</li><li>Creating a user with a null password, salt, or hash threw a NullPointerException. </li><li>Creating a user with a null password, salt, or hash threw a NullPointerException.
</li><li>Foreign key: don't add a single column index if column </li><li>Foreign key: don't add a single column index if column
......
...@@ -10,6 +10,7 @@ import java.util.ArrayList; ...@@ -10,6 +10,7 @@ import java.util.ArrayList;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.WriteBuffer;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
import org.h2.util.New;
/** /**
* A spatial data type. This class supports up to 31 dimensions. Each dimension * A spatial data type. This class supports up to 31 dimensions. Each dimension
...@@ -138,6 +139,9 @@ public class SpatialDataType implements DataType { ...@@ -138,6 +139,9 @@ public class SpatialDataType implements DataType {
public boolean isOverlap(Object objA, Object objB) { public boolean isOverlap(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA; SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB; SpatialKey b = (SpatialKey) objB;
if (a.isNull() || b.isNull()) {
return false;
}
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
if (a.max(i) < b.min(i) || a.min(i) > b.max(i)) { if (a.max(i) < b.min(i) || a.min(i) > b.max(i)) {
return false; return false;
...@@ -153,8 +157,11 @@ public class SpatialDataType implements DataType { ...@@ -153,8 +157,11 @@ public class SpatialDataType implements DataType {
* @param add the value * @param add the value
*/ */
public void increaseBounds(Object bounds, Object add) { public void increaseBounds(Object bounds, Object add) {
SpatialKey b = (SpatialKey) bounds;
SpatialKey a = (SpatialKey) add; SpatialKey a = (SpatialKey) add;
SpatialKey b = (SpatialKey) bounds;
if (a.isNull() || b.isNull()) {
return;
}
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
b.setMin(i, Math.min(b.min(i), a.min(i))); b.setMin(i, Math.min(b.min(i), a.min(i)));
b.setMax(i, Math.max(b.max(i), a.max(i))); b.setMax(i, Math.max(b.max(i), a.max(i)));
...@@ -169,8 +176,11 @@ public class SpatialDataType implements DataType { ...@@ -169,8 +176,11 @@ public class SpatialDataType implements DataType {
* @return the area * @return the area
*/ */
public float getAreaIncrease(Object objA, Object objB) { public float getAreaIncrease(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB; SpatialKey b = (SpatialKey) objB;
SpatialKey a = (SpatialKey) objA;
if (a.isNull() || b.isNull()) {
return 0;
}
float min = a.min(0); float min = a.min(0);
float max = a.max(0); float max = a.max(0);
float areaOld = max - min; float areaOld = max - min;
...@@ -198,6 +208,11 @@ public class SpatialDataType implements DataType { ...@@ -198,6 +208,11 @@ public class SpatialDataType implements DataType {
float getCombinedArea(Object objA, Object objB) { float getCombinedArea(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA; SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB; SpatialKey b = (SpatialKey) objB;
if (a.isNull()) {
return getArea(b);
} else if (b.isNull()) {
return getArea(a);
}
float area = 1; float area = 1;
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
float min = Math.min(a.min(i), b.min(i)); float min = Math.min(a.min(i), b.min(i));
...@@ -207,6 +222,17 @@ public class SpatialDataType implements DataType { ...@@ -207,6 +222,17 @@ public class SpatialDataType implements DataType {
return area; return area;
} }
private float getArea(SpatialKey a) {
if (a.isNull()) {
return 0;
}
float area = 1;
for (int i = 0; i < dimensions; i++) {
area *= a.max(i) - a.min(i);
}
return area;
}
/** /**
* Check whether a contains b. * Check whether a contains b.
* *
...@@ -217,6 +243,9 @@ public class SpatialDataType implements DataType { ...@@ -217,6 +243,9 @@ public class SpatialDataType implements DataType {
public boolean contains(Object objA, Object objB) { public boolean contains(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA; SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB; SpatialKey b = (SpatialKey) objB;
if (a.isNull() || b.isNull()) {
return false;
}
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
if (a.min(i) > b.min(i) || a.max(i) < b.max(i)) { if (a.min(i) > b.min(i) || a.max(i) < b.max(i)) {
return false; return false;
...@@ -236,6 +265,9 @@ public class SpatialDataType implements DataType { ...@@ -236,6 +265,9 @@ public class SpatialDataType implements DataType {
public boolean isInside(Object objA, Object objB) { public boolean isInside(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA; SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB; SpatialKey b = (SpatialKey) objB;
if (a.isNull() || b.isNull()) {
return false;
}
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
if (a.min(i) <= b.min(i) || a.max(i) >= b.max(i)) { if (a.min(i) <= b.min(i) || a.max(i) >= b.max(i)) {
return false; return false;
...@@ -251,8 +283,11 @@ public class SpatialDataType implements DataType { ...@@ -251,8 +283,11 @@ public class SpatialDataType implements DataType {
* @return the bounding box * @return the bounding box
*/ */
Object createBoundingBox(Object objA) { Object createBoundingBox(Object objA) {
float[] minMax = new float[dimensions * 2];
SpatialKey a = (SpatialKey) objA; SpatialKey a = (SpatialKey) objA;
if (a.isNull()) {
return a;
}
float[] minMax = new float[dimensions * 2];
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
minMax[i + i] = a.min(i); minMax[i + i] = a.min(i);
minMax[i + i + 1] = a.max(i); minMax[i + i + 1] = a.max(i);
...@@ -269,6 +304,10 @@ public class SpatialDataType implements DataType { ...@@ -269,6 +304,10 @@ public class SpatialDataType implements DataType {
* @return the indexes of the extremes * @return the indexes of the extremes
*/ */
public int[] getExtremes(ArrayList<Object> list) { public int[] getExtremes(ArrayList<Object> list) {
list = getNotNull(list);
if (list.size() == 0) {
return null;
}
SpatialKey bounds = (SpatialKey) createBoundingBox(list.get(0)); SpatialKey bounds = (SpatialKey) createBoundingBox(list.get(0));
SpatialKey boundsInner = (SpatialKey) createBoundingBox(bounds); SpatialKey boundsInner = (SpatialKey) createBoundingBox(bounds);
for (int i = 0; i < dimensions; i++) { for (int i = 0; i < dimensions; i++) {
...@@ -313,6 +352,27 @@ public class SpatialDataType implements DataType { ...@@ -313,6 +352,27 @@ public class SpatialDataType implements DataType {
return new int[] { firstIndex, lastIndex }; return new int[] { firstIndex, lastIndex };
} }
ArrayList<Object> getNotNull(ArrayList<Object> list) {
ArrayList<Object> result = null;
for (Object o : list) {
SpatialKey a = (SpatialKey) o;
if (a.isNull()) {
result = New.arrayList();
break;
}
}
if (result == null) {
return list;
}
for (Object o : list) {
SpatialKey a = (SpatialKey) o;
if (!a.isNull()) {
result.add(a);
}
}
return result;
}
private void increaseMaxInnerBounds(Object bounds, Object add) { private void increaseMaxInnerBounds(Object bounds, Object add) {
SpatialKey b = (SpatialKey) bounds; SpatialKey b = (SpatialKey) bounds;
SpatialKey a = (SpatialKey) add; SpatialKey a = (SpatialKey) add;
......
...@@ -91,6 +91,7 @@ public class TestSpatial extends TestBase { ...@@ -91,6 +91,7 @@ public class TestSpatial extends TestBase {
testExplainSpatialIndexWithPk(); testExplainSpatialIndexWithPk();
testNullableGeometry(); testNullableGeometry();
testNullableGeometryDelete(); testNullableGeometryDelete();
testNullableGeometryInsert();
testNullableGeometryUpdate(); testNullableGeometryUpdate();
} }
...@@ -204,9 +205,11 @@ public class TestSpatial extends TestBase { ...@@ -204,9 +205,11 @@ public class TestSpatial extends TestBase {
"(id int primary key, poly geometry)"); "(id int primary key, poly geometry)");
stat.execute("insert into test values(1, " + stat.execute("insert into test values(1, " +
"'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); "'POLYGON ((1 1, 1 2, 2 2, 1 1))')");
stat.execute("insert into test values(2, " + stat.execute("insert into test values(2,null)");
"'POLYGON ((3 1, 3 2, 4 2, 3 1))')");
stat.execute("insert into test values(3, " + stat.execute("insert into test values(3, " +
"'POLYGON ((3 1, 3 2, 4 2, 3 1))')");
stat.execute("insert into test values(4,null)");
stat.execute("insert into test values(5, " +
"'POLYGON ((1 3, 1 4, 2 4, 1 3))')"); "'POLYGON ((1 3, 1 4, 2 4, 1 3))')");
stat.execute("create spatial index on test(poly)"); stat.execute("create spatial index on test(poly)");
...@@ -260,18 +263,20 @@ public class TestSpatial extends TestBase { ...@@ -260,18 +263,20 @@ public class TestSpatial extends TestBase {
"(id int primary key, poly geometry)"); "(id int primary key, poly geometry)");
stat.execute("insert into test values(1, " + stat.execute("insert into test values(1, " +
"'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); "'POLYGON ((1 1, 1 2, 2 2, 1 1))')");
stat.execute("insert into test values(2, " + stat.execute("insert into test values(2,null)");
"'POLYGON ((3 1, 3 2, 4 2, 3 1))')");
stat.execute("insert into test values(3, " + stat.execute("insert into test values(3, " +
"'POLYGON ((3 1, 3 2, 4 2, 3 1))')");
stat.execute("insert into test values(4,null)");
stat.execute("insert into test values(5, " +
"'POLYGON ((1 3, 1 4, 2 4, 1 3))')"); "'POLYGON ((1 3, 1 4, 2 4, 1 3))')");
ResultSet rs = stat.executeQuery( ResultSet rs = stat.executeQuery(
"select * from test " + "select * from test " +
"where NOT poly && 'POINT (1.5 1.5)'::Geometry"); "where NOT poly && 'POINT (1.5 1.5)'::Geometry");
assertTrue(rs.next()); assertTrue(rs.next());
assertEquals(2, rs.getInt("id"));
assertTrue(rs.next());
assertEquals(3, rs.getInt("id")); assertEquals(3, rs.getInt("id"));
assertTrue(rs.next());
assertEquals(5, rs.getInt("id"));
assertFalse(rs.next()); assertFalse(rs.next());
stat.execute("drop table test"); stat.execute("drop table test");
} finally { } finally {
...@@ -294,6 +299,10 @@ public class TestSpatial extends TestBase { ...@@ -294,6 +299,10 @@ public class TestSpatial extends TestBase {
"'POLYGON ((90 9, 190 9, 190 -91, 90 -91, 90 9))')"); "'POLYGON ((90 9, 190 9, 190 -91, 90 -91, 90 9))')");
stat.execute("insert into area values(6, " + stat.execute("insert into area values(6, " +
"'POLYGON ((190 9, 290 9, 290 -91, 190 -91, 190 9))')"); "'POLYGON ((190 9, 290 9, 290 -91, 190 -91, 190 9))')");
stat.execute("insert into area values(7,null)");
stat.execute("insert into area values(8,null)");
stat.execute("create table roads(idRoad int primary key, the_geom geometry)"); stat.execute("create table roads(idRoad int primary key, the_geom geometry)");
stat.execute("create spatial index on roads(the_geom)"); stat.execute("create spatial index on roads(the_geom)");
stat.execute("insert into roads values(1, " + stat.execute("insert into roads values(1, " +
...@@ -317,6 +326,9 @@ public class TestSpatial extends TestBase { ...@@ -317,6 +326,9 @@ public class TestSpatial extends TestBase {
stat.execute("insert into roads values(7, " + stat.execute("insert into roads values(7, " +
"'LINESTRING (60.321361058601155 -13.099243856332663, " + "'LINESTRING (60.321361058601155 -13.099243856332663, " +
"149.24385633270325 5.955576559546344)')"); "149.24385633270325 5.955576559546344)')");
stat.execute("insert into roads values(8, null)");
stat.execute("insert into roads values(9, null)");
} }
private void testSpatialIndexQueryMultipleTable() throws SQLException { private void testSpatialIndexQueryMultipleTable() throws SQLException {
...@@ -369,6 +381,7 @@ public class TestSpatial extends TestBase { ...@@ -369,6 +381,7 @@ public class TestSpatial extends TestBase {
createTestTable(stat); createTestTable(stat);
Savepoint sp = conn.setSavepoint(); Savepoint sp = conn.setSavepoint();
// Remove a row but do not commit // Remove a row but do not commit
stat.execute("delete from roads where idRoad=9");
stat.execute("delete from roads where idRoad=7"); stat.execute("delete from roads where idRoad=7");
// Check if index is updated // Check if index is updated
ResultSet rs = stat.executeQuery( ResultSet rs = stat.executeQuery(
...@@ -416,6 +429,7 @@ public class TestSpatial extends TestBase { ...@@ -416,6 +429,7 @@ public class TestSpatial extends TestBase {
stat.execute("create memory table test(id int primary key, polygon geometry)"); stat.execute("create memory table test(id int primary key, polygon geometry)");
stat.execute("create spatial index idx_test_polygon on test(polygon)"); stat.execute("create spatial index idx_test_polygon on test(polygon)");
stat.execute("insert into test values(1, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); stat.execute("insert into test values(1, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')");
stat.execute("insert into test values(2, null)");
ResultSet rs; ResultSet rs;
// an query that can not possibly return a result // an query that can not possibly return a result
...@@ -707,7 +721,7 @@ public class TestSpatial extends TestBase { ...@@ -707,7 +721,7 @@ public class TestSpatial extends TestBase {
st.execute("CREATE AGGREGATE TABLE_ENVELOPE FOR \""+ st.execute("CREATE AGGREGATE TABLE_ENVELOPE FOR \""+
TableEnvelope.class.getName()+"\""); TableEnvelope.class.getName()+"\"");
st.execute("CREATE TABLE test(the_geom GEOMETRY)"); st.execute("CREATE TABLE test(the_geom GEOMETRY)");
st.execute("INSERT INTO test VALUES ('POINT(1 1)'), ('POINT(10 5)')"); st.execute("INSERT INTO test VALUES ('POINT(1 1)'), (null), (null), ('POINT(10 5)')");
ResultSet rs = st.executeQuery("select TABLE_ENVELOPE(the_geom) from test"); ResultSet rs = st.executeQuery("select TABLE_ENVELOPE(the_geom) from test");
assertEquals("geometry", rs.getMetaData(). assertEquals("geometry", rs.getMetaData().
getColumnTypeName(1).toLowerCase()); getColumnTypeName(1).toLowerCase());
...@@ -769,6 +783,7 @@ public class TestSpatial extends TestBase { ...@@ -769,6 +783,7 @@ public class TestSpatial extends TestBase {
stat.execute("drop view if exists test_view"); stat.execute("drop view if exists test_view");
stat.execute("create table test(id int primary key, poly geometry)"); stat.execute("create table test(id int primary key, poly geometry)");
stat.execute("insert into test values(1, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); stat.execute("insert into test values(1, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')");
stat.execute("insert into test values(4, null)");
stat.execute("insert into test values(2, 'POLYGON ((3 1, 3 2, 4 2, 3 1))')"); stat.execute("insert into test values(2, 'POLYGON ((3 1, 3 2, 4 2, 3 1))')");
stat.execute("insert into test values(3, 'POLYGON ((1 3, 1 4, 2 4, 1 3))')"); stat.execute("insert into test values(3, 'POLYGON ((1 3, 1 4, 2 4, 1 3))')");
stat.execute("create view test_view as select * from test"); stat.execute("create view test_view as select * from test");
...@@ -925,10 +940,45 @@ public class TestSpatial extends TestBase { ...@@ -925,10 +940,45 @@ public class TestSpatial extends TestBase {
stat.execute("insert into test values(2, null)"); stat.execute("insert into test values(2, null)");
stat.execute("delete from test where the_geom is null"); stat.execute("delete from test where the_geom is null");
stat.execute("insert into test values(1, null)"); stat.execute("insert into test values(1, null)");
stat.execute("insert into test values(2, null)");
stat.execute("insert into test values(3, 'POLYGON ((1000 2000, 1000 3000, 2000 3000, 1000 2000))')");
stat.execute("insert into test values(4, null)");
stat.execute("insert into test values(5, null)");
stat.execute("insert into test values(6, 'POLYGON ((1000 3000, 1000 4000, 2000 4000, 1000 3000))')");
ResultSet rs = stat.executeQuery("select * from test"); ResultSet rs = stat.executeQuery("select * from test");
assertTrue(rs.next()); int count = 0;
assertEquals(1, rs.getInt(1)); while (rs.next()) {
count++;
int id = rs.getInt(1);
if (id == 3 || id == 6) {
assertTrue(rs.getObject(2) != null);
} else {
assertNull(rs.getObject(2));
}
}
assertEquals(6, count);
rs = stat.executeQuery("select * from test where the_geom is null");
count = 0;
while (rs.next()) {
count++;
assertNull(rs.getObject(2)); assertNull(rs.getObject(2));
}
assertEquals(4, count);
rs = stat.executeQuery("select * from test where the_geom is not null");
count = 0;
while (rs.next()) {
count++;
assertTrue(rs.getObject(2) != null);
}
assertEquals(2, count);
rs = stat.executeQuery(
"select * from test " +
"where intersects(the_geom, 'POLYGON ((1000 1000, 1000 2000, 2000 2000, 1000 1000))')");
conn.close(); conn.close();
if (!config.memory) { if (!config.memory) {
conn = getConnection(url); conn = getConnection(url);
...@@ -952,10 +1002,10 @@ public class TestSpatial extends TestBase { ...@@ -952,10 +1002,10 @@ public class TestSpatial extends TestBase {
stat.execute("insert into test values(1, null)"); stat.execute("insert into test values(1, null)");
stat.execute("insert into test values(2, null)"); stat.execute("insert into test values(2, null)");
stat.execute("insert into test values(3, null)"); stat.execute("insert into test values(3, null)");
ResultSet rs = stat.executeQuery("select * from test"); ResultSet rs = stat.executeQuery("select * from test order by id");
assertTrue(rs.next()); while (rs.next()) {
assertEquals(1, rs.getInt(1));
assertNull(rs.getObject(2)); assertNull(rs.getObject(2));
}
stat.execute("delete from test where id = 1"); stat.execute("delete from test where id = 1");
stat.execute("delete from test where id = 2"); stat.execute("delete from test where id = 2");
stat.execute("delete from test where id = 3"); stat.execute("delete from test where id = 3");
...@@ -969,6 +1019,24 @@ public class TestSpatial extends TestBase { ...@@ -969,6 +1019,24 @@ public class TestSpatial extends TestBase {
deleteDb("spatial"); deleteDb("spatial");
} }
private void testNullableGeometryInsert() throws SQLException {
deleteDb("spatial");
Connection conn = getConnection(url);
Statement stat = conn.createStatement();
stat.execute("create memory table test"
+ "(id identity, the_geom geometry)");
stat.execute("create spatial index on test(the_geom)");
for (int i = 0; i < 1000; i++) {
stat.execute("insert into test values(null, null)");
}
ResultSet rs = stat.executeQuery("select * from test");
while (rs.next()) {
assertNull(rs.getObject(2));
}
conn.close();
deleteDb("spatial");
}
private void testNullableGeometryUpdate() throws SQLException { private void testNullableGeometryUpdate() throws SQLException {
deleteDb("spatial"); deleteDb("spatial");
Connection conn = getConnection(url); Connection conn = getConnection(url);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论