提交 9e6867b0 authored 作者: Thomas Mueller's avatar Thomas Mueller

The spatial index now works in MVCC mode when using the MVStore storage.

上级 b5ede1e6
...@@ -21,6 +21,7 @@ Change Log ...@@ -21,6 +21,7 @@ Change Log
<ul><li>Subqueries or views with "order by" an alias expression could not be executed <ul><li>Subqueries or views with "order by" an alias expression could not be executed
due to a regression introduced in version 1.3.174. due to a regression introduced in version 1.3.174.
</li><li>Issue 73: MySQL compatibility: support REPLACE, patch by Cemo Koc. </li><li>Issue 73: MySQL compatibility: support REPLACE, patch by Cemo Koc.
</li><li>The spatial index now works in MVCC mode when using the MVStore storage.
</li><li>MVStore: concurrency problems have been fixed. </li><li>MVStore: concurrency problems have been fixed.
</li><li>Improve error message when dropping an index that belongs to a constraint, </li><li>Improve error message when dropping an index that belongs to a constraint,
specify constraint in error message. specify constraint in error message.
......
...@@ -194,7 +194,6 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -194,7 +194,6 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
@Override @Override
public void remove(Session session) { public void remove(Session session) {
if (!treeMap.isClosed()) { if (!treeMap.isClosed()) {
MVStore store = session.getDatabase().getMvStore().getStore();
store.removeMap(treeMap); store.removeMap(treeMap);
} }
} }
......
...@@ -379,8 +379,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -379,8 +379,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public boolean previous() { public boolean previous() {
// TODO previous throw DbException.getUnsupportedException("previous");
return false;
} }
} }
......
...@@ -342,8 +342,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -342,8 +342,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public boolean previous() { public boolean previous() {
// TODO previous throw DbException.getUnsupportedException("previous");
return false;
} }
} }
......
/*
* Copyright 2004-2013 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.mvstore.db;
import java.util.Iterator;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.index.BaseIndex;
import org.h2.index.Cursor;
import org.h2.index.IndexCondition;
import org.h2.index.IndexType;
import org.h2.index.SpatialIndex;
import org.h2.message.DbException;
import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.mvstore.db.TransactionStore.TransactionMap;
import org.h2.mvstore.db.TransactionStore.VersionedValue;
import org.h2.mvstore.db.TransactionStore.VersionedValueType;
import org.h2.mvstore.rtree.MVRTreeMap;
import org.h2.mvstore.rtree.MVRTreeMap.RTreeCursor;
import org.h2.mvstore.rtree.SpatialKey;
import org.h2.result.Row;
import org.h2.result.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.h2.value.ValueGeometry;
import org.h2.value.ValueLong;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
/**
* This is an index based on a MVRTreeMap.
*
* @author Thomas Mueller
* @author Noel Grandin
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/
public class MVSpatialIndex extends BaseIndex implements SpatialIndex {
/**
* The multi-value table.
*/
final MVTable mvTable;
private final String mapName;
private TransactionMap<SpatialKey, Value> dataMap;
private MVRTreeMap<VersionedValue> spatialMap;
/**
* Constructor.
*
* @param table the table instance
* @param id the index id
* @param indexName the index name
* @param columns the indexed columns (only one geometry column allowed)
* @param indexType the index type (only spatial index)
*/
public MVSpatialIndex(
Database db, MVTable table, int id, String indexName,
IndexColumn[] columns, IndexType indexType) {
if (columns.length != 1) {
throw DbException.getUnsupportedException("Can only index one column");
}
IndexColumn col = columns[0];
if ((col.sortType & SortOrder.DESCENDING) != 0) {
throw DbException.getUnsupportedException("Cannot index in descending order");
}
if ((col.sortType & SortOrder.NULLS_FIRST) != 0) {
throw DbException.getUnsupportedException("Nulls first is not supported");
}
if ((col.sortType & SortOrder.NULLS_LAST) != 0) {
throw DbException.getUnsupportedException("Nulls last is not supported");
}
if (col.column.getType() != Value.GEOMETRY) {
throw DbException.getUnsupportedException("Spatial index on non-geometry column, "
+ col.column.getCreateSQL());
}
this.mvTable = table;
initBaseIndex(table, id, indexName, columns, indexType);
if (!database.isStarting()) {
checkIndexColumnTypes(columns);
}
mapName = "index." + getId();
ValueDataType vt = new ValueDataType(null, null, null);
VersionedValueType valueType = new VersionedValueType(vt);
MVRTreeMap.Builder<VersionedValue> mapBuilder =
new MVRTreeMap.Builder<VersionedValue>().
valueType(valueType);
spatialMap = db.getMvStore().getStore().openMap(mapName, mapBuilder);
dataMap = mvTable.getTransaction(null).openMap(spatialMap);
}
@Override
public void close(Session session) {
// ok
}
@Override
public void add(Session session, Row row) {
TransactionMap<SpatialKey, Value> map = getMap(session);
SpatialKey key = getKey(row);
if (indexType.isUnique()) {
// this will detect committed entries only
RTreeCursor cursor = spatialMap.findContainedKeys(key);
Iterator<SpatialKey> it = map.wrapIterator(cursor, false);
while (it.hasNext()) {
SpatialKey k = it.next();
if (k.equalsIgnoringId(key)) {
throw getDuplicateKeyException(key.toString());
}
}
}
try {
map.put(key, ValueLong.get(0));
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName());
}
if (indexType.isUnique()) {
// check if there is another (uncommitted) entry
RTreeCursor cursor = spatialMap.findContainedKeys(key);
Iterator<SpatialKey> it = map.wrapIterator(cursor, true);
while (it.hasNext()) {
SpatialKey k = it.next();
if (k.equalsIgnoringId(key)) {
if (map.isSameTransaction(k)) {
continue;
}
map.remove(key);
if (map.get(k) != null) {
// committed
throw getDuplicateKeyException(k.toString());
}
throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName());
}
}
}
}
private SpatialKey getKey(SearchRow r) {
if (r == null) {
return null;
}
Value v = r.getValue(columnIds[0]);
Geometry g = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getGeometry();
Envelope env = g.getEnvelopeInternal();
return new SpatialKey(r.getKey(),
(float) env.getMinX(), (float) env.getMaxX(),
(float) env.getMinY(), (float) env.getMaxY());
}
@Override
public void remove(Session session, Row row) {
SpatialKey key = getKey(row);
TransactionMap<SpatialKey, Value> map = getMap(session);
try {
Value old = map.remove(key);
if (old == null) {
throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1,
getSQL() + ": " + row.getKey());
}
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName());
}
}
@Override
public Cursor find(TableFilter filter, SearchRow first, SearchRow last) {
return find(filter.getSession());
}
@Override
public Cursor find(Session session, SearchRow first, SearchRow last) {
return find(session);
}
private Cursor find(Session session) {
Iterator<SpatialKey> cursor = spatialMap.keyIterator(null);
TransactionMap<SpatialKey, Value> map = getMap(session);
Iterator<SpatialKey> it = map.wrapIterator(cursor, false);
return new MVStoreCursor(session, it);
}
@Override
public Cursor findByGeometry(TableFilter filter, SearchRow intersection) {
Session session = filter.getSession();
if (intersection == null) {
return find(session);
}
Iterator<SpatialKey> cursor = spatialMap.findIntersectingKeys(getEnvelope(intersection));
TransactionMap<SpatialKey, Value> map = getMap(session);
Iterator<SpatialKey> it = map.wrapIterator(cursor, false);
return new MVStoreCursor(session, it);
}
private SpatialKey getEnvelope(SearchRow row) {
Value v = row.getValue(columnIds[0]);
Geometry g = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getGeometry();
Envelope env = g.getEnvelopeInternal();
return new SpatialKey(row.getKey(),
(float) env.getMinX(), (float) env.getMaxX(),
(float) env.getMinY(), (float) env.getMaxY());
}
/**
* Get the row with the given index key.
*
* @param array the index key
* @return the row
*/
SearchRow getRow(SpatialKey key) {
SearchRow searchRow = mvTable.getTemplateRow();
searchRow.setKey(key.getId());
return searchRow;
}
@Override
public MVTable getTable() {
return mvTable;
}
@Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
return getCostRangeIndex(masks, table.getRowCountApproximation(), filter, sortOrder);
}
@Override
protected long getCostRangeIndex(int[] masks, long rowCount, TableFilter filter, SortOrder sortOrder) {
rowCount += Constants.COST_ROW_OFFSET;
long cost = 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 + rowCount / 4;
}
}
return cost;
}
@Override
public void remove(Session session) {
TransactionMap<SpatialKey, Value> map = getMap(session);
if (!map.isClosed()) {
Transaction t = mvTable.getTransaction(session);
t.removeMap(map);
}
}
@Override
public void truncate(Session session) {
TransactionMap<SpatialKey, Value> map = getMap(session);
map.clear();
}
@Override
public boolean canGetFirstOrLast() {
return true;
}
@Override
public Cursor findFirstOrLast(Session session, boolean first) {
if (!first) {
throw DbException.throwInternalError("Spatial Index can only be fetch in ascending order");
}
return find(session);
}
@Override
public boolean needRebuild() {
try {
return dataMap.map.sizeAsLong() == 0;
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
}
@Override
public long getRowCount(Session session) {
TransactionMap<SpatialKey, Value> map = getMap(session);
return map.sizeAsLong();
}
@Override
public long getRowCountApproximation() {
try {
return dataMap.map.sizeAsLong();
} catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
}
@Override
public long getDiskSpaceUsed() {
// TODO estimate disk space usage
return 0;
}
@Override
public void checkRename() {
// ok
}
/**
* Get the map to store the data.
*
* @param session the session
* @return the map
*/
TransactionMap<SpatialKey, Value> getMap(Session session) {
if (session == null) {
return dataMap;
}
Transaction t = mvTable.getTransaction(session);
return dataMap.getInstance(t, Long.MAX_VALUE);
}
/**
* A cursor.
*/
class MVStoreCursor implements Cursor {
private final Session session;
private final Iterator<SpatialKey> it;
private SpatialKey current;
private SearchRow searchRow;
private Row row;
public MVStoreCursor(Session session, Iterator<SpatialKey> it) {
this.session = session;
this.it = it;
}
@Override
public Row get() {
if (row == null) {
SearchRow r = getSearchRow();
if (r != null) {
row = mvTable.getRow(session, r.getKey());
}
}
return row;
}
@Override
public SearchRow getSearchRow() {
if (searchRow == null) {
if (current != null) {
searchRow = getRow(current);
}
}
return searchRow;
}
@Override
public boolean next() {
current = it.next();
searchRow = null;
row = null;
return current != null;
}
@Override
public boolean previous() {
throw DbException.getUnsupportedException("previous");
}
}
}
...@@ -26,7 +26,6 @@ import org.h2.index.Cursor; ...@@ -26,7 +26,6 @@ import org.h2.index.Cursor;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.index.IndexType; import org.h2.index.IndexType;
import org.h2.index.MultiVersionIndex; import org.h2.index.MultiVersionIndex;
import org.h2.index.SpatialTreeIndex;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.mvstore.db.TransactionStore.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
...@@ -399,9 +398,9 @@ public class MVTable extends TableBase { ...@@ -399,9 +398,9 @@ public class MVTable extends TableBase {
index = new MVDelegateIndex(this, indexId, index = new MVDelegateIndex(this, indexId,
indexName, primaryIndex, indexType); indexName, primaryIndex, indexType);
} else if (indexType.isSpatial()) { } else if (indexType.isSpatial()) {
int todo; index = new MVSpatialIndex(session.getDatabase(),
index = new SpatialTreeIndex(this, indexId, indexName, cols, this, indexId,
indexType, true, create, session); indexName, cols, indexType);
} else { } else {
index = new MVSecondaryIndex(session.getDatabase(), index = new MVSecondaryIndex(session.getDatabase(),
this, indexId, this, indexId,
......
...@@ -173,10 +173,10 @@ public class MVTableEngine implements TableEngine { ...@@ -173,10 +173,10 @@ public class MVTableEngine implements TableEngine {
if (s == null || s.isReadOnly()) { if (s == null || s.isReadOnly()) {
return; return;
} }
store.commit(); if (!store.compact(50)) {
store.compact(50);
store.store(); store.store();
} }
}
/** /**
* Close the store, without persisting changes. * Close the store, without persisting changes.
......
...@@ -170,7 +170,7 @@ public class TransactionStore { ...@@ -170,7 +170,7 @@ public class TransactionStore {
public synchronized void close() { public synchronized void close() {
// to avoid losing transaction ids // to avoid losing transaction ids
settings.put(LAST_TRANSACTION_ID, "" + lastTransactionId); settings.put(LAST_TRANSACTION_ID, "" + lastTransactionId);
store.commit(); store.store();
} }
/** /**
...@@ -658,9 +658,8 @@ public class TransactionStore { ...@@ -658,9 +658,8 @@ public class TransactionStore {
return new TransactionMap<K, V>(this, map, mapId); return new TransactionMap<K, V>(this, map, mapId);
} }
public <K, V> TransactionMap<K, V> openMap(String name, MVMap.Builder<K, VersionedValue> builder) { public <K, V> TransactionMap<K, V> openMap(MVMap<K, VersionedValue> map) {
checkNotClosed(); checkNotClosed();
MVMap<K, VersionedValue> map = store.store.openMap(name, builder);
int mapId = map.getId(); int mapId = map.getId();
return new TransactionMap<K, V>(this, map, mapId); return new TransactionMap<K, V>(this, map, mapId);
} }
...@@ -1214,11 +1213,11 @@ public class TransactionStore { ...@@ -1214,11 +1213,11 @@ public class TransactionStore {
/** /**
* Iterate over keys. * Iterate over keys.
* *
* @param cursor the wrapped cursor * @param iterator the iterator to wrap
* @param includeUncommitted whether uncommitted entries should be included * @param includeUncommitted whether uncommitted entries should be included
* @return the iterator * @return the iterator
*/ */
private Iterator<K> wrapIterator(final Cursor<K> cursor, final boolean includeUncommitted) { public Iterator<K> wrapIterator(final Iterator<K> iterator, final boolean includeUncommitted) {
return new Iterator<K>() { return new Iterator<K>() {
private K current; private K current;
...@@ -1227,8 +1226,8 @@ public class TransactionStore { ...@@ -1227,8 +1226,8 @@ public class TransactionStore {
} }
private void fetchNext() { private void fetchNext() {
while (cursor.hasNext()) { while (iterator.hasNext()) {
current = cursor.next(); current = iterator.next();
if (includeUncommitted) { if (includeUncommitted) {
return; return;
} }
......
...@@ -17,6 +17,8 @@ import org.h2.constant.ErrorCode; ...@@ -17,6 +17,8 @@ import org.h2.constant.ErrorCode;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
import org.h2.mvstore.WriteBuffer; import org.h2.mvstore.WriteBuffer;
import org.h2.mvstore.rtree.SpatialDataType;
import org.h2.mvstore.rtree.SpatialKey;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.store.DataHandler; import org.h2.store.DataHandler;
...@@ -68,10 +70,12 @@ public class ValueDataType implements DataType { ...@@ -68,10 +70,12 @@ public class ValueDataType implements DataType {
private static final int LONG_NEG = 67; private static final int LONG_NEG = 67;
private static final int STRING_0_31 = 68; private static final int STRING_0_31 = 68;
private static final int BYTES_0_31 = 100; private static final int BYTES_0_31 = 100;
private static final int SPATIAL_KEY_2D = 132;
final DataHandler handler; final DataHandler handler;
final CompareMode compareMode; final CompareMode compareMode;
final int[] sortTypes; final int[] sortTypes;
final SpatialDataType spatialType = new SpatialDataType(2);
public ValueDataType(CompareMode compareMode, DataHandler handler, int[] sortTypes) { public ValueDataType(CompareMode compareMode, DataHandler handler, int[] sortTypes) {
this.compareMode = compareMode; this.compareMode = compareMode;
...@@ -134,6 +138,9 @@ public class ValueDataType implements DataType { ...@@ -134,6 +138,9 @@ public class ValueDataType implements DataType {
@Override @Override
public int getMemory(Object obj) { public int getMemory(Object obj) {
if (obj instanceof SpatialKey) {
return spatialType.getMemory(obj);
}
return getMemory((Value) obj); return getMemory((Value) obj);
} }
...@@ -142,12 +149,17 @@ public class ValueDataType implements DataType { ...@@ -142,12 +149,17 @@ public class ValueDataType implements DataType {
} }
@Override @Override
public Value read(ByteBuffer buff) { public Object read(ByteBuffer buff) {
return readValue(buff); return readValue(buff);
} }
@Override @Override
public void write(WriteBuffer buff, Object obj) { public void write(WriteBuffer buff, Object obj) {
if (obj instanceof SpatialKey) {
buff.put((byte) SPATIAL_KEY_2D);
spatialType.write(buff, obj);
return;
}
Value x = (Value) obj; Value x = (Value) obj;
writeValue(buff, x); writeValue(buff, x);
} }
...@@ -418,7 +430,7 @@ public class ValueDataType implements DataType { ...@@ -418,7 +430,7 @@ public class ValueDataType implements DataType {
* *
* @return the value * @return the value
*/ */
private Value readValue(ByteBuffer buff) { private Object readValue(ByteBuffer buff) {
int type = buff.get() & 255; int type = buff.get() & 255;
switch (type) { switch (type) {
case Value.NULL: case Value.NULL:
...@@ -440,9 +452,9 @@ public class ValueDataType implements DataType { ...@@ -440,9 +452,9 @@ public class ValueDataType implements DataType {
case Value.SHORT: case Value.SHORT:
return ValueShort.get(buff.getShort()); return ValueShort.get(buff.getShort());
case DECIMAL_0_1: case DECIMAL_0_1:
return (ValueDecimal) ValueDecimal.ZERO; return ValueDecimal.ZERO;
case DECIMAL_0_1 + 1: case DECIMAL_0_1 + 1:
return (ValueDecimal) ValueDecimal.ONE; return ValueDecimal.ONE;
case DECIMAL_SMALL_0: case DECIMAL_SMALL_0:
return ValueDecimal.get(BigDecimal.valueOf(readVarLong(buff))); return ValueDecimal.get(BigDecimal.valueOf(readVarLong(buff)));
case DECIMAL_SMALL: { case DECIMAL_SMALL: {
...@@ -537,7 +549,7 @@ public class ValueDataType implements DataType { ...@@ -537,7 +549,7 @@ public class ValueDataType implements DataType {
int len = readVarInt(buff); int len = readVarInt(buff);
Value[] list = new Value[len]; Value[] list = new Value[len];
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
list[i] = readValue(buff); list[i] = (Value) readValue(buff);
} }
return ValueArray.get(list); return ValueArray.get(list);
} }
...@@ -553,7 +565,7 @@ public class ValueDataType implements DataType { ...@@ -553,7 +565,7 @@ public class ValueDataType implements DataType {
} }
Object[] o = new Object[columns]; Object[] o = new Object[columns];
for (int i = 0; i < columns; i++) { for (int i = 0; i < columns; i++) {
o[i] = readValue(buff).getObject(); o[i] = ((Value) readValue(buff)).getObject();
} }
rs.addRow(o); rs.addRow(o);
} }
...@@ -565,6 +577,8 @@ public class ValueDataType implements DataType { ...@@ -565,6 +577,8 @@ public class ValueDataType implements DataType {
buff.get(b, 0, len); buff.get(b, 0, len);
return ValueGeometry.get(b); return ValueGeometry.get(b);
} }
case SPATIAL_KEY_2D:
return spatialType.read(buff);
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);
......
...@@ -478,7 +478,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> { ...@@ -478,7 +478,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
/** /**
* A cursor to iterate over a subset of the keys. * A cursor to iterate over a subset of the keys.
*/ */
static class RTreeCursor implements Iterator<SpatialKey> { public static class RTreeCursor implements Iterator<SpatialKey> {
private final SpatialKey filter; private final SpatialKey filter;
private CursorPos pos; private CursorPos pos;
......
...@@ -91,10 +91,25 @@ public class SpatialKey { ...@@ -91,10 +91,25 @@ public class SpatialKey {
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (!(other instanceof SpatialKey)) { if (other == this) {
return true;
} else if (!(other instanceof SpatialKey)) {
return false; return false;
} }
SpatialKey o = (SpatialKey) other; SpatialKey o = (SpatialKey) other;
if (id != o.id) {
return false;
}
return equalsIgnoringId(o);
}
/**
* Check whether two objects are equals, but do not compare the id fields.
*
* @param o the other key
* @return true if the contents are the same
*/
public boolean equalsIgnoringId(SpatialKey o) {
return Arrays.equals(minMax, o.minMax); return Arrays.equals(minMax, o.minMax);
} }
......
...@@ -45,7 +45,7 @@ public class TestSpatial extends TestBase { ...@@ -45,7 +45,7 @@ public class TestSpatial extends TestBase {
@Override @Override
public void test() throws SQLException { public void test() throws SQLException {
if (config.mvcc) { if (!config.mvStore && config.mvcc) {
return; return;
} }
if (DataType.GEOMETRY_CLASS != null) { if (DataType.GEOMETRY_CLASS != null) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论