org.h2.mvstore.rtree.MVRTreeMap Maven / Gradle / Ivy
/*
* Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.mvstore.rtree;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import org.h2.mvstore.CursorPos;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap;
import org.h2.mvstore.Page;
import org.h2.mvstore.RootReference;
import org.h2.mvstore.type.DataType;
/**
* An r-tree implementation. It supports both the linear and the quadratic split
* algorithm.
*
* @param the value class
*/
public final class MVRTreeMap extends MVMap {
/**
* The spatial key type.
*/
final SpatialDataType keyType;
private boolean quadraticSplit;
public MVRTreeMap(Map config) {
super(config);
keyType = (SpatialDataType) config.get("key");
quadraticSplit = Boolean.valueOf(String.valueOf(config.get("quadraticSplit")));
}
private MVRTreeMap(MVRTreeMap source) {
super(source);
this.keyType = source.keyType;
this.quadraticSplit = source.quadraticSplit;
}
@Override
public MVRTreeMap cloneIt() {
return new MVRTreeMap<>(this);
}
/**
* Iterate over all keys that have an intersection with the given rectangle.
*
* @param x the rectangle
* @return the iterator
*/
public RTreeCursor findIntersectingKeys(SpatialKey x) {
return new RTreeCursor(getRootPage(), x) {
@Override
protected boolean check(boolean leaf, SpatialKey key,
SpatialKey test) {
return keyType.isOverlap(key, test);
}
};
}
/**
* Iterate over all keys that are fully contained within the given
* rectangle.
*
* @param x the rectangle
* @return the iterator
*/
public RTreeCursor findContainedKeys(SpatialKey x) {
return new RTreeCursor(getRootPage(), x) {
@Override
protected boolean check(boolean leaf, SpatialKey key,
SpatialKey test) {
if (leaf) {
return keyType.isInside(key, test);
}
return keyType.isOverlap(key, test);
}
};
}
private boolean contains(Page p, int index, Object key) {
return keyType.contains(p.getKey(index), key);
}
/**
* Get the object for the given key. An exact match is required.
*
* @param p the page
* @param key the key
* @return the value, or null if not found
*/
@SuppressWarnings("unchecked")
@Override
public V get(Page p, Object key) {
int keyCount = p.getKeyCount();
if (!p.isLeaf()) {
for (int i = 0; i < keyCount; i++) {
if (contains(p, i, key)) {
V o = get(p.getChildPage(i), key);
if (o != null) {
return o;
}
}
}
} else {
for (int i = 0; i < keyCount; i++) {
if (keyType.equals(p.getKey(i), key)) {
return (V)p.getValue(i);
}
}
}
return null;
}
/**
* Remove a key-value pair, if the key exists.
*
* @param key the key (may not be null)
* @return the old value if the key existed, or null otherwise
*/
@Override
public V remove(Object key) {
return operate((SpatialKey) key, null, DecisionMaker.REMOVE);
}
@Override
public V operate(SpatialKey key, V value, DecisionMaker super V> decisionMaker) {
beforeWrite();
int attempt = 0;
while(true) {
++attempt;
RootReference rootReference = flushAndGetRoot();
Page p = rootReference.root.copy(true);
V result = operate(p, key, value, decisionMaker);
if (!p.isLeaf() && p.getTotalCount() == 0) {
p.removePage();
p = createEmptyLeaf();
} else if (p.getKeyCount() > store.getKeysPerPage() || p.getMemory() > store.getMaxPageSize()
&& p.getKeyCount() > 3) {
// only possible if this is the root, else we would have
// split earlier (this requires pageSplitSize is fixed)
long totalCount = p.getTotalCount();
Page split = split(p);
Object k1 = getBounds(p);
Object k2 = getBounds(split);
Object[] keys = {k1, k2};
Page.PageReference[] children = {
new Page.PageReference(p),
new Page.PageReference(split),
Page.PageReference.EMPTY
};
p = Page.createNode(this, keys, children, totalCount, 0);
if(store.getFileStore() != null) {
store.registerUnsavedPage(p.getMemory());
}
}
if(updateRoot(rootReference, p, attempt)) {
return result;
}
decisionMaker.reset();
}
}
@SuppressWarnings("unchecked")
private V operate(Page p, Object key, V value, DecisionMaker super V> decisionMaker) {
V result = null;
if (p.isLeaf()) {
int index = -1;
int keyCount = p.getKeyCount();
for (int i = 0; i < keyCount; i++) {
if (keyType.equals(p.getKey(i), key)) {
index = i;
}
}
result = index < 0 ? null : (V)p.getValue(index);
Decision decision = decisionMaker.decide(result, value);
switch (decision) {
case REPEAT: break;
case ABORT: break;
case REMOVE:
if(index >= 0) {
p.remove(index);
}
break;
case PUT:
value = decisionMaker.selectValue(result, value);
if(index < 0) {
p.insertLeaf(p.getKeyCount(), key, value);
} else {
p.setKey(index, key);
p.setValue(index, value);
}
break;
}
return result;
}
// p is a node
if(value == null)
{
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page cOld = p.getChildPage(i);
// this will mark the old page as deleted
// so we need to update the parent in any case
// (otherwise the old page might be deleted again)
Page c = cOld.copy(true);
long oldSize = c.getTotalCount();
result = operate(c, key, value, decisionMaker);
p.setChild(i, c);
if (oldSize == c.getTotalCount()) {
decisionMaker.reset();
continue;
}
if (c.getTotalCount() == 0) {
// this child was deleted
p.remove(i);
if (p.getKeyCount() == 0) {
c.removePage();
}
break;
}
Object oldBounds = p.getKey(i);
if (!keyType.isInside(key, oldBounds)) {
p.setKey(i, getBounds(c));
}
break;
}
}
} else {
int index = -1;
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page c = p.getChildPage(i);
if(get(c, key) != null) {
index = i;
break;
}
if(index < 0) {
index = i;
}
}
}
if (index < 0) {
// a new entry, we don't know where to add yet
float min = Float.MAX_VALUE;
for (int i = 0; i < p.getKeyCount(); i++) {
Object k = p.getKey(i);
float areaIncrease = keyType.getAreaIncrease(k, key);
if (areaIncrease < min) {
index = i;
min = areaIncrease;
}
}
}
Page c = p.getChildPage(index).copy(true);
if (c.getKeyCount() > store.getKeysPerPage() || c.getMemory() > store.getMaxPageSize()
&& c.getKeyCount() > 4) {
// split on the way down
Page split = split(c);
p.setKey(index, getBounds(c));
p.setChild(index, c);
p.insertNode(index, getBounds(split), split);
// now we are not sure where to add
result = operate(p, key, value, decisionMaker);
} else {
result = operate(c, key, value, decisionMaker);
Object bounds = p.getKey(index);
if (!keyType.contains(bounds, key)) {
bounds = keyType.createBoundingBox(bounds);
keyType.increaseBounds(bounds, key);
p.setKey(index, bounds);
}
p.setChild(index, c);
}
}
return result;
}
private Object getBounds(Page x) {
Object bounds = keyType.createBoundingBox(x.getKey(0));
int keyCount = x.getKeyCount();
for (int i = 1; i < keyCount; i++) {
keyType.increaseBounds(bounds, x.getKey(i));
}
return bounds;
}
@Override
public V put(SpatialKey key, V value) {
return operate(key, value, DecisionMaker.PUT);
}
/**
* Add a given key-value pair. The key should not exist (if it exists, the
* result is undefined).
*
* @param key the key
* @param value the value
*/
public void add(SpatialKey key, V value) {
operate(key, value, DecisionMaker.PUT);
}
private Page split(Page p) {
return quadraticSplit ?
splitQuadratic(p) :
splitLinear(p);
}
private Page splitLinear(Page p) {
int keyCount = p.getKeyCount();
ArrayList
© 2015 - 2024 Weber Informatics LLC | Privacy Policy