package org.h2.index;
import static org.h2.util.geometry.GeometryUtils.MAX_X;
import static org.h2.util.geometry.GeometryUtils.MAX_Y;
import static org.h2.util.geometry.GeometryUtils.MIN_X;
import static org.h2.util.geometry.GeometryUtils.MIN_Y;
import java.util.Iterator;
import org.h2.command.dml.AllColumnsForPlan;
import org.h2.engine.Session;
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.SearchRow;
import org.h2.result.SortOrder;
import org.h2.table.Column;
import org.h2.table.IndexColumn;
import org.h2.table.Table;
import org.h2.table.TableFilter;
import org.h2.value.Value;
import org.h2.value.ValueGeometry;
import org.h2.value.ValueNull;
public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
private static final String MAP_PREFIX = "RTREE_";
private final MVRTreeMap<Long> treeMap;
private final MVStore store;
private boolean closed;
private boolean needRebuild;
public SpatialTreeIndex(Table table, int id, String indexName,
IndexColumn[] columns, IndexType indexType, boolean persistent,
boolean create, Session session) {
super(table, id, indexName, columns, indexType);
if (indexType.isUnique()) {
throw DbException.getUnsupportedException("not unique");
}
if (!persistent && !create) {
throw DbException.getUnsupportedException(
"Non persistent index called with create==false");
}
if (columns.length > 1) {
throw DbException.getUnsupportedException(
"can only do one column");
}
if ((columns[0].sortType & SortOrder.DESCENDING) != 0) {
throw DbException.getUnsupportedException(
"cannot do descending");
}
if ((columns[0].sortType & SortOrder.NULLS_FIRST) != 0) {
throw DbException.getUnsupportedException(
"cannot do nulls first");
}
if ((columns[0].sortType & SortOrder.NULLS_LAST) != 0) {
throw DbException.getUnsupportedException(
"cannot do nulls last");
}
this.needRebuild = create;
if (!database.isStarting()) {
if (columns[0].column.getType().getValueType() != Value.GEOMETRY) {
throw DbException.getUnsupportedException(
"spatial index on non-geometry column, " +
columns[0].column.getCreateSQL());
}
}
if (!persistent) {
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().getStore().getMvStore();
treeMap = store.openMap(MAP_PREFIX + getId(),
new MVRTreeMap.Builder<Long>());
if (treeMap.isEmpty()) {
needRebuild = true;
}
}
}
@Override
public void close(Session session) {
store.close();
closed = true;
}
@Override
public void add(Session session, Row row) {
if (closed) {
throw DbException.throwInternalError();
}
treeMap.add(getKey(row), row.getKey());
}
private SpatialKey getKey(SearchRow row) {
if (row == null) {
return null;
}
Value v = row.getValue(columnIds[0]);
double[] env;
if (v == ValueNull.INSTANCE ||
(env = ((ValueGeometry) v.convertTo(Value.GEOMETRY)).getEnvelopeNoCopy()) == null) {
return new SpatialKey(row.getKey());
}
return new SpatialKey(row.getKey(),
(float) env[MIN_X], (float) env[MAX_X], (float) env[MIN_Y], (float) env[MAX_Y]);
}
@Override
public void remove(Session session, Row row) {
if (closed) {
throw DbException.throwInternalError();
}
if (!treeMap.remove(getKey(row), row.getKey())) {
throw DbException.throwInternalError("row not found");
}
}
@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) {
return new SpatialCursor(treeMap.keySet().iterator(), table, session);
}
@Override
public Cursor findByGeometry(TableFilter filter, SearchRow first,
SearchRow last, SearchRow intersection) {
if (intersection == null) {
return find(filter.getSession(), first, last);
}
return new SpatialCursor(
treeMap.findIntersectingKeys(getKey(intersection)), table,
filter.getSession());
}
public static long getCostRangeIndex(int[] masks, Column[] columns) {
if (columns.length == 0) {
return Long.MAX_VALUE;
}
for (Column column : columns) {
int index = column.getColumnId();
int mask = masks[index];
if ((mask & IndexCondition.SPATIAL_INTERSECTS) != IndexCondition.SPATIAL_INTERSECTS) {
return Long.MAX_VALUE;
}
}
return 2;
}
@Override
public double getCost(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder,
AllColumnsForPlan allColumnsSet) {
return getCostRangeIndex(masks, columns);
}
@Override
public void remove(Session session) {
if (!treeMap.isClosed()) {
store.removeMap(treeMap);
}
}
@Override
public void truncate(Session session) {
treeMap.clear();
}
@Override
public void checkRename() {
}
@Override
public boolean needRebuild() {
return needRebuild;
}
@Override
public boolean canGetFirstOrLast() {
return true;
}
@Override
public Cursor findFirstOrLast(Session session, boolean first) {
if (closed) {
throw DbException.throwInternalError(toString());
}
if (!first) {
throw DbException.throwInternalError(
"Spatial Index can only be fetch by ascending order");
}
return find(session);
}
@Override
public long getRowCount(Session session) {
return treeMap.sizeAsLong();
}
@Override
public long getRowCountApproximation() {
return treeMap.sizeAsLong();
}
@Override
public long getDiskSpaceUsed() {
return 0;
}
private static final class SpatialCursor implements Cursor {
private final Iterator<SpatialKey> it;
private SpatialKey current;
private final Table table;
private final Session session;
public SpatialCursor(Iterator<SpatialKey> it, Table table, Session session) {
this.it = it;
this.table = table;
this.session = session;
}
@Override
public Row get() {
return table.getRow(session, current.getId());
}
@Override
public SearchRow getSearchRow() {
return get();
}
@Override
public boolean next() {
if (!it.hasNext()) {
return false;
}
current = it.next();
return true;
}
@Override
public boolean previous() {
return false;
}
}
}