提交 0fd0134f authored 作者: Thomas Mueller's avatar Thomas Mueller

Spatial index (work in progress)

上级 d23ee071
...@@ -18,7 +18,8 @@ Change Log ...@@ -18,7 +18,8 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>Issue 467: OSGi Class Loader (ability to create reference to class <ul><li>Improved spatial index and data type.
</li><li>Issue 467: OSGi Class Loader (ability to create reference to class
in other ClassLoader, for example in another OSGi bundle). in other ClassLoader, for example in another OSGi bundle).
</li></ul> </li></ul>
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
* (http://h2database.com/html/license.html). * (http://h2database.com/html/license.html).
* Initial Developer: H2 Group * Initial Developer: H2 Group
* *
* N. Fortin, Atelier SIG - IRSTV CNRS 2488: * Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
* Support for the operator "&&" as an alias for SPATIAL_INTERSECTS. * Support for the operator "&&" as an alias for SPATIAL_INTERSECTS
*/ */
package org.h2.command; package org.h2.command;
...@@ -149,6 +149,10 @@ import org.h2.value.ValueTimestamp; ...@@ -149,6 +149,10 @@ import org.h2.value.ValueTimestamp;
/** /**
* The parser is used to convert a SQL statement string to an command object. * The parser is used to convert a SQL statement string to an command object.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class Parser { public class Parser {
......
...@@ -22,6 +22,10 @@ import org.h2.value.ValueNull; ...@@ -22,6 +22,10 @@ import org.h2.value.ValueNull;
/** /**
* Example comparison expressions are ID=1, NAME=NAME, NAME IS NULL. * Example comparison expressions are ID=1, NAME=NAME, NAME IS NULL.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class Comparison extends Condition { public class Comparison extends Condition {
...@@ -102,7 +106,7 @@ public class Comparison extends Condition { ...@@ -102,7 +106,7 @@ public class Comparison extends Condition {
/** /**
* This is a comparison type that is only used for spatial index * This is a comparison type that is only used for spatial index
* conditions. * conditions (operator "&&").
*/ */
public static final int SPATIAL_INTERSECTS = 11; public static final int SPATIAL_INTERSECTS = 11;
...@@ -161,6 +165,8 @@ public class Comparison extends Condition { ...@@ -161,6 +165,8 @@ public class Comparison extends Condition {
return "<>"; return "<>";
case NOT_EQUAL_NULL_SAFE: case NOT_EQUAL_NULL_SAFE:
return "IS NOT"; return "IS NOT";
case SPATIAL_INTERSECTS:
return "&&";
default: default:
throw DbException.throwInternalError("compareType=" + compareType); throw DbException.throwInternalError("compareType=" + compareType);
} }
...@@ -285,7 +291,7 @@ public class Comparison extends Condition { ...@@ -285,7 +291,7 @@ public class Comparison extends Condition {
case SPATIAL_INTERSECTS: { case SPATIAL_INTERSECTS: {
ValueGeometry lg = (ValueGeometry) l.convertTo(Value.GEOMETRY); ValueGeometry lg = (ValueGeometry) l.convertTo(Value.GEOMETRY);
ValueGeometry rg = (ValueGeometry) r.convertTo(Value.GEOMETRY); ValueGeometry rg = (ValueGeometry) r.convertTo(Value.GEOMETRY);
result = lg.intersects(rg); result = lg.intersectsBoundingBox(rg);
break; break;
} }
default: default:
...@@ -300,6 +306,7 @@ public class Comparison extends Condition { ...@@ -300,6 +306,7 @@ public class Comparison extends Condition {
case EQUAL_NULL_SAFE: case EQUAL_NULL_SAFE:
case NOT_EQUAL: case NOT_EQUAL:
case NOT_EQUAL_NULL_SAFE: case NOT_EQUAL_NULL_SAFE:
case SPATIAL_INTERSECTS:
return type; return type;
case BIGGER_EQUAL: case BIGGER_EQUAL:
return SMALLER_EQUAL; return SMALLER_EQUAL;
...@@ -314,6 +321,15 @@ public class Comparison extends Condition { ...@@ -314,6 +321,15 @@ public class Comparison extends Condition {
} }
} }
@Override
public Expression getNotIfPossible(Session session) {
if (compareType == SPATIAL_INTERSECTS) {
return null;
}
int type = getNotCompareType();
return new Comparison(session, type, left, right);
}
private int getNotCompareType() { private int getNotCompareType() {
switch (compareType) { switch (compareType) {
case EQUAL: case EQUAL:
...@@ -341,12 +357,6 @@ public class Comparison extends Condition { ...@@ -341,12 +357,6 @@ public class Comparison extends Condition {
} }
} }
@Override
public Expression getNotIfPossible(Session session) {
int type = getNotCompareType();
return new Comparison(session, type, left, right);
}
@Override @Override
public void createIndexConditions(Session session, TableFilter filter) { public void createIndexConditions(Session session, TableFilter filter) {
ExpressionColumn l = null; ExpressionColumn l = null;
......
...@@ -29,6 +29,10 @@ import org.h2.value.Value; ...@@ -29,6 +29,10 @@ import org.h2.value.Value;
* A index condition object is made for each condition that can potentially use * A index condition object is made for each condition that can potentially use
* an index. This class does not extend expression, but in general there is one * an index. This class does not extend expression, but in general there is one
* expression that maps to each index condition. * expression that maps to each index condition.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class IndexCondition { public class IndexCondition {
...@@ -210,6 +214,9 @@ public class IndexCondition { ...@@ -210,6 +214,9 @@ public class IndexCondition {
buff.append(expressionQuery.getPlanSQL()); buff.append(expressionQuery.getPlanSQL());
buff.append(')'); buff.append(')');
break; break;
case Comparison.SPATIAL_INTERSECTS:
buff.append(" && ");
break;
default: default:
DbException.throwInternalError("type="+compareType); DbException.throwInternalError("type="+compareType);
} }
......
...@@ -26,6 +26,10 @@ import org.h2.value.ValueNull; ...@@ -26,6 +26,10 @@ import org.h2.value.ValueNull;
/** /**
* The filter used to walk through an index. This class supports IN(..) * The filter used to walk through an index. This class supports IN(..)
* and IN(SELECT ...) optimizations. * and IN(SELECT ...) optimizations.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class IndexCursor implements Cursor { public class IndexCursor implements Cursor {
...@@ -125,7 +129,7 @@ public class IndexCursor implements Cursor { ...@@ -125,7 +129,7 @@ public class IndexCursor implements Cursor {
end = getSearchRow(end, columnId, v, false); end = getSearchRow(end, columnId, v, false);
} }
if (isIntersects) { if (isIntersects) {
intersects = getSpatialSearchRow(intersects, columnId, v, true); intersects = getSpatialSearchRow(intersects, columnId, v);
} }
if (isStart || isEnd) { if (isStart || isEnd) {
// an X=? condition will produce less rows than // an X=? condition will produce less rows than
...@@ -148,8 +152,8 @@ public class IndexCursor implements Cursor { ...@@ -148,8 +152,8 @@ public class IndexCursor implements Cursor {
return; return;
} }
if (!alwaysFalse) { if (!alwaysFalse) {
if (intersects != null && index instanceof SpatialTreeIndex) { if (intersects != null && index instanceof SpatialIndex) {
cursor = ((SpatialTreeIndex) index).findByGeometry(tableFilter, cursor = ((SpatialIndex) index).findByGeometry(tableFilter,
intersects); intersects);
} else { } else {
cursor = index.find(tableFilter, start, end); cursor = index.find(tableFilter, start, end);
...@@ -174,16 +178,13 @@ public class IndexCursor implements Cursor { ...@@ -174,16 +178,13 @@ public class IndexCursor implements Cursor {
return idxCol == null || idxCol.column == column; return idxCol == null || idxCol.column == column;
} }
private SearchRow getSpatialSearchRow(SearchRow row, int columnId, Value v, boolean isIntersects) { private SearchRow getSpatialSearchRow(SearchRow row, int columnId, Value v) {
if (row == null) { if (row == null) {
row = table.getTemplateRow(); row = table.getTemplateRow();
} else { } else if (row.getValue(columnId) != null) {
ValueGeometry vg = (ValueGeometry) row.getValue(columnId); // the intersection of the two envelopes
if (isIntersects) { ValueGeometry vg = (ValueGeometry) row.getValue(columnId).convertTo(Value.GEOMETRY);
v = ((ValueGeometry) v).intersection(vg); v = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getEnvelopeIntersection(vg);
} else {
v = ((ValueGeometry) v).union(vg);
}
} }
if (columnId < 0) { if (columnId < 0) {
row.setKey(v.getLong()); row.setKey(v.getLong());
......
...@@ -5,36 +5,65 @@ ...@@ -5,36 +5,65 @@
*/ */
package org.h2.index; package org.h2.index;
import java.util.List; import java.util.Iterator;
import org.h2.engine.Constants;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.db.MVTableEngine;
import org.h2.mvstore.rtree.MVRTreeMap;
import org.h2.mvstore.rtree.SpatialKey;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn; import org.h2.table.IndexColumn;
import org.h2.table.RegularTable; import org.h2.table.RegularTable;
import org.h2.table.TableFilter; import org.h2.table.TableFilter;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueGeometry; import org.h2.value.ValueGeometry;
import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.index.quadtree.Quadtree;
/** /**
* This is an in-memory index based on a R-Tree. * This is an index based on a MVR-TreeMap.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
private Quadtree root; private static final String MAP_PREFIX = "RTREE_";
private final MVRTreeMap<Long> treeMap;
private final MVStore store;
private final RegularTable tableData; private final RegularTable tableData;
private long rowCount;
private boolean closed; private boolean closed;
private boolean needRebuild;
private boolean persistent;
public SpatialTreeIndex(RegularTable table, int id, String indexName, IndexColumn[] columns, IndexType indexType) { /**
* Constructor.
* @param table Table instance
* @param id Index Id
* @param indexName Index name
* @param columns Indexed columns (only one geometry column allowed)
* @param indexType Index type (only spatial index)
* @param persistent Persistent, can be used in-memory or stored in a file.
*/
public SpatialTreeIndex(RegularTable table, int id, String indexName,
IndexColumn[] columns, IndexType indexType, boolean persistent,
boolean create, Session session) {
if (indexType.isUnique()) { if (indexType.isUnique()) {
throw DbException.getUnsupportedException("not unique"); throw DbException.getUnsupportedException("not unique");
} }
if (!persistent && !create) {
throw DbException.getUnsupportedException("Non persistent index called with create==false");
}
if (columns.length > 1) { if (columns.length > 1) {
throw DbException.getUnsupportedException("can only do one column"); throw DbException.getUnsupportedException("can only do one column");
} }
...@@ -47,8 +76,9 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -47,8 +76,9 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
if ((columns[0].sortType & SortOrder.NULLS_LAST) != 0) { if ((columns[0].sortType & SortOrder.NULLS_LAST) != 0) {
throw DbException.getUnsupportedException("cannot do nulls last"); throw DbException.getUnsupportedException("cannot do nulls last");
} }
initBaseIndex(table, id, indexName, columns, indexType); initBaseIndex(table, id, indexName, columns, indexType);
this.needRebuild = create;
this.persistent = persistent;
tableData = table; tableData = table;
if (!database.isStarting()) { if (!database.isStarting()) {
if (columns[0].column.getType() != Value.GEOMETRY) { if (columns[0].column.getType() != Value.GEOMETRY) {
...@@ -56,13 +86,34 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -56,13 +86,34 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
+ columns[0].column.getCreateSQL()); + columns[0].column.getCreateSQL());
} }
} }
if (!persistent) {
root = new Quadtree(); // Index in memory
store = MVStore.open(null);
treeMap = store.openMap("spatialIndex",
new MVRTreeMap.Builder<Long>());
} else {
if (id < 0) {
throw DbException.getUnsupportedException("Persistent index with id<0");
}
MVTableEngine.init(session.getDatabase());
store = session.getDatabase().getMvStore().getStore();
// Called after CREATE SPATIAL INDEX or
// by PageStore.addMeta
treeMap = store.openMap(MAP_PREFIX + getId(),
new MVRTreeMap.Builder<Long>());
if (treeMap.isEmpty()) {
needRebuild = true;
}
}
} }
@Override @Override
public void close(Session session) { public void close(Session session) {
root = null; if (persistent) {
store.store();
} else {
store.close();
}
closed = true; closed = true;
} }
...@@ -71,14 +122,16 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -71,14 +122,16 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
if (closed) { if (closed) {
throw DbException.throwInternalError(); throw DbException.throwInternalError();
} }
root.insert(getEnvelope(row), row); treeMap.add(getEnvelope(row), row.getKey());
rowCount++;
} }
private Envelope getEnvelope(SearchRow row) { private SpatialKey getEnvelope(SearchRow row) {
Value v = row.getValue(columnIds[0]); Value v = row.getValue(columnIds[0]);
Geometry g = ((ValueGeometry) v).getGeometry(); Geometry g = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getGeometry();
return g.getEnvelopeInternal(); Envelope env = g.getEnvelopeInternal();
return new SpatialKey(row.getKey(),
(float) env.getMinX(), (float) env.getMaxX(),
(float) env.getMinY(), (float) env.getMaxY());
} }
@Override @Override
...@@ -86,45 +139,49 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -86,45 +139,49 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
if (closed) { if (closed) {
throw DbException.throwInternalError(); throw DbException.throwInternalError();
} }
if (!root.remove(getEnvelope(row), row)) { if (!treeMap.remove(getEnvelope(row), row.getKey())) {
throw DbException.throwInternalError("row not found"); throw DbException.throwInternalError("row not found");
} }
rowCount--;
} }
@Override @Override
public Cursor find(TableFilter filter, SearchRow first, SearchRow last) { public Cursor find(TableFilter filter, SearchRow first, SearchRow last) {
return find(); return find(filter.getSession());
} }
@Override @Override
public Cursor find(Session session, SearchRow first, SearchRow last) { public Cursor find(Session session, SearchRow first, SearchRow last) {
return find(); return find(session);
} }
@SuppressWarnings("unchecked") private Cursor find(Session session) {
private Cursor find() { return new SpatialCursor(treeMap.keySet().iterator(), tableData, session);
// TODO use an external iterator,
// but let's see if we can get it working first
// TODO in the context of a spatial index,
// a query that uses ">" or "<" has no real meaning, so for now just ignore
// it and return all rows
List<Row> list = root.queryAll();
return new ListCursor(list, true /*first*/);
} }
@SuppressWarnings("unchecked")
@Override @Override
public Cursor findByGeometry(TableFilter filter, SearchRow intersection) { public Cursor findByGeometry(TableFilter filter, SearchRow intersection) {
// TODO use an external iterator, if (intersection == null) {
// but let's see if we can get it working first return find(filter.getSession());
List<Row> list;
if (intersection != null) {
list = root.query(getEnvelope(intersection));
} else {
list = root.queryAll();
} }
return new ListCursor(list, true/*first*/); return new SpatialCursor(treeMap.findIntersectingKeys(getEnvelope(intersection)), tableData, filter.getSession());
}
@Override
protected long getCostRangeIndex(int[] masks, long rowCount, SortOrder sortOrder) {
rowCount += Constants.COST_ROW_OFFSET;
long cost = rowCount;
long rows = rowCount;
if (masks == null) {
return cost;
}
for (Column column : columns) {
int index = column.getColumnId();
int mask = masks[index];
if ((mask & IndexCondition.SPATIAL_INTERSECTS) != 0) {
cost = 3 + rows / 4;
}
}
return cost;
} }
@Override @Override
...@@ -134,13 +191,14 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -134,13 +191,14 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
@Override @Override
public void remove(Session session) { public void remove(Session session) {
truncate(session); if (!treeMap.isClosed()) {
treeMap.removeMap();
}
} }
@Override @Override
public void truncate(Session session) { public void truncate(Session session) {
root = null; treeMap.clear();
rowCount = 0;
} }
@Override @Override
...@@ -150,7 +208,7 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -150,7 +208,7 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
@Override @Override
public boolean needRebuild() { public boolean needRebuild() {
return true; return needRebuild;
} }
@Override @Override
...@@ -163,65 +221,69 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -163,65 +221,69 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
if (closed) { if (closed) {
throw DbException.throwInternalError(); throw DbException.throwInternalError();
} }
if (!first) {
// TODO use an external iterator, throw DbException.throwInternalError("Spatial Index can only be fetch by ascending order");
// but let's see if we can get it working first }
@SuppressWarnings("unchecked") return find(session);
List<Row> list = root.queryAll();
return new ListCursor(list, first);
} }
@Override @Override
public long getRowCount(Session session) { public long getRowCount(Session session) {
return rowCount; return treeMap.getSize();
} }
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
return rowCount; return treeMap.getSize();
} }
@Override @Override
public long getDiskSpaceUsed() { public long getDiskSpaceUsed() {
// TODO estimate disk space usage
return 0; return 0;
} }
/** /**
* A cursor of a fixed list of rows. * A cursor to iterate over spatial keys.
*/ */
private static final class ListCursor implements Cursor { private static final class SpatialCursor implements Cursor {
private final List<Row> rows;
private int index; private final Iterator<SpatialKey> it;
private Row current; private SpatialKey current;
private final RegularTable tableData;
private Session session;
public ListCursor(List<Row> rows, boolean first) { public SpatialCursor(Iterator<SpatialKey> it, RegularTable tableData, Session session) {
this.rows = rows; this.it = it;
this.index = first ? 0 : rows.size(); this.tableData = tableData;
this.session = session;
} }
@Override @Override
public Row get() { public Row get() {
return current; return tableData.getRow(session, current.getId());
} }
@Override @Override
public SearchRow getSearchRow() { public SearchRow getSearchRow() {
return current; return get();
} }
@Override @Override
public boolean next() { public boolean next() {
current = index >= rows.size() ? null : rows.get(index++); if (!it.hasNext()) {
return current != null; return false;
}
current = it.next();
return true;
} }
@Override @Override
public boolean previous() { public boolean previous() {
current = index < 0 ? null : rows.get(index--); return false;
return current != null;
} }
} }
} }
...@@ -56,6 +56,10 @@ import org.h2.value.ValueUuid; ...@@ -56,6 +56,10 @@ import org.h2.value.ValueUuid;
/** /**
* This class represents a byte buffer that contains persistent data of a page. * This class represents a byte buffer that contains persistent data of a page.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class Data { public class Data {
...@@ -520,11 +524,13 @@ public class Data { ...@@ -520,11 +524,13 @@ public class Data {
} }
break; break;
} }
case Value.GEOMETRY:
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
writeByte((byte) type); writeByte((byte) type);
byte[] b = v.getBytesNoCopy(); byte[] b = v.getBytesNoCopy();
writeVarInt(b.length); int len = b.length;
write(b, 0, b.length); writeVarInt(len);
write(b, 0, len);
break; break;
} }
case Value.BYTES: { case Value.BYTES: {
...@@ -532,11 +538,11 @@ public class Data { ...@@ -532,11 +538,11 @@ public class Data {
int len = b.length; int len = b.length;
if (len < 32) { if (len < 32) {
writeByte((byte) (BYTES_0_31 + len)); writeByte((byte) (BYTES_0_31 + len));
write(b, 0, b.length); write(b, 0, len);
} else { } else {
writeByte((byte) type); writeByte((byte) type);
writeVarInt(b.length); writeVarInt(len);
write(b, 0, b.length); write(b, 0, len);
} }
break; break;
} }
...@@ -671,14 +677,6 @@ public class Data { ...@@ -671,14 +677,6 @@ public class Data {
} }
break; break;
} }
case Value.GEOMETRY: {
writeByte((byte) type);
byte[] b = v.getBytes();
int len = b.length;
writeVarInt(len);
write(b, 0, len);
break;
}
default: default:
DbException.throwInternalError("type=" + v.getType()); DbException.throwInternalError("type=" + v.getType());
} }
...@@ -764,6 +762,12 @@ public class Data { ...@@ -764,6 +762,12 @@ public class Data {
read(b, 0, len); read(b, 0, len);
return ValueBytes.getNoCopy(b); return ValueBytes.getNoCopy(b);
} }
case Value.GEOMETRY: {
int len = readVarInt();
byte[] b = DataUtils.newBytes(len);
read(b, 0, len);
return ValueGeometry.get(b);
}
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
int len = readVarInt(); int len = readVarInt();
byte[] b = DataUtils.newBytes(len); byte[] b = DataUtils.newBytes(len);
...@@ -848,12 +852,6 @@ public class Data { ...@@ -848,12 +852,6 @@ public class Data {
} }
return ValueResultSet.get(rs); return ValueResultSet.get(rs);
} }
case Value.GEOMETRY: {
int len = readVarInt();
byte[] b = DataUtils.newBytes(len);
read(b, 0, len);
return ValueGeometry.get(b);
}
default: default:
if (type >= INT_0_15 && type < INT_0_15 + 16) { if (type >= INT_0_15 && type < INT_0_15 + 16) {
return ValueInt.get(type - INT_0_15); return ValueInt.get(type - INT_0_15);
...@@ -999,6 +997,7 @@ public class Data { ...@@ -999,6 +997,7 @@ public class Data {
Timestamp ts = v.getTimestamp(); Timestamp ts = v.getTimestamp();
return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(ts)) + getVarIntLen(ts.getNanos()); return 1 + getVarLongLen(DateTimeUtils.getTimeLocalWithoutDst(ts)) + getVarIntLen(ts.getNanos());
} }
case Value.GEOMETRY:
case Value.JAVA_OBJECT: { case Value.JAVA_OBJECT: {
byte[] b = v.getBytesNoCopy(); byte[] b = v.getBytesNoCopy();
return 1 + getVarIntLen(b.length) + b.length; return 1 + getVarIntLen(b.length) + b.length;
...@@ -1089,11 +1088,6 @@ public class Data { ...@@ -1089,11 +1088,6 @@ public class Data {
} }
return len; return len;
} }
case Value.GEOMETRY: {
byte[] b = v.getBytesNoCopy();
int len = b.length;
return 1 + getVarIntLen(len) + len;
}
default: default:
throw DbException.throwInternalError("type=" + v.getType()); throw DbException.throwInternalError("type=" + v.getType());
} }
......
...@@ -228,10 +228,10 @@ public class RegularTable extends TableBase { ...@@ -228,10 +228,10 @@ public class RegularTable extends TableBase {
if (mainIndexColumn != -1) { if (mainIndexColumn != -1) {
mainIndex.setMainIndexColumn(mainIndexColumn); mainIndex.setMainIndexColumn(mainIndexColumn);
index = new PageDelegateIndex(this, indexId, indexName, indexType, mainIndex, create, session); index = new PageDelegateIndex(this, indexId, indexName, indexType, mainIndex, create, session);
} else if (!indexType.isSpatial()) { } else if (indexType.isSpatial()) {
index = new PageBtreeIndex(this, indexId, indexName, cols, indexType, create, session); index = new SpatialTreeIndex(this, indexId, indexName, cols, indexType, true, create, session);
} else { } else {
throw new UnsupportedOperationException("Spatial index only supported with the MVStore"); index = new PageBtreeIndex(this, indexId, indexName, cols, indexType, create, session);
} }
} else { } else {
if (indexType.isHash() && cols.length <= 1) { if (indexType.isHash() && cols.length <= 1) {
...@@ -243,7 +243,7 @@ public class RegularTable extends TableBase { ...@@ -243,7 +243,7 @@ public class RegularTable extends TableBase {
} else if (!indexType.isSpatial()) { } else if (!indexType.isSpatial()) {
index = new TreeIndex(this, indexId, indexName, cols, indexType); index = new TreeIndex(this, indexId, indexName, cols, indexType);
} else { } else {
index = new SpatialTreeIndex(this, indexId, indexName, cols, indexType); index = new SpatialTreeIndex(this, indexId, indexName, cols, indexType, false, true, session);
} }
} }
if (database.isMultiVersion()) { if (database.isMultiVersion()) {
......
...@@ -961,7 +961,13 @@ public class DataType { ...@@ -961,7 +961,13 @@ public class DataType {
} }
} }
private static boolean isGeometry(Object x) { /**
* Check whether a given object is a Geometry object.
*
* @param x the the object
* @return true if it is a Geometry object
*/
public static boolean isGeometry(Object x) {
if (x == null || GEOMETRY_CLASS == null) { if (x == null || GEOMETRY_CLASS == null) {
return false; return false;
} }
......
...@@ -34,6 +34,10 @@ import org.h2.util.Utils; ...@@ -34,6 +34,10 @@ import org.h2.util.Utils;
/** /**
* This is the base class for all value classes. * This is the base class for all value classes.
* It provides conversion and comparison methods. * It provides conversion and comparison methods.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public abstract class Value { public abstract class Value {
...@@ -793,6 +797,11 @@ public abstract class Value { ...@@ -793,6 +797,11 @@ public abstract class Value {
switch(getType()) { switch(getType()) {
case BYTES: case BYTES:
return ValueGeometry.get(getBytesNoCopy()); return ValueGeometry.get(getBytesNoCopy());
case JAVA_OBJECT:
Object object = Utils.deserialize(getBytesNoCopy());
if (DataType.isGeometry(object)) {
return ValueGeometry.getFromGeometry(object);
}
} }
} }
// conversion by parsing the string value // conversion by parsing the string value
......
...@@ -11,7 +11,9 @@ import java.sql.SQLException; ...@@ -11,7 +11,9 @@ import java.sql.SQLException;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKBReader; import com.vividsolutions.jts.io.WKBReader;
import com.vividsolutions.jts.io.WKBWriter; import com.vividsolutions.jts.io.WKBWriter;
...@@ -20,6 +22,10 @@ import com.vividsolutions.jts.io.WKTWriter; ...@@ -20,6 +22,10 @@ import com.vividsolutions.jts.io.WKTWriter;
/** /**
* Implementation of the GEOMETRY data type. * Implementation of the GEOMETRY data type.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class ValueGeometry extends Value { public class ValueGeometry extends Value {
...@@ -71,33 +77,35 @@ public class ValueGeometry extends Value { ...@@ -71,33 +77,35 @@ public class ValueGeometry extends Value {
} }
/** /**
* Check whether two values intersect. * Test if this geometry envelope intersects with the other geometry
* envelope.
* *
* @param r the second value * @param r the other geometry
* @return true if they intersect * @return true if the two envelopes overlaps
*/ */
public boolean intersects(ValueGeometry r) { public boolean intersectsBoundingBox(ValueGeometry r) {
return geometry.intersects(r.getGeometry()); // it is useless to cache the envelope as the Geometry object do this already
return geometry.getEnvelopeInternal().intersects(r.getGeometry().getEnvelopeInternal());
} }
/** /**
* Get the intersection of two values. * Get the intersection.
* *
* @param r the second value * @param r the other geometry
* @return the intersection * @return the intersection of this geometry envelope and another geometry envelope
*/ */
public Value intersection(ValueGeometry r) { public ValueGeometry getEnvelopeIntersection(ValueGeometry r) {
return get(geometry.intersection(r.geometry)); Envelope e1 = geometry.getEnvelopeInternal();
Envelope e2 = r.getGeometry().getEnvelopeInternal();
Envelope e3 = e1.intersection(e2);
// try to re-use the object
if (e3 == e1) {
return this;
} else if (e3 == e2) {
return r;
} }
GeometryFactory gf = new GeometryFactory();
/** return get(gf.toGeometry(e3));
* Get the union of two values.
*
* @param r the second value
* @return the union
*/
public Value union(ValueGeometry r) {
return get(geometry.union(r.geometry));
} }
@Override @Override
...@@ -107,7 +115,7 @@ public class ValueGeometry extends Value { ...@@ -107,7 +115,7 @@ public class ValueGeometry extends Value {
@Override @Override
public String getSQL() { public String getSQL() {
return StringUtils.quoteStringSQL(toWKT()); return StringUtils.quoteStringSQL(toWKT()) + "'::Geometry";
} }
@Override @Override
...@@ -141,6 +149,11 @@ public class ValueGeometry extends Value { ...@@ -141,6 +149,11 @@ public class ValueGeometry extends Value {
return toWKB(); return toWKB();
} }
@Override
public byte[] getBytesNoCopy() {
return toWKB();
}
@Override @Override
public void set(PreparedStatement prep, int parameterIndex) throws SQLException { public void set(PreparedStatement prep, int parameterIndex) throws SQLException {
prep.setObject(parameterIndex, geometry); prep.setObject(parameterIndex, geometry);
...@@ -167,18 +180,16 @@ public class ValueGeometry extends Value { ...@@ -167,18 +180,16 @@ public class ValueGeometry extends Value {
* @return the well-known-text * @return the well-known-text
*/ */
public String toWKT() { public String toWKT() {
WKTWriter w = new WKTWriter(); return new WKTWriter().write(geometry);
return w.write(geometry);
} }
/** /**
* Convert to value to the Well-Known-Binary format. * Convert to Well-Known-Binary format.
* *
* @return the well-known-binary * @return the well-known-binary
*/ */
public byte[] toWKB() { public byte[] toWKB() {
WKBWriter w = new WKBWriter(); return new WKBWriter().write(geometry);
return w.write(geometry);
} }
/** /**
...@@ -188,9 +199,8 @@ public class ValueGeometry extends Value { ...@@ -188,9 +199,8 @@ public class ValueGeometry extends Value {
* @return the Geometry object * @return the Geometry object
*/ */
private static Geometry fromWKT(String s) { private static Geometry fromWKT(String s) {
WKTReader r = new WKTReader();
try { try {
return r.read(s); return new WKTReader().read(s);
} catch (ParseException ex) { } catch (ParseException ex) {
throw DbException.convert(ex); throw DbException.convert(ex);
} }
...@@ -203,9 +213,8 @@ public class ValueGeometry extends Value { ...@@ -203,9 +213,8 @@ public class ValueGeometry extends Value {
* @return the Geometry object * @return the Geometry object
*/ */
private static Geometry fromWKB(byte[] bytes) { private static Geometry fromWKB(byte[] bytes) {
WKBReader r = new WKBReader();
try { try {
return r.read(bytes); return new WKBReader().read(bytes);
} catch (ParseException ex) { } catch (ParseException ex) {
throw DbException.convert(ex); throw DbException.convert(ex);
} }
......
...@@ -6,18 +6,40 @@ ...@@ -6,18 +6,40 @@
package org.h2.test.db; package org.h2.test.db;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement; import java.sql.Statement;
import java.sql.Types;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.jdbc.JdbcConnection;
import org.h2.mvstore.MVStore;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.tools.SimpleResultSet;
import org.h2.tools.SimpleRowSource;
import org.h2.value.DataType; import org.h2.value.DataType;
import org.h2.value.ValueGeometry;
import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon; import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
/** /**
* Spatial datatype and index tests. * Spatial datatype and index tests.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/ */
public class TestSpatial extends TestBase { public class TestSpatial extends TestBase {
...@@ -34,8 +56,17 @@ public class TestSpatial extends TestBase { ...@@ -34,8 +56,17 @@ public class TestSpatial extends TestBase {
public void test() throws SQLException { public void test() throws SQLException {
if (DataType.GEOMETRY_CLASS != null) { if (DataType.GEOMETRY_CLASS != null) {
deleteDb("spatial"); deleteDb("spatial");
testSpatialValues(); // testSpatialValues();
// testOverlap();
// testNotOverlap();
// testPersistentSpatialIndex();
// testSpatialIndexQueryMultipleTable();
// testIndexTransaction();
// testJavaAlias();
// testJavaAliasTableFunction();
// testPersistentSpatialIndex2();
testMemorySpatialIndex(); testMemorySpatialIndex();
testRandom();
deleteDb("spatial"); deleteDb("spatial");
} }
} }
...@@ -70,7 +101,270 @@ public class TestSpatial extends TestBase { ...@@ -70,7 +101,270 @@ public class TestSpatial extends TestBase {
deleteDb("spatial"); deleteDb("spatial");
} }
/** test in the in-memory spatial index */ /**
* Generate a random linestring under the given bounding box
* @param minX Bounding box min x
* @param maxX Bounding box max x
* @param minY Bounding box min y
* @param maxY Bounding box max y
* @param maxLength LineString maximum length
* @return A segment within this bounding box
*/
public static Geometry getRandomGeometry(Random geometryRand,double minX,double maxX,double minY, double maxY, double maxLength) {
GeometryFactory factory = new GeometryFactory();
// Create the start point
Coordinate start = new Coordinate(geometryRand.nextDouble()*(maxX-minX)+minX,
geometryRand.nextDouble()*(maxY-minY)+minY);
// Compute an angle
double angle = geometryRand.nextDouble() * Math.PI * 2;
// Compute length
double length = geometryRand.nextDouble() * maxLength;
// Compute end point
Coordinate end = new Coordinate(start.x + Math.cos(angle) * length, start.y + Math.sin(angle) * length);
return factory.createLineString(new Coordinate[]{start,end});
}
private void testRandom() throws SQLException {
deleteDb("spatial");
Connection conn = getConnection("spatial");
testRandom(conn, 69, 3500);
testRandom(conn, 44, 3500);
conn.close();
}
private void testRandom(Connection conn, long seed,long size) throws SQLException {
Statement stat = conn.createStatement();
stat.execute("drop table if exists test");
Random geometryRand = new Random(seed);
// Generate a set of geometry
// It is marked as random, but it generate always the same geometry set, given the same seed
stat.execute("create memory table test(id long primary key auto_increment, poly geometry)");
// Create segment generation bounding box
Envelope bbox = ValueGeometry.get("POLYGON ((301804.1049793153 2251719.1222191923," +
" 301804.1049793153 2254747.2888244865, 304646.87362918374 2254747.2888244865," +
" 304646.87362918374 2251719.1222191923, 301804.1049793153 2251719.1222191923))")
.getGeometry().getEnvelopeInternal();
// Create overlap test bounding box
String testBBoxString = "POLYGON ((302215.44416332216 2252748, 302215.44416332216 2253851.781225762," +
" 303582.85796541866 2253851.781225762, 303582.85796541866 2252748.526908161," +
" 302215.44416332216 2252748))";
Envelope testBBox = ValueGeometry.get(testBBoxString).getGeometry().getEnvelopeInternal();
PreparedStatement ps = conn.prepareStatement("insert into test(poly) values (?)");
long overlapCount = 0;
Set<Integer> overlaps = new HashSet<Integer>(680);
for(int i=1;i<=size;i++) {
Geometry geometry = getRandomGeometry(geometryRand,bbox.getMinX(),bbox.getMaxX(),bbox.getMinY(),bbox.getMaxY(),200);
ps.setObject(1,geometry);
ps.execute();
ResultSet keys = ps.getGeneratedKeys();
keys.next();
if(geometry.getEnvelopeInternal().intersects(testBBox)) {
overlapCount++;
overlaps.add(keys.getInt(1));
}
}
ps.close();
// Create index
stat.execute("create spatial index idx_test_poly on test(poly)");
// Must find the same overlap count with index
ps = conn.prepareStatement("select id from test where poly && ?::Geometry");
ps.setString(1,testBBoxString);
ResultSet rs = ps.executeQuery();
long found = 0;
while(rs.next()) {
overlaps.remove(rs.getInt(1));
found++;
}
// Index count must be the same as sequential count
assertEquals(overlapCount,found);
// Missing id still in overlaps map
assertTrue(overlaps.isEmpty());
stat.execute("drop table if exists test");
}
private void testOverlap() throws SQLException {
deleteDb("spatial");
Connection conn = getConnection("spatial");
try {
Statement stat = conn.createStatement();
stat.execute("create memory 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(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))')");
ResultSet rs = stat.executeQuery("select * from test where poly && 'POINT (1.5 1.5)'::Geometry");
assertTrue(rs.next());
assertEquals(1,rs.getInt("id"));
assertFalse(rs.next());
stat.execute("drop table test");
} finally {
conn.close();
}
}
private void testPersistentSpatialIndex() throws SQLException {
deleteDb("spatial_pers");
Connection conn = getConnection("spatial_pers");
try {
Statement stat = conn.createStatement();
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(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("create spatial index on test(poly)");
ResultSet rs = stat.executeQuery("select * from test where poly && 'POINT (1.5 1.5)'::Geometry");
assertTrue(rs.next());
assertEquals(1, rs.getInt("id"));
assertFalse(rs.next());
rs.close();
// Test with multiple operator
rs = stat.executeQuery("select * from test where poly && 'POINT (1.5 1.5)'::Geometry AND poly && 'POINT (1.7 1.75)'::Geometry");
assertTrue(rs.next());
assertEquals(1, rs.getInt("id"));
assertFalse(rs.next());
rs.close();
} finally {
// Close the database
conn.close();
}
conn = getConnection("spatial_pers");
try {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select * from test where poly && 'POINT (1.5 1.5)'::Geometry");
assertTrue(rs.next());
assertEquals(1,rs.getInt("id"));
assertFalse(rs.next());
stat.execute("drop table test");
} finally {
conn.close();
}
}
private void testNotOverlap() throws SQLException {
deleteDb("spatial");
Connection conn = getConnection("spatial");
try {
Statement stat = conn.createStatement();
stat.execute("create memory 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(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))')");
ResultSet rs = stat.executeQuery("select * from test where NOT poly && 'POINT (1.5 1.5)'::Geometry");
assertTrue(rs.next());
assertEquals(2,rs.getInt("id"));
assertTrue(rs.next());
assertEquals(3,rs.getInt("id"));
assertFalse(rs.next());
stat.execute("drop table test");
} finally {
conn.close();
}
}
private static void createTestTable(Statement stat) throws SQLException {
stat.execute("create table area(idarea int primary key, the_geom geometry)");
stat.execute("create spatial index on area(the_geom)");
stat.execute("insert into area values(1, 'POLYGON ((-10 109, 90 109, 90 9, -10 9, -10 109))')");
stat.execute("insert into area values(2, 'POLYGON ((90 109, 190 109, 190 9, 90 9, 90 109))')");
stat.execute("insert into area values(3, 'POLYGON ((190 109, 290 109, 290 9, 190 9, 190 109))')");
stat.execute("insert into area values(4, 'POLYGON ((-10 9, 90 9, 90 -91, -10 -91, -10 9))')");
stat.execute("insert into area values(5, 'POLYGON ((90 9, 190 9, 190 -91, 90 -91, 90 9))')");
stat.execute("insert into area values(6, 'POLYGON ((190 9, 290 9, 290 -91, 190 -91, 190 9))')");
stat.execute("create table roads(idroad int primary key, the_geom geometry)");
stat.execute("create spatial index on roads(the_geom)");
stat.execute("insert into roads values(1, 'LINESTRING (27.65595463138 -16.728733459357244, 47.61814744801515 40.435727788279806)')");
stat.execute("insert into roads values(2, 'LINESTRING (17.674858223062415 55.861058601134246, 55.78449905482046 76.73062381852554)')");
stat.execute("insert into roads values(3, 'LINESTRING (68.48771266540646 67.65689981096412, 108.4120982986768 88.52646502835542)')");
stat.execute("insert into roads values(4, 'LINESTRING (177.3724007561437 18.65879017013235, 196.4272211720227 -16.728733459357244)')");
stat.execute("insert into roads values(5, 'LINESTRING (106.5973534971645 -12.191871455576518, 143.79962192816637 30.454631379962223)')");
stat.execute("insert into roads values(6, 'LINESTRING (144.70699432892252 55.861058601134246, 150.1512287334594 83.9896030245747)')");
stat.execute("insert into roads values(7, 'LINESTRING (60.321361058601155 -13.099243856332663, 149.24385633270325 5.955576559546344)')");
}
private void testSpatialIndexQueryMultipleTable() throws SQLException {
deleteDb("spatial");
Connection conn = getConnection("spatial");
try {
Statement stat = conn.createStatement();
createTestTable(stat);
testRoadAndArea(stat);
} finally {
// Close the database
conn.close();
}
deleteDb("spatial");
}
private void testRoadAndArea(Statement stat) throws SQLException {
ResultSet rs = stat.executeQuery("select idarea, COUNT(idroad) roadscount from area,roads where area.the_geom && roads.the_geom GROUP BY idarea ORDER BY idarea");
assertTrue(rs.next());
assertEquals(1,rs.getInt("idarea"));
assertEquals(3,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(2,rs.getInt("idarea"));
assertEquals(4,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(3,rs.getInt("idarea"));
assertEquals(1,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(4,rs.getInt("idarea"));
assertEquals(2,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(5,rs.getInt("idarea"));
assertEquals(3,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(6,rs.getInt("idarea"));
assertEquals(1,rs.getInt("roadscount"));
assertFalse(rs.next());
rs.close();
}
private void testIndexTransaction() throws SQLException {
// Check session management in index
deleteDb("spatialIndex");
Connection conn = getConnection("spatialIndex");
conn.setAutoCommit(false);
try {
Statement stat = conn.createStatement();
createTestTable(stat);
Savepoint sp = conn.setSavepoint();
// Remove a row but do not commit
stat.execute("delete from roads where idroad=7");
// Check if index is updated
ResultSet rs = stat.executeQuery("select idarea, COUNT(idroad) roadscount from area,roads where area.the_geom && roads.the_geom GROUP BY idarea ORDER BY idarea");
assertTrue(rs.next());
assertEquals(1,rs.getInt("idarea"));
assertEquals(3,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(2,rs.getInt("idarea"));
assertEquals(4,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(3,rs.getInt("idarea"));
assertEquals(1,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(4,rs.getInt("idarea"));
assertEquals(1,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(5,rs.getInt("idarea"));
assertEquals(2,rs.getInt("roadscount"));
assertTrue(rs.next());
assertEquals(6,rs.getInt("idarea"));
assertEquals(1,rs.getInt("roadscount"));
assertFalse(rs.next());
rs.close();
conn.rollback(sp);
// Check if the index is restored
testRoadAndArea(stat);
} finally {
conn.close();
}
}
/**
* Test the in the in-memory spatial index
*/
private void testMemorySpatialIndex() throws SQLException { private void testMemorySpatialIndex() throws SQLException {
deleteDb("spatialIndex"); deleteDb("spatialIndex");
Connection conn = getConnection("spatialIndex"); Connection conn = getConnection("spatialIndex");
...@@ -79,16 +373,28 @@ public class TestSpatial extends TestBase { ...@@ -79,16 +373,28 @@ 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))')");
ResultSet rs;
// an query that can not possibly return a result
rs = stat.executeQuery("select * from test " +
"where polygon && 'POLYGON ((1 1, 1 2, 2 2, 1 1))'::Geometry " +
"and polygon && 'POLYGON ((10 10, 10 20, 20 20, 10 10))'::Geometry");
assertFalse(rs.next());
ResultSet rs = stat.executeQuery("explain select * from test where polygon = 'POLYGON ((1 1, 1 2, 2 2, 1 1))'"); rs = stat.executeQuery("explain select * from test where polygon && 'POLYGON ((1 1, 1 2, 2 2, 1 1))'::Geometry");
rs.next(); rs.next();
assertContains(rs.getString(1), "/* PUBLIC.IDX_TEST_POLYGON: POLYGON ="); assertContains(rs.getString(1), "/* PUBLIC.IDX_TEST_POLYGON: POLYGON &&");
// these queries actually have no meaning in the context of a spatial index, int todo;
// just check that the query works // TODO equality should probably also use the spatial index
stat.executeQuery("select * from test where polygon = 'POLYGON ((1 1, 1 2, 2 2, 1 1))'"); // rs = stat.executeQuery("explain select * from test where polygon = 'POLYGON ((1 1, 1 2, 2 2, 1 1))'");
stat.executeQuery("select * from test where polygon > 'POLYGON ((1 1, 1 2, 2 2, 1 1))'"); // rs.next();
stat.executeQuery("select * from test where polygon < 'POLYGON ((1 1, 1 2, 2 2, 1 1))'"); // assertContains(rs.getString(1), "/* PUBLIC.IDX_TEST_POLYGON: POLYGON =");
// these queries actually have no meaning in the context of a spatial index, but
// check them anyhow
stat.executeQuery("select * from test where polygon > 'POLYGON ((1 1, 1 2, 2 2, 1 1))'::Geometry");
stat.executeQuery("select * from test where polygon < 'POLYGON ((1 1, 1 2, 2 2, 1 1))'::Geometry");
rs = stat.executeQuery("select * from test where intersects(polygon, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); rs = stat.executeQuery("select * from test where intersects(polygon, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')");
assertTrue(rs.next()); assertTrue(rs.next());
...@@ -104,4 +410,124 @@ public class TestSpatial extends TestBase { ...@@ -104,4 +410,124 @@ public class TestSpatial extends TestBase {
deleteDb("spatialIndex"); deleteDb("spatialIndex");
} }
/**
* Test java alias with Geometry type.
*/
private void testJavaAlias() throws SQLException {
deleteDb("spatialIndex");
Connection conn = getConnection("spatialIndex");
try {
Statement stat = conn.createStatement();
stat.execute("CREATE ALIAS T_GEOMFROMTEXT FOR \"" + TestSpatial.class.getName() + ".geomFromText\"");
stat.execute("create table test(id int primary key auto_increment, the_geom geometry)");
stat.execute("insert into test(the_geom) values(T_GEOMFROMTEXT('POLYGON ((62 48, 84 48, 84 42, 56 34, 62 48))',1488))");
stat.execute("DROP ALIAS T_GEOMFROMTEXT");
ResultSet rs = stat.executeQuery("select the_geom from test");
assertTrue(rs.next());
assertEquals("POLYGON ((62 48, 84 48, 84 42, 56 34, 62 48))", rs.getObject(1).toString());
} finally {
conn.close();
}
deleteDb("spatialIndex");
}
/**
* Test java alias with Geometry type.
*/
private void testJavaAliasTableFunction() throws SQLException {
deleteDb("spatialIndex");
Connection conn = getConnection("spatialIndex");
try {
Statement stat = conn.createStatement();
stat.execute("CREATE ALIAS T_RANDOM_GEOM_TABLE FOR \"" + TestSpatial.class.getName() + ".getRandomGeometryTable\"");
stat.execute("create table test as select * from T_RANDOM_GEOM_TABLE(42,20,-100,100,-100,100,4)");
stat.execute("DROP ALIAS T_RANDOM_GEOM_TABLE");
ResultSet rs = stat.executeQuery("select count(*) cpt from test");
assertTrue(rs.next());
assertEquals(20, rs.getInt(1));
} finally {
conn.close();
}
deleteDb("spatialIndex");
}
public static ResultSet getRandomGeometryTable(final long seed,final long rowCount, final double minX,final double maxX,final double minY, final double maxY, final double maxLength) {
SimpleResultSet rs = new SimpleResultSet(new SimpleRowSource() {
private final Random rnd = new Random(seed);
private int cpt = 0;
@Override
public Object[] readRow() throws SQLException {
if(cpt++<rowCount) {
return new Object[]{getRandomGeometry(rnd,minX,maxX,minY,maxY,maxLength)}; //To change body of implemented methods use File | Settings | File Templates.
} else {
return null;
}
}
@Override
public void close() {
}
@Override
public void reset() throws SQLException {
rnd.setSeed(seed);
}
});
rs.addColumn("the_geom", Types.OTHER,Integer.MAX_VALUE,0);
return rs;
}
/**
*
* @param text Geometry in Well Known Text
* @param srid Projection ID
* @return Geometry object
*/
public static Geometry geomFromText(String text, int srid) throws SQLException {
WKTReader wktReader = new WKTReader();
try {
Geometry geom = wktReader.read(text);
geom.setSRID(srid);
return geom;
} catch (ParseException ex) {
throw new SQLException(ex);
}
}
/**
* Not really a test case but show that something go crazy (in mvstore) after some seconds.
* @throws SQLException
*/
private void testPersistentSpatialIndex2() throws SQLException {
deleteDb("spatial_pers");
final long count = 150000;
Connection conn = getConnection("spatial_pers");
try {
Statement stat = conn.createStatement();
stat.execute("create table test(id int primary key auto_increment, the_geom geometry)");
PreparedStatement ps = conn.prepareStatement("insert into test(the_geom) values(?)");
Random rnd = new Random(44);
for(int i=0;i<count;i++) {
ps.setObject(1,getRandomGeometry(rnd,0,100,-50,50,3));
ps.execute();
}
stat.execute("create spatial index on test(the_geom)");
Database db = ((Session)((JdbcConnection) conn).getSession()).getDatabase();
MVStore store = db.getMvStore().getStore();
int cpt=0;
while(cpt<46) {
try {
// First it shows 610, then 5 until cpt==44, finally at cpt==45 it shows an unsaved 688 with a trace in spatial_pers.trace.db
System.out.println((cpt++)+" store.getUnsavedPageCount()=="+store.getUnsavedPageCount());
Thread.sleep(1000);
} catch (InterruptedException ex) {
throw new SQLException(ex);
}
}
} finally {
// Close the database
conn.close();
}
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论