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

A persistent multi-version map (work in progress): r-tree

上级 b1edabaa
......@@ -19,10 +19,6 @@ class IntegerType implements DataType {
return ((Integer) a).compareTo((Integer) b);
}
public int length(Object obj) {
return DataUtils.getVarIntLen((Integer) obj);
}
public int getMaxLength(Object obj) {
return DataUtils.MAX_VAR_INT_LEN;
}
......
......@@ -46,16 +46,6 @@ public class RowType implements DataType {
return 0;
}
public int length(Object obj) {
Object[] x = (Object[]) obj;
int len = x.length;
int result = DataUtils.getVarIntLen(len);
for (int i = 0; i < len; i++) {
result += types[i].length(x[i]);
}
return result;
}
public int getMaxLength(Object obj) {
Object[] x = (Object[]) obj;
int len = x.length;
......
......@@ -6,21 +6,365 @@
*/
package org.h2.test.store;
import java.util.ArrayList;
import org.h2.dev.store.btree.BtreeMap;
import org.h2.dev.store.btree.BtreeMapStore;
import org.h2.dev.store.btree.CursorPos;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.Page;
/**
* A stored r-tree.
* An r-tree implementation.
*
* @param <K> the key class
* @param <V> the value class
*/
public class RtreeMap<K, V> extends BtreeMap<K, V> {
private final SpatialType keyType;
RtreeMap(BtreeMapStore store, int id, String name, DataType keyType,
DataType valueType, long createVersion) {
super(store, id, name, keyType, valueType, createVersion);
this.keyType = (SpatialType) keyType;
}
@SuppressWarnings("unchecked")
public V get(Object key) {
checkOpen();
if (root == null) {
return null;
}
return (V) getSpatial(root, key);
}
private boolean overlap(Page p, int index, Object key) {
return keyType.isOverlap(p.getKey(index), key);
}
private boolean contains(Page p, int index, Object key) {
return keyType.contains(p.getKey(index), key);
}
private float getAreaIncrease(Page p, int index, Object key) {
return keyType.getAreaIncrease(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
*/
protected Object getSpatial(Page p, Object key) {
if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Object o = getSpatial(p.getChildPage(i), key);
if (o != null) {
return o;
}
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
return p.getValue(i);
}
}
}
return null;
}
protected Page getPage(K key) {
if (root == null) {
return null;
}
return getPageSpatial(root, key);
}
protected Page getPageSpatial(Page p, Object key) {
if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page x = getPageSpatial(p.getChildPage(i), key);
if (x != null) {
return x;
}
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
return p;
}
}
}
return null;
}
protected Page set(Page p, long writeVersion, Object key, Object value) {
if (p == null) {
throw KEY_NOT_FOUND;
}
if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page c = p.getChildPage(i);
Page c2 = set(c, writeVersion, key, value);
if (c != c2) {
p = p.copyOnWrite(writeVersion);
setChildUpdateBox(p, i, c2);
break;
}
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
p = p.copyOnWrite(writeVersion);
p.setValue(i, value);
break;
}
}
}
return p;
}
protected Page removeExisting(Page p, long writeVersion, Object key) {
if (p == null) {
throw KEY_NOT_FOUND;
}
if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) {
Page c = p.getChildPage(i);
Page c2 = removeExisting(c, writeVersion, key);
if (c != c2) {
p = p.copyOnWrite(writeVersion);
setChildUpdateBox(p, i, c2);
break;
}
}
}
} else {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
p = p.copyOnWrite(writeVersion);
p.remove(i);
break;
}
}
}
return p;
}
/**
* Set the child and update the bounding box.
*
* @param p the parent (this page is changed)
* @param index the child index
* @param c the child page
*/
private void setChildUpdateBox(Page p, int index, Page c) {
p.setKey(index, getBounds(c));
p.setChild(index, c);
}
private SpatialKey getBounds(Page x) {
SpatialKey bounds = keyType.copy((SpatialKey) x.getKey(0));
for (int i = 1; i < x.getKeyCount(); i++) {
keyType.increase(bounds, (SpatialKey) x.getKey(i));
}
return bounds;
}
protected Page add(Page p, long writeVersion, Object key, Object value) {
if (p == null) {
Object[] keys = { key };
Object[] values = { value };
p = Page.create(this, writeVersion, keys, values, null, null, 1);
return p;
}
if (p.getKeyCount() >= store.getMaxPageSize()) {
// only possible if this is the root,
// otherwise we would have split earlier
p = p.copyOnWrite(writeVersion);
long totalSize = p.getTotalSize();
Page split = split(p, writeVersion);
Object[] keys = { getBounds(p), getBounds(split) };
long[] children = { p.getPos(), split.getPos(), 0 };
long[] childrenSize = { p.getTotalSize(), split.getTotalSize(), 0 };
p = Page.create(this, writeVersion, keys, null, children, childrenSize,
totalSize);
// now p is a node; insert continues
} else if (p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) {
throw KEY_ALREADY_EXISTS;
}
}
p.insert(p.getKeyCount(), key, value, 0, 0);
return p;
}
// p is a node
float min = Float.MAX_VALUE;
int index = 0;
for (int i = 0; i < p.getKeyCount(); i++) {
float areaIncrease = getAreaIncrease(p, i, key);
if (areaIncrease < min) {
index = i;
min = areaIncrease;
}
}
Page c = p.getChildPage(index);
if (c.getKeyCount() >= store.getMaxPageSize()) {
// split on the way down
c = c.copyOnWrite(writeVersion);
Page split = split(c, writeVersion);
p = p.copyOnWrite(writeVersion);
setChildUpdateBox(p, index, c);
p.insert(index, getBounds(split), null, split.getPos(), split.getTotalSize());
// now we are not sure where to add
return add(p, writeVersion, key, value);
}
Page c2 = add(c, writeVersion, key, value);
p = p.copyOnWrite(writeVersion);
// the child might be the same, but not the size
setChildUpdateBox(p, index, c2);
return p;
}
private Page split(Page p, long writeVersion) {
// quadratic algorithm
Object[] values = p.isLeaf() ? new Object[0] : null;
long[] c = p.isLeaf() ? null : new long[1];
Page split = Page.create(this, writeVersion, new Object[0],
values, c, c, 0);
Page newP = Page.create(this, writeVersion, new Object[0],
values, c, c, 0);
float largest = Float.MIN_VALUE;
int iBest = 0, jBest = 0;
for (int i = 0; i < p.getKeyCount(); i++) {
Object oi = p.getKey(i);
for (int j = 0; j < p.getKeyCount(); j++) {
if (i == j) {
continue;
}
Object oj = p.getKey(j);
float area = keyType.getCombinedArea(oi, oj);
if (area > largest) {
largest = area;
iBest = i;
jBest = j;
}
}
}
move(p, newP, iBest);
if (iBest < jBest) {
jBest--;
}
move(p, split, jBest);
while (p.getKeyCount() > 0) {
float diff = 0, bestA = 0, bestB = 0;
int best = 0;
SpatialKey ba = getBounds(newP);
SpatialKey bb = getBounds(split);
for (int i = 0; i < p.getKeyCount(); i++) {
Object o = p.getKey(i);
float a = keyType.getAreaIncrease(ba, o);
float b = keyType.getAreaIncrease(bb, o);
float d = Math.abs(a - b);
if (d > diff) {
diff = d;
bestA = a;
bestB = b;
best = i;
}
}
if (bestA < bestB) {
move(p, newP, best);
} else {
move(p, split, best);
}
}
while (newP.getKeyCount() > 0) {
move(newP, p, 0);
}
return split;
}
private static void move(Page source, Page target, int sourceIndex) {
Object k = source.getKey(sourceIndex);
Object v = source.isLeaf() ? source.getValue(sourceIndex) : null;
long c = source.isLeaf() ? 0 : source.getChildPage(sourceIndex).getPos();
long count = source.isLeaf() ? 0 : source.getCounts(sourceIndex);
target.insert(0, k, v, c, count);
source.remove(sourceIndex);
}
public void addNodeKeys(ArrayList<SpatialKey> list, Page p) {
if (p != null && !p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) {
list.add((SpatialKey) p.getKey(i));
addNodeKeys(list, p.getChildPage(i));
}
}
}
/**
* Go to the first element for the given key.
*
* @param p the current page
* @param parents the stack of parent page positions
* @param key the key
*/
protected void min(Page p, ArrayList<CursorPos> parents, Object key) {
while (p != null) {
CursorPos c = new CursorPos();
c.page = p;
parents.add(c);
if (p.isLeaf()) {
return;
}
p = p.getChildPage(0);
}
}
/**
* Get the next key.
*
* @param parents the stack of parent page positions
* @return the next key
*/
protected Object nextKey(ArrayList<CursorPos> parents) {
if (parents.size() == 0) {
return null;
}
while (true) {
// TODO performance: avoid remove/add pairs if possible
CursorPos p = parents.remove(parents.size() - 1);
int index = p.index++;
if (index < p.page.getKeyCount()) {
parents.add(p);
return p.page.getKey(index);
}
while (true) {
if (parents.size() == 0) {
return null;
}
p = parents.remove(parents.size() - 1);
index = ++p.index;
if (index < p.page.getKeyCount()) {
parents.add(p);
Page x = p.page;
x = x.getChildPage(index);
min(x, parents, null);
break;
}
}
}
}
}
/*
* Copyright 2004-2011 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.test.store;
/**
* A unique spatial key.
*/
public class SpatialKey {
public float[] min;
public float[] max;
public long id;
public static SpatialKey create(long id, float... minMax) {
SpatialKey k = new SpatialKey();
k.id = id;
int dimensions = minMax.length / 2;
k.min = new float[dimensions];
k.max = new float[dimensions];
for (int i = 0; i < dimensions; i++) {
k.min[i] = minMax[i + i];
k.max[i] = minMax[i + i + 1];
}
return k;
}
public String toString() {
StringBuilder buff = new StringBuilder();
buff.append(id).append(": (");
for (int i = 0; i < min.length; i++) {
if (i > 0) {
buff.append(", ");
}
buff.append(min[i]).append('/').append(max[i]);
}
return buff.append(")").toString();
}
}
/*
* Copyright 2004-2011 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.test.store;
import java.nio.ByteBuffer;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.DataUtils;
/**
* A spatial data type. This class supports up to 255 dimensions. Each dimension
* can have a minimum and a maximum value of type float. For each dimension, the
* maximum value is only stored when it is not the same as the minimum.
*/
public class SpatialType implements DataType {
private final int dimensions;
private SpatialType(int dimensions) {
if (dimensions <= 0 || dimensions > 255) {
throw new IllegalArgumentException("Dimensions: " + dimensions);
}
this.dimensions = dimensions;
}
public static SpatialType fromString(String s) {
return new SpatialType(Integer.parseInt(s.substring(1)));
}
@Override
public int compare(Object a, Object b) {
long la = ((SpatialKey) a).id;
long lb = ((SpatialKey) b).id;
return la < lb ? -1 : la > lb ? 1 : 0;
}
public boolean equals(Object a, Object b) {
long la = ((SpatialKey) a).id;
long lb = ((SpatialKey) b).id;
return la == lb;
}
@Override
public int getMaxLength(Object obj) {
return 1 + dimensions * 8 + DataUtils.MAX_VAR_LONG_LEN;
}
@Override
public int getMemory(Object obj) {
return 40 + dimensions * 4;
}
@Override
public void write(ByteBuffer buff, Object obj) {
SpatialKey k = (SpatialKey) obj;
int flags = 0;
for (int i = 0; i < dimensions; i++) {
if (k.min[i] == k.max[i]) {
flags |= 1 << i;
}
}
DataUtils.writeVarInt(buff, flags);
for (int i = 0; i < dimensions; i++) {
buff.putFloat(k.min[i]);
if ((flags & (1 << i)) == 0) {
buff.putFloat(k.max[i]);
}
}
DataUtils.writeVarLong(buff, k.id);
}
@Override
public Object read(ByteBuffer buff) {
SpatialKey k = new SpatialKey();
int flags = DataUtils.readVarInt(buff);
k.min = new float[dimensions];
k.max = new float[dimensions];
for (int i = 0; i < dimensions; i++) {
k.min[i] = buff.getFloat();
if ((flags & (1 << i)) != 0) {
k.max[i] = k.min[i];
} else {
k.max[i] = buff.getFloat();
}
}
k.id = DataUtils.readVarLong(buff);
return k;
}
@Override
public String asString() {
return "s" + dimensions;
}
public boolean isOverlap(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
for (int i = 0; i < dimensions; i++) {
if (a.max[i] < b.min[i] || a.min[i] > b.max[i]) {
return false;
}
}
return true;
}
public SpatialKey copy(SpatialKey old) {
SpatialKey k = new SpatialKey();
k.min = new float[dimensions];
k.max = new float[dimensions];
System.arraycopy(old.min, 0, k.min, 0, dimensions);
System.arraycopy(old.max, 0, k.max, 0, dimensions);
return k;
}
public void increase(SpatialKey bounds, SpatialKey add) {
for (int i = 0; i < dimensions; i++) {
bounds.min[i] = Math.min(bounds.min[i], add.min[i]);
bounds.max[i] = Math.max(bounds.max[i], add.max[i]);
}
}
/**
* Get the area increase by extending a to contain b.
*
* @param objA the bounding box
* @param objB the object
* @return the area
*/
public float getAreaIncrease(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
float areaOld = 1, areaNew = 1;
for (int i = 0; i < dimensions; i++) {
float min = a.min[i];
float max = a.max[i];
areaOld *= max - min;
min = Math.min(min, b.min[i]);
max = Math.max(max, b.max[i]);
areaNew *= max - min;
}
return areaNew - areaOld;
}
public float getCombinedArea(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
float area = 1;
for (int i = 0; i < dimensions; i++) {
float min = Math.min(a.min[i], b.min[i]);
float max = Math.max(a.max[i], b.max[i]);
area *= max - min;
}
return area;
}
/**
* Check whether a contains b.
*
* @param objA the bounding box
* @param objB the object
* @return the area
*/
public boolean contains(Object objA, Object objB) {
SpatialKey a = (SpatialKey) objA;
SpatialKey b = (SpatialKey) objB;
for (int i = 0; i < dimensions; i++) {
if (a.min[i] > b.min[i] || a.max[i] < b.max[i]) {
return false;
}
}
return true;
}
}
......@@ -5,14 +5,27 @@
*/
package org.h2.test.store;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Random;
import java.util.TreeMap;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import org.h2.dev.store.btree.BtreeMap;
import org.h2.dev.store.btree.BtreeMapStore;
import org.h2.jaqu.bytecode.Null;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.util.New;
/**
* Tests the tree map store.
......@@ -29,6 +42,8 @@ public class TestBtreeMapStore extends TestBase {
}
public void test() {
testRtreeMany();
testRtree();
testCustomMapType();
testTruncateFile();
testFastDelete();
......@@ -45,12 +60,124 @@ public class TestBtreeMapStore extends TestBase {
testSimple();
}
private void testRtreeMany() {
String fileName = getBaseDir() + "/testMeta.h3";
FileUtils.delete(fileName);
BtreeMapStore s;
s = openStore(fileName);
// s.setMaxPageSize(100);
RtreeMap<SpatialKey, String> r = s.openMap("data", "r", "s2", "");
Random rand = new Random(1);
for (int i = 0; i < 1000; i++) {
float x = rand.nextFloat(), y = rand.nextFloat();
float p = (float) (rand.nextFloat() * 0.01);
SpatialKey k = SpatialKey.create(i, x - p, x + p, y - p, y + p);
r.put(k, "" + i);
if (i > 0 && i % 10000 == 0) {
render(r, getBaseDir() + "/test.png");
}
}
s.close();
}
private void testRtree() {
String fileName = getBaseDir() + "/testMeta.h3";
FileUtils.delete(fileName);
BtreeMapStore s;
s = openStore(fileName);
RtreeMap<SpatialKey, String> r = s.openMap("data", "r", "s2", "");
add(r, "Bern", 46.57, 7.27, 124381);
add(r, "Basel", 47.34, 7.36, 170903);
add(r, "Zurich", 47.22, 8.33, 376008);
add(r, "Lucerne", 47.03, 8.18, 77491);
add(r, "Geneva", 46.12, 6.09, 191803);
add(r, "Lausanne", 46.31, 6.38, 127821);
add(r, "Winterthur", 47.30, 8.45, 102966);
add(r, "St. Gallen", 47.25, 9.22, 73500);
add(r, "Biel/Bienne", 47.08, 7.15, 51203);
add(r, "Lugano", 46.00, 8.57, 54667);
add(r, "Thun", 46.46, 7.38, 42623);
add(r, "Bellinzona", 46.12, 9.01, 17373);
add(r, "Chur", 46.51, 9.32, 33756);
ArrayList<String> list = New.arrayList();
for (SpatialKey x : r.keySet()) {
list.add(r.get(x));
}
Collections.sort(list);
assertEquals("[Basel, Bellinzona, Bern, Biel/Bienne, Chur, Geneva, " +
"Lausanne, Lucerne, Lugano, St. Gallen, Thun, Winterthur, Zurich]",
list.toString());
// render(r, getBaseDir() + "/test.png");
s.close();
}
private static void add(RtreeMap<SpatialKey, String> r, String name, double y, double x, int population) {
int id = r.size();
float a = (float) ((int) x + (x - (int) x) * 5 / 3);
float b = 50 - (float) ((int) y + (y - (int) y) * 5 / 3);
float s = (float) Math.sqrt(population / 10000000.);
SpatialKey k = SpatialKey.create(id, a - s, a + s, b - s, b + s);
r.put(k, name);
}
private static void render(RtreeMap<SpatialKey, String> r, String fileName) {
int width = 1000, height = 500;
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = (Graphics2D) img.getGraphics();
g2d.setBackground(Color.WHITE);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.BLACK);
SpatialKey b = SpatialKey.create(0, Float.MAX_VALUE, Float.MIN_VALUE,
Float.MAX_VALUE, Float.MIN_VALUE);
for (SpatialKey x : r.keySet()) {
b.min[0] = Math.min(b.min[0], x.min[0]);
b.min[1] = Math.min(b.min[1], x.min[1]);
b.max[0] = Math.max(b.max[0], x.max[0]);
b.max[1] = Math.max(b.max[1], x.max[1]);
}
// System.out.println(b);
for (SpatialKey x : r.keySet()) {
int[] rect = scale(b, x, width, height);
g2d.drawRect(rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]);
String s = r.get(x);
g2d.drawChars(s.toCharArray(), 0, s.length(), rect[0], rect[1] - 4);
}
g2d.setColor(Color.red);
ArrayList<SpatialKey> list = New.arrayList();
r.addNodeKeys(list, r.getRoot());
for (SpatialKey x : list) {
int[] rect = scale(b, x, width, height);
g2d.drawRect(rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]);
}
ImageWriter out = ImageIO.getImageWritersByFormatName("png").next();
try {
out.setOutput(new FileImageOutputStream(new File(fileName)));
out.write(img);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static int[] scale(SpatialKey b, SpatialKey x, int width, int height) {
int[] rect = {
(int) ((x.min[0] - b.min[0]) * (width * 0.9) / (b.max[0] - b.min[0]) + width * 0.05),
(int) ((x.min[1] - b.min[1]) * (height * 0.9) / (b.max[1] - b.min[1]) + height * 0.05),
(int) ((x.max[0] - b.min[0]) * (width * 0.9) / (b.max[0] - b.min[0]) + width * 0.05),
(int) ((x.max[1] - b.min[1]) * (height * 0.9) / (b.max[1] - b.min[1]) + height * 0.05),
};
return rect;
}
private void testCustomMapType() {
String fileName = getBaseDir() + "/testMeta.h3";
FileUtils.delete(fileName);
BtreeMapStore s;
s = openStore(fileName);
SequenceMap<Integer, String> seq = s.openMap("data", "s", "i", "").cast();
SequenceMap<Integer, String> seq = s.openMap("data", "s", "i", "");
StringBuilder buff = new StringBuilder();
for (int x : seq.keySet()) {
buff.append(x).append(';');
......
......@@ -5,6 +5,7 @@
*/
package org.h2.test.store;
import java.nio.ByteBuffer;
import org.h2.dev.store.btree.DataUtils;
import org.h2.test.TestBase;
......@@ -23,11 +24,66 @@ public class TestDataUtils extends TestBase {
}
public void test() throws Exception {
testVarIntVarLong();
testCheckValue();
testPagePos();
testEncodeLength();
}
private void testVarIntVarLong() {
ByteBuffer buff = ByteBuffer.allocate(100);
for (long x = 0; x < 1000; x++) {
testVarIntVarLong(buff, x);
testVarIntVarLong(buff, -x);
}
for (long x = Long.MIN_VALUE, i = 0; i < 1000; x++, i++) {
testVarIntVarLong(buff, x);
}
for (long x = Long.MAX_VALUE, i = 0; i < 1000; x--, i++) {
testVarIntVarLong(buff, x);
}
for (int shift = 0; shift < 64; shift++) {
for (long x = 250; x < 260; x++) {
testVarIntVarLong(buff, x << shift);
testVarIntVarLong(buff, -(x << shift));
}
}
// invalid varInt / varLong
// should work, but not read far too much
for (int i = 0; i < 50; i++) {
buff.put((byte) 255);
}
buff.flip();
assertEquals(-1, DataUtils.readVarInt(buff));
assertEquals(5, buff.position());
buff.rewind();
assertEquals(-1, DataUtils.readVarLong(buff));
assertEquals(10, buff.position());
}
private void testVarIntVarLong(ByteBuffer buff, long x) {
int len;
DataUtils.writeVarLong(buff, x);
len = buff.position();
buff.flip();
long y = DataUtils.readVarLong(buff);
assertEquals(y, x);
assertEquals(len, buff.position());
assertEquals(len, DataUtils.getVarLongLen(x));
buff.clear();
int intX = (int) x;
DataUtils.writeVarInt(buff, intX);
len = buff.position();
buff.flip();
int intY = DataUtils.readVarInt(buff);
assertEquals(intY, intX);
assertEquals(len, buff.position());
assertEquals(len, DataUtils.getVarIntLen(intX));
buff.clear();
}
private void testCheckValue() {
// 0 xor 0 = 0
assertEquals(0, DataUtils.getCheckValue(0));
......
......@@ -22,8 +22,11 @@ public class TestMapFactory implements MapFactory {
long createVersion) {
if (mapType.equals("s")) {
return new SequenceMap<K, V>(store, id, name, keyType, valueType, createVersion);
} else if (mapType.equals("r")) {
return new RtreeMap<K, V>(store, id, name, keyType, valueType, createVersion);
} else {
throw new RuntimeException("Unsupported map type " + mapType);
}
throw new RuntimeException("Unsupported map type " + mapType);
}
@Override
......@@ -36,6 +39,8 @@ public class TestMapFactory implements MapFactory {
return new IntegerType();
case 'r':
return RowType.fromString(s, this);
case 's':
return SpatialType.fromString(s);
}
throw new RuntimeException("Unknown data type " + s);
}
......
......@@ -20,11 +20,14 @@ import java.util.TreeMap;
*/
public class BtreeMap<K, V> {
static final IllegalArgumentException KEY_NOT_FOUND = new IllegalArgumentException(
protected static final IllegalArgumentException KEY_NOT_FOUND = new IllegalArgumentException(
"Key not found");
static final IllegalArgumentException KEY_ALREADY_EXISTS = new IllegalArgumentException(
protected static final IllegalArgumentException KEY_ALREADY_EXISTS = new IllegalArgumentException(
"Key already exists");
protected Page root;
protected BtreeMapStore store;
private final int id;
private final String name;
private final DataType keyType;
......@@ -36,8 +39,6 @@ public class BtreeMap<K, V> {
* before this version.
*/
private final TreeMap<Long, Page> oldRoots = new TreeMap<Long, Page>();
private BtreeMapStore store;
private Page root;
private boolean readOnly;
protected BtreeMap(BtreeMapStore store, int id, String name,
......@@ -79,8 +80,7 @@ public class BtreeMap<K, V> {
* @throws InvalidArgumentException if this key does not exist (without
* stack trace)
*/
private Page set(Page p, long writeVersion, Object key,
Object value) {
protected Page set(Page p, long writeVersion, Object key, Object value) {
if (p == null) {
throw KEY_NOT_FOUND;
}
......@@ -103,7 +103,7 @@ public class BtreeMap<K, V> {
Page c2 = set(c, writeVersion, key, value);
if (c != c2) {
p = p.copyOnWrite(writeVersion);
p.setChild(index, c2.getPos(), c2.getPos());
p.setChild(index, c2);
}
return p;
}
......@@ -120,8 +120,7 @@ public class BtreeMap<K, V> {
* @throws InvalidArgumentException if this key already exists (without
* stack trace)
*/
private Page add(Page p, long writeVersion, Object key,
Object value) {
protected Page add(Page p, long writeVersion, Object key, Object value) {
if (p == null) {
Object[] keys = { key };
Object[] values = { value };
......@@ -167,15 +166,15 @@ public class BtreeMap<K, V> {
Object k = c.getKey(at);
Page split = c.split(at);
p = p.copyOnWrite(writeVersion);
p.setChild(index, c.getPos(), c.getTotalSize());
p.insert(index, k, null, split.getPos(), split.getTotalSize());
p.setChild(index, split);
p.insert(index, k, null, c.getPos(), c.getTotalSize());
// now we are not sure where to add
return add(p, writeVersion, key, value);
}
Page c2 = add(c, writeVersion, key, value);
p = p.copyOnWrite(writeVersion);
// the child might be the same, but not the size
p.setChild(index, c2.getPos(), c2.getTotalSize());
p.setChild(index, c2);
return p;
}
......@@ -201,7 +200,7 @@ public class BtreeMap<K, V> {
* @param parents the stack of parent page positions
* @param key the key
*/
void min(Page p, ArrayList<CursorPos> parents, Object key) {
protected void min(Page p, ArrayList<CursorPos> parents, Object key) {
while (p != null) {
if (!p.isLeaf()) {
int x = key == null ? -1 : p.binarySearch(key);
......@@ -235,7 +234,7 @@ public class BtreeMap<K, V> {
* @param parents the stack of parent page positions
* @return the next key
*/
Object nextKey(ArrayList<CursorPos> parents) {
protected Object nextKey(ArrayList<CursorPos> parents) {
if (parents.size() == 0) {
return null;
}
......@@ -270,7 +269,7 @@ public class BtreeMap<K, V> {
* @param key the key
* @return the value or null
*/
private Object binarySearch(Page p, Object key) {
protected Object binarySearch(Page p, Object key) {
int x = p.binarySearch(key);
if (!p.isLeaf()) {
if (x < 0) {
......@@ -287,7 +286,6 @@ public class BtreeMap<K, V> {
return null;
}
public boolean containsKey(Object key) {
return get(key) != null;
}
......@@ -298,7 +296,7 @@ public class BtreeMap<K, V> {
* @param key the key
* @return the value, or null if not found
*/
Page getPage(K key) {
protected Page getPage(K key) {
if (root == null) {
return null;
}
......@@ -312,7 +310,7 @@ public class BtreeMap<K, V> {
* @param key the key
* @return the page or null
*/
private Page binarySearchPage(Page p, Object key) {
protected Page binarySearchPage(Page p, Object key) {
int x = p.binarySearch(key);
if (!p.isLeaf()) {
if (x < 0) {
......@@ -379,7 +377,6 @@ public class BtreeMap<K, V> {
}
}
/**
* Remove an existing key-value pair.
*
......@@ -389,7 +386,7 @@ public class BtreeMap<K, V> {
* @return the new root page (null if empty)
* @throws InvalidArgumentException if not found (without stack trace)
*/
private Page removeExisting(Page p, long writeVersion, Object key) {
protected Page removeExisting(Page p, long writeVersion, Object key) {
if (p == null) {
throw KEY_NOT_FOUND;
}
......@@ -397,7 +394,7 @@ public class BtreeMap<K, V> {
if (p.isLeaf()) {
if (index >= 0) {
if (p.getKeyCount() == 1) {
store.removePage(p.getPos());
removePage(p);
return null;
}
p = p.copyOnWrite(writeVersion);
......@@ -420,16 +417,16 @@ public class BtreeMap<K, V> {
// this child was deleted
p.remove(index);
if (p.getKeyCount() == 0) {
store.removePage(p.getPos());
removePage(p);
p = p.getChildPage(0);
}
} else {
p.setChild(index, c2.getPos(), c2.getTotalSize());
p.setChild(index, c2);
}
return p;
}
private void markChanged(Page oldRoot) {
protected void markChanged(Page oldRoot) {
if (oldRoot != root) {
long v = store.getCurrentVersion();
if (!oldRoots.containsKey(v)) {
......@@ -530,7 +527,7 @@ public class BtreeMap<K, V> {
*
* @return the root page
*/
Page getRoot() {
public Page getRoot() {
return root;
}
......@@ -585,13 +582,13 @@ public class BtreeMap<K, V> {
return readOnly;
}
private void checkOpen() {
protected void checkOpen() {
if (store == null) {
throw new IllegalStateException("This map is closed");
}
}
private void checkWrite() {
protected void checkWrite() {
if (readOnly) {
checkOpen();
throw new IllegalStateException("This map is read-only");
......@@ -631,9 +628,8 @@ public class BtreeMap<K, V> {
return createVersion;
}
@SuppressWarnings("unchecked")
public <M> M cast() {
return (M) this;
protected void removePage(Page p) {
store.removePage(p.getPos());
}
}
......@@ -38,7 +38,6 @@ header:
blockSize=4096
TODO:
- support custom map types: b-tree, r-tree
- ability to diff / merge versions
- map.getVersion and opening old maps read-only
- limited support for writing to old versions (branches)
......@@ -56,6 +55,9 @@ TODO:
- support large binaries
- support stores that span multiple files (chunks stored in other files)
- triggers
- compare with newest version of IndexedDb
- support database version / schema version
- implement more counted b-tree (skip, get positions)
*/
......@@ -142,8 +144,7 @@ public class BtreeMapStore {
/**
* Open a map.
*
* @param <K> the key type
* @param <V> the value type
* @param <T> the map type
* @param name the name of the map
* @param mapType the map type
* @param keyType the key type
......@@ -151,8 +152,8 @@ public class BtreeMapStore {
* @return the map
*/
@SuppressWarnings("unchecked")
public <K, V> BtreeMap<K, V> openMap(String name, String mapType, String keyType, String valueType) {
BtreeMap<K, V> m = (BtreeMap<K, V>) maps.get(name);
public <T extends BtreeMap<?, ?>> T openMap(String name, String mapType, String keyType, String valueType) {
BtreeMap<?, ?> m = maps.get(name);
if (m == null) {
String identifier = meta.get("map." + name);
int id;
......@@ -178,14 +179,14 @@ public class BtreeMapStore {
DataType k = buildDataType(keyType);
DataType v = buildDataType(valueType);
if (mapType.equals("")) {
m = new BtreeMap<K, V>(this, id, name, k, v, createVersion);
m = new BtreeMap<Object, Object>(this, id, name, k, v, createVersion);
} else {
m = getMapFactory().buildMap(mapType, this, id, name, k, v, createVersion);
}
maps.put(name, m);
m.setRootPos(root);
}
return m;
return (T) m;
}
/**
......@@ -860,7 +861,7 @@ public class BtreeMapStore {
*
* @return the maximum number of entries
*/
int getMaxPageSize() {
public int getMaxPageSize() {
return maxPageSize;
}
......
......@@ -9,16 +9,17 @@ package org.h2.dev.store.btree;
/**
* A position in a cursor
*/
class CursorPos {
public class CursorPos {
/**
* The current page.
*/
Page page;
public Page page;
/**
* The current index.
*/
int index;
public int index;
}
......@@ -22,14 +22,6 @@ public interface DataType {
*/
int compare(Object a, Object b);
/**
* Get the length in bytes used to store an object.
*
* @param obj the object
* @return the length
*/
int length(Object obj);
/**
* Get the maximum length in bytes used to store an object. In many cases,
* this method can be faster than calculating the exact length.
......
......@@ -123,13 +123,14 @@ public class DataUtils {
return x;
}
x &= 0x7f;
for (int s = 7;; s += 7) {
for (int s = 7; s < 64; s += 7) {
long b = buff.get();
x |= (b & 0x7f) << s;
if (b >= 0) {
return x;
break;
}
}
return x;
}
/**
......
......@@ -30,9 +30,9 @@ public class Page {
private Object[] keys;
private Object[] values;
private long[] children;
private long[] childrenSize;
private long[] counts;
private int cachedCompare;
private long totalSize;
private long totalCount;
private Page(BtreeMap<?, ?> map, long version) {
this.map = map;
......@@ -49,16 +49,16 @@ public class Page {
* @param children the children
* @return the page
*/
static Page create(BtreeMap<?, ?> map, long version, Object[] keys,
Object[] values, long[] children, long[] childrenSize,
long totalSize) {
public static Page create(BtreeMap<?, ?> map, long version, Object[] keys,
Object[] values, long[] children, long[] counts,
long totalCount) {
Page p = new Page(map, version);
p.pos = map.getStore().registerTempPage(p);
p.keys = keys;
p.values = values;
p.children = children;
p.childrenSize = childrenSize;
p.totalSize = totalSize;
p.counts = counts;
p.totalCount = totalCount;
return p;
}
......@@ -95,23 +95,23 @@ public class Page {
return p;
}
Object getKey(int index) {
public Object getKey(int index) {
return keys[index];
}
Page getChildPage(int index) {
public Page getChildPage(int index) {
return map.readPage(children[index]);
}
Object getValue(int x) {
public Object getValue(int x) {
return values[x];
}
int getKeyCount() {
public int getKeyCount() {
return keys.length;
}
boolean isLeaf() {
public boolean isLeaf() {
return children == null;
}
......@@ -120,7 +120,7 @@ public class Page {
*
* @return the position
*/
long getPos() {
public long getPos() {
return pos;
}
......@@ -145,13 +145,13 @@ public class Page {
return buff.toString();
}
Page copyOnWrite(long writeVersion) {
public Page copyOnWrite(long writeVersion) {
if (version == writeVersion) {
return this;
}
map.getStore().removePage(pos);
Page newPage = create(map, writeVersion, keys, values, children,
childrenSize, totalSize);
counts, totalCount);
newPage.cachedCompare = cachedCompare;
return newPage;
}
......@@ -166,7 +166,7 @@ public class Page {
* @param key the key
* @return the value or null
*/
int binarySearch(Object key) {
public int binarySearch(Object key) {
int low = 0, high = keys.length - 1;
int x = cachedCompare - 1;
if (x < 0 || x > high) {
......@@ -203,7 +203,7 @@ public class Page {
// return -(low + 1);
}
Page split(int at) {
public Page split(int at) {
return isLeaf() ? splitLeaf(at) : splitNode(at);
}
......@@ -220,7 +220,7 @@ public class Page {
System.arraycopy(values, 0, aValues, 0, a);
System.arraycopy(values, a, bValues, 0, b);
values = aValues;
totalSize = keys.length;
totalCount = keys.length;
Page newPage = create(map, version, bKeys, bValues, null, null,
bKeys.length);
return newPage;
......@@ -238,61 +238,70 @@ public class Page {
System.arraycopy(children, 0, aChildren, 0, a + 1);
System.arraycopy(children, a + 1, bChildren, 0, b);
children = aChildren;
long[] aChildrenSize = new long[a + 1];
long[] bChildrenSize = new long[b];
System.arraycopy(childrenSize, 0, aChildrenSize, 0, a + 1);
System.arraycopy(childrenSize, a + 1, bChildrenSize, 0, b);
childrenSize = aChildrenSize;
long[] aCounts = new long[a + 1];
long[] bCounts = new long[b];
System.arraycopy(counts, 0, aCounts, 0, a + 1);
System.arraycopy(counts, a + 1, bCounts, 0, b);
counts = aCounts;
long t = 0;
for (long x : aChildrenSize) {
for (long x : aCounts) {
t += x;
}
totalSize = t;
totalCount = t;
t = 0;
for (long x : bChildrenSize) {
for (long x : bCounts) {
t += x;
}
Page newPage = create(map, version, bKeys, null, bChildren,
bChildrenSize, t);
bCounts, t);
return newPage;
}
long getTotalSize() {
public long getTotalSize() {
if (BtreeMapStore.ASSERT) {
long check = 0;
if (isLeaf()) {
check = keys.length;
} else {
for (long x : childrenSize) {
for (long x : counts) {
check += x;
}
}
if (check != totalSize) {
if (check != totalCount) {
throw new AssertionError("Expected: " + check + " got: "
+ totalSize);
+ totalCount);
}
}
return totalSize;
return totalCount;
}
void setChild(int index, long pos, long childSize) {
if (pos != children[index]) {
public void setChild(int index, Page c) {
if (c.getPos() != children[index]) {
long[] newChildren = new long[children.length];
System.arraycopy(children, 0, newChildren, 0, newChildren.length);
newChildren[index] = pos;
newChildren[index] = c.getPos();
children = newChildren;
}
if (childSize != childrenSize[index]) {
long[] newChildrenSize = new long[childrenSize.length];
System.arraycopy(childrenSize, 0, newChildrenSize, 0,
newChildrenSize.length);
newChildrenSize[index] = childSize;
totalSize += newChildrenSize[index] - childrenSize[index];
childrenSize = newChildrenSize;
if (c.getTotalSize() != counts[index]) {
long[] newCounts = new long[counts.length];
System.arraycopy(counts, 0, newCounts, 0,
newCounts.length);
newCounts[index] = c.getTotalSize();
totalCount += newCounts[index] - counts[index];
counts = newCounts;
}
}
void setValue(int index, Object value) {
public void setKey(int index, Object key) {
// create a copy - not required if already cloned once in this version,
// but avoid unnecessary cloning would require a "modified" flag
Object[] newKeys = new Object[keys.length];
System.arraycopy(keys, 0, newKeys, 0, newKeys.length);
newKeys[index] = key;
keys = newKeys;
}
public void setValue(int index, Object value) {
// create a copy - not required if already cloned once in this version,
// but avoid unnecessary cloning would require a "modified" flag
Object[] newValues = new Object[values.length];
......@@ -318,8 +327,8 @@ public class Page {
map.getStore().removePage(pos);
}
void insert(int index, Object key, Object value, long child,
long childSize) {
public void insert(int index, Object key, Object value, long child,
long count) {
Object[] newKeys = new Object[keys.length + 1];
DataUtils.copyWithGap(keys, newKeys, keys.length, index);
newKeys[index] = key;
......@@ -329,24 +338,22 @@ public class Page {
DataUtils.copyWithGap(values, newValues, values.length, index);
newValues[index] = value;
values = newValues;
totalSize++;
totalCount++;
}
if (children != null) {
long[] newChildren = new long[children.length + 1];
DataUtils.copyWithGap(children, newChildren, children.length,
index + 1);
newChildren[index + 1] = child;
DataUtils.copyWithGap(children, newChildren, children.length, index);
newChildren[index] = child;
children = newChildren;
long[] newChildrenSize = new long[childrenSize.length + 1];
DataUtils.copyWithGap(childrenSize, newChildrenSize,
childrenSize.length, index + 1);
newChildrenSize[index + 1] = childSize;
childrenSize = newChildrenSize;
totalSize += childSize;
long[] newCounts = new long[counts.length + 1];
DataUtils.copyWithGap(counts, newCounts, counts.length, index);
newCounts[index] = count;
counts = newCounts;
totalCount += count;
}
}
void remove(int index) {
public void remove(int index) {
Object[] newKeys = new Object[keys.length - 1];
int keyIndex = index >= keys.length ? index - 1 : index;
DataUtils.copyExcept(keys, newKeys, keys.length, keyIndex);
......@@ -355,18 +362,18 @@ public class Page {
Object[] newValues = new Object[values.length - 1];
DataUtils.copyExcept(values, newValues, values.length, index);
values = newValues;
totalSize--;
totalCount--;
}
if (children != null) {
long sizeOffset = childrenSize[index];
long countOffset = counts[index];
long[] newChildren = new long[children.length - 1];
DataUtils.copyExcept(children, newChildren, children.length, index);
children = newChildren;
long[] newChildrenSize = new long[childrenSize.length - 1];
DataUtils.copyExcept(childrenSize, newChildrenSize,
childrenSize.length, index);
childrenSize = newChildrenSize;
totalSize -= sizeOffset;
long[] newCounts = new long[counts.length - 1];
DataUtils.copyExcept(counts, newCounts,
counts.length, index);
counts = newCounts;
totalCount -= countOffset;
}
}
......@@ -413,20 +420,20 @@ public class Page {
for (int i = 0; i <= len; i++) {
children[i] = buff.getLong();
}
childrenSize = new long[len + 1];
counts = new long[len + 1];
long total = 0;
for (int i = 0; i <= len; i++) {
long s = DataUtils.readVarLong(buff);
total += s;
childrenSize[i] = s;
counts[i] = s;
}
totalSize = total;
totalCount = total;
} else {
values = new Object[len];
for (int i = 0; i < len; i++) {
values[i] = map.getValueType().read(buff);
}
totalSize = len;
totalCount = len;
}
}
......@@ -456,7 +463,7 @@ public class Page {
buff.putLong(children[i]);
}
for (int i = 0; i <= len; i++) {
DataUtils.writeVarLong(buff, childrenSize[i]);
DataUtils.writeVarLong(buff, counts[i]);
}
} else {
for (int i = 0; i < len; i++) {
......@@ -558,4 +565,8 @@ public class Page {
return count;
}
public long getCounts(int index) {
return counts[index];
}
}
......@@ -17,21 +17,6 @@ public class StringType implements DataType {
return a.toString().compareTo(b.toString());
}
public int length(Object obj) {
int plus = 0;
String s = obj.toString();
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (c >= 0x800) {
plus += 2;
} else if (c >= 0x80) {
plus++;
}
}
return DataUtils.getVarIntLen(len) + len + plus;
}
public int getMaxLength(Object obj) {
return DataUtils.MAX_VAR_INT_LEN + obj.toString().length() * 3;
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论