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

A persistent tree map (work in progress) which might replace the storage of the…

A persistent tree map (work in progress) which might replace the storage of the database at some point in the far future.
上级 16c9d346
...@@ -163,6 +163,7 @@ import org.h2.test.unit.TestStreams; ...@@ -163,6 +163,7 @@ import org.h2.test.unit.TestStreams;
import org.h2.test.unit.TestStringCache; import org.h2.test.unit.TestStringCache;
import org.h2.test.unit.TestStringUtils; import org.h2.test.unit.TestStringUtils;
import org.h2.test.unit.TestTools; import org.h2.test.unit.TestTools;
import org.h2.test.unit.TestTreeMapStore;
import org.h2.test.unit.TestUtils; import org.h2.test.unit.TestUtils;
import org.h2.test.unit.TestValue; import org.h2.test.unit.TestValue;
import org.h2.test.unit.TestValueHashMap; import org.h2.test.unit.TestValueHashMap;
...@@ -700,6 +701,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -700,6 +701,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestStringCache().runTest(this); new TestStringCache().runTest(this);
new TestStringUtils().runTest(this); new TestStringUtils().runTest(this);
new TestTools().runTest(this); new TestTools().runTest(this);
new TestTreeMapStore().runTest(this);
new TestTraceSystem().runTest(this); new TestTraceSystem().runTest(this);
new TestUpgrade().runTest(this); new TestUpgrade().runTest(this);
new TestUtils().runTest(this); new TestUtils().runTest(this);
......
/*
* 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.unit;
import java.util.Iterator;
import java.util.Random;
import java.util.TreeMap;
import org.h2.dev.store.StoredMap;
import org.h2.dev.store.TreeMapStore;
import org.h2.jaqu.bytecode.Null;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
/**
* Tests the tree map store.
*/
public class TestTreeMapStore extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
public void test() throws Exception {
testRandom();
testKeyValueClasses();
testIterate();
testSimple();
}
private void testRandom() {
String fileName = getBaseDir() + "/data.h3";
FileUtils.delete(fileName);
TreeMapStore s = TreeMapStore.open(fileName);
StoredMap<Integer, Integer> m = s.openMap("intString", Integer.class, Integer.class);
TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
Random r = new Random(1);
for (int i = 0; i < 100; i++) {
int k = r.nextInt(20);
int v = r.nextInt();
if (r.nextBoolean()) {
m.put(k, v);
map.put(k, v);
} else {
m.remove(k);
map.remove(k);
}
Iterator<Integer> it = m.keyIterator(null);
Iterator<Integer> itm = map.keySet().iterator();
while (itm.hasNext()) {
assertTrue(it.hasNext());
assertEquals(itm.next(), it.next());
}
assertFalse(it.hasNext());
}
s.close();
}
private void testKeyValueClasses() {
String fileName = getBaseDir() + "/data.h3";
FileUtils.delete(fileName);
TreeMapStore s = TreeMapStore.open(fileName);
StoredMap<Integer, String> is = s.openMap("intString", Integer.class, String.class);
is.put(1, "Hello");
StoredMap<Integer, Integer> ii = s.openMap("intInt", Integer.class, Integer.class);
ii.put(1, 10);
StoredMap<String, Integer> si = s.openMap("stringInt", String.class, Integer.class);
si.put("Test", 10);
StoredMap<String, String> ss = s.openMap("stringString", String.class, String.class);
ss.put("Hello", "World");
try {
s.openMap("invalid", Null.class, Integer.class);
fail();
} catch (RuntimeException e) {
// expected
}
try {
s.openMap("invalid", Integer.class, Null.class);
fail();
} catch (RuntimeException e) {
// expected
}
s.close();
s = TreeMapStore.open(fileName);
is = s.openMap("intString", Integer.class, String.class);
assertEquals("Hello", is.get(1));
ii = s.openMap("intInt", Integer.class, Integer.class);
assertEquals(10, ii.get(1).intValue());
si = s.openMap("stringInt", String.class, Integer.class);
assertEquals(10, si.get("Test").intValue());
ss = s.openMap("stringString", String.class, String.class);
assertEquals("World", ss.get("Hello"));
s.close();
}
private void testIterate() {
String fileName = getBaseDir() + "/data.h3";
FileUtils.delete(fileName);
TreeMapStore s = TreeMapStore.open(fileName);
StoredMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
Iterator<Integer> it = m.keyIterator(null);
assertFalse(it.hasNext());
for (int i = 0; i < 10; i++) {
m.put(i, "hello " + i);
}
s.store();
it = m.keyIterator(null);
it.next();
assertThrows(UnsupportedOperationException.class, it).remove();
it = m.keyIterator(null);
for (int i = 0; i < 10; i++) {
assertTrue(it.hasNext());
assertEquals(i, it.next().intValue());
}
assertFalse(it.hasNext());
assertNull(it.next());
for (int j = 0; j < 10; j++) {
it = m.keyIterator(j);
for (int i = j; i < 10; i++) {
assertTrue(it.hasNext());
assertEquals(i, it.next().intValue());
}
assertFalse(it.hasNext());
}
}
private void testSimple() {
String fileName = getBaseDir() + "/data.h3";
FileUtils.delete(fileName);
TreeMapStore s = TreeMapStore.open(fileName);
StoredMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 3; i++) {
m.put(i, "hello " + i);
}
s.store();
m.remove(0);
assertNull(m.get(0));
for (int i = 1; i < 3; i++) {
assertEquals("hello " + i, m.get(i));
}
s.close();
s = TreeMapStore.open(fileName);
m = s.openMap("data", Integer.class, String.class);
assertNull(m.get(0));
for (int i = 1; i < 3; i++) {
assertEquals("hello " + i, m.get(i));
}
s.close();
}
}
...@@ -16,7 +16,7 @@ class Node { ...@@ -16,7 +16,7 @@ class Node {
private static final int FLAG_BLACK = 1; private static final int FLAG_BLACK = 1;
// private static final int FLAG_BACK_REFERENCES = 2; // private static final int FLAG_BACK_REFERENCES = 2;
private final TreeMapStore store; private final StoredMap<?, ?> map;
private long id; private long id;
private long leftId, rightId; private long leftId, rightId;
private long transaction; private long transaction;
...@@ -25,36 +25,36 @@ class Node { ...@@ -25,36 +25,36 @@ class Node {
private Node left, right; private Node left, right;
private int flags; private int flags;
private Node(TreeMapStore store) { private Node(StoredMap<?, ?> map) {
this.store = store; this.map = map;
} }
static Node create(TreeMapStore store, Object key, Object data) { static Node create(StoredMap<?, ?> map, Object key, Object data) {
Node n = new Node(store); Node n = new Node(map);
n.key = key; n.key = key;
n.data = data; n.data = data;
n.transaction = store.getTransaction(); n.transaction = map.getTransaction();
n.id = store.nextTempNodeId(); n.id = map.nextTempNodeId();
return n; return n;
} }
static Node load(TreeMapStore store, long id, ByteBuffer buff) { static Node read(StoredMap<?, ?> map, long id, ByteBuffer buff) {
Node n = new Node(store); Node n = new Node(map);
n.id = id; n.id = id;
n.load(buff); n.read(buff);
return n; return n;
} }
Node getLeft() { Node getLeft() {
if (left == null && leftId != 0) { if (left == null && leftId != 0) {
left = store.loadNode(leftId); return map.readNode(leftId);
} }
return left; return left;
} }
Node getRight() { Node getRight() {
if (right == null && rightId != 0) { if (right == null && rightId != 0) {
right = store.loadNode(rightId); return map.readNode(rightId);
} }
return right; return right;
} }
...@@ -87,12 +87,12 @@ class Node { ...@@ -87,12 +87,12 @@ class Node {
this.rightId = r == null ? 0 : r.getId(); this.rightId = r == null ? 0 : r.getId();
} }
private Node copyOnWrite(long writeTransaction) { private Node copyOnWrite() {
if (writeTransaction == transaction) { if (transaction == map.getTransaction()) {
return this; return this;
} }
store.removeNode(id); map.removeNode(id);
Node n2 = create(store, key, data); Node n2 = create(map, key, data);
n2.leftId = leftId; n2.leftId = leftId;
n2.left = left; n2.left = left;
n2.rightId = rightId; n2.rightId = rightId;
...@@ -124,9 +124,9 @@ class Node { ...@@ -124,9 +124,9 @@ class Node {
private void flipColor() { private void flipColor() {
flags = flags ^ FLAG_BLACK; flags = flags ^ FLAG_BLACK;
setLeft(getLeft().copyOnWrite(transaction)); setLeft(getLeft().copyOnWrite());
getLeft().flags = getLeft().flags ^ FLAG_BLACK; getLeft().flags = getLeft().flags ^ FLAG_BLACK;
setRight(getRight().copyOnWrite(transaction)); setRight(getRight().copyOnWrite());
getRight().flags = getRight().flags ^ FLAG_BLACK; getRight().flags = getRight().flags ^ FLAG_BLACK;
} }
...@@ -147,7 +147,7 @@ class Node { ...@@ -147,7 +147,7 @@ class Node {
} }
private Node rotateLeft() { private Node rotateLeft() {
Node x = getRight().copyOnWrite(store.getTransaction()); Node x = getRight().copyOnWrite();
setRight(x.getLeft()); setRight(x.getLeft());
x.setLeft(this); x.setLeft(this);
x.flags = flags; x.flags = flags;
...@@ -157,7 +157,7 @@ class Node { ...@@ -157,7 +157,7 @@ class Node {
} }
private Node rotateRight() { private Node rotateRight() {
Node x = getLeft().copyOnWrite(store.getTransaction()); Node x = getLeft().copyOnWrite();
setLeft(x.getRight()); setLeft(x.getRight());
x.setRight(this); x.setRight(this);
x.flags = flags; x.flags = flags;
...@@ -187,7 +187,7 @@ class Node { ...@@ -187,7 +187,7 @@ class Node {
return this; return this;
} }
private Node min() { private Node getMin() {
Node n = this; Node n = this;
while (n.getLeft() != null) { while (n.getLeft() != null) {
n = n.getLeft(); n = n.getLeft();
...@@ -195,61 +195,61 @@ class Node { ...@@ -195,61 +195,61 @@ class Node {
return n; return n;
} }
private Node deleteMin() { private Node removeMin() {
if (getLeft() == null) { if (getLeft() == null) {
store.removeNode(id); map.removeNode(id);
return null; return null;
} }
Node n = copyOnWrite(transaction); Node n = copyOnWrite();
if (!isRed(n.getLeft()) && !isRed(n.getLeft().getLeft())) { if (!isRed(n.getLeft()) && !isRed(n.getLeft().getLeft())) {
n = n.moveRedLeft(); n = n.moveRedLeft();
} }
n.setLeft(n.getLeft().deleteMin()); n.setLeft(n.getLeft().removeMin());
return n.fixUp(); return n.fixUp();
} }
static Node remove(Node n, Object key) { static Node remove(Node n, Object key) {
if (findNode(n, key) == null) { if (getNode(n, key) == null) {
return n; return n;
} }
return n.delete(key); return n.remove(key);
} }
private int compare(Object key) { int compare(Object key) {
return store.compare(key, this.key); return map.compare(key, this.key);
} }
private Node delete(Object key) { private Node remove(Object key) {
Node n = copyOnWrite(transaction); Node n = copyOnWrite();
if (store.compare(key, n) < 0) { if (map.compare(key, n.key) < 0) {
if (!isRed(n.getLeft()) && !isRed(n.getLeft().getLeft())) { if (!isRed(n.getLeft()) && !isRed(n.getLeft().getLeft())) {
n = n.moveRedLeft(); n = n.moveRedLeft();
} }
n.setLeft(n.getLeft().delete(key)); n.setLeft(n.getLeft().remove(key));
} else { } else {
if (isRed(n.getLeft())) { if (isRed(n.getLeft())) {
n = n.rotateRight(); n = n.rotateRight();
} }
if (n.compare(key) == 0 && n.getRight() == null) { if (n.compare(key) == 0 && n.getRight() == null) {
store.removeNode(id); map.removeNode(id);
return null; return null;
} }
if (!isRed(n.getRight()) && !isRed(n.getRight().getLeft())) { if (!isRed(n.getRight()) && !isRed(n.getRight().getLeft())) {
n = n.moveRedRight(); n = n.moveRedRight();
} }
if (n.compare(key) == 0) { if (n.compare(key) == 0) {
Node min = n.getRight().min(); Node min = n.getRight().getMin();
n.key = min.key; n.key = min.key;
n.data = min.data; n.data = min.data;
n.setRight(n.getRight().deleteMin()); n.setRight(n.getRight().removeMin());
} else { } else {
n.setRight(n.getRight().delete(key)); n.setRight(n.getRight().remove(key));
} }
} }
return n.fixUp(); return n.fixUp();
} }
static Node findNode(Node n, Object key) { static Node getNode(Node n, Object key) {
while (n != null) { while (n != null) {
int compare = n.compare(key); int compare = n.compare(key);
if (compare == 0) { if (compare == 0) {
...@@ -263,19 +263,19 @@ class Node { ...@@ -263,19 +263,19 @@ class Node {
return null; return null;
} }
static Node add(TreeMapStore store, Node n, Object key, Object data) { static Node put(StoredMap<?, ?> map, Node n, Object key, Object data) {
if (n == null) { if (n == null) {
n = Node.create(store, key, data); n = Node.create(map, key, data);
return n; return n;
} }
n = n.copyOnWrite(store.getTransaction()); n = n.copyOnWrite();
int compare = n.compare(key); int compare = n.compare(key);
if (compare == 0) { if (compare == 0) {
n.data = data; n.data = data;
} else if (compare < 0) { } else if (compare < 0) {
n.setLeft(add(store, n.getLeft(), key, data)); n.setLeft(put(map, n.getLeft(), key, data));
} else { } else {
n.setRight(add(store, n.getRight(), key, data)); n.setRight(put(map, n.getRight(), key, data));
} }
return n.fixUp(); return n.fixUp();
} }
...@@ -298,25 +298,25 @@ class Node { ...@@ -298,25 +298,25 @@ class Node {
return n != null && (n.flags & FLAG_BLACK) == 0; return n != null && (n.flags & FLAG_BLACK) == 0;
} }
private void load(ByteBuffer buff) { private void read(ByteBuffer buff) {
flags = buff.get(); flags = buff.get();
leftId = buff.getLong(); leftId = buff.getLong();
rightId = buff.getLong(); rightId = buff.getLong();
key = store.getKeyType().read(buff); key = map.getKeyType().read(buff);
data = store.getValueType().read(buff); data = map.getValueType().read(buff);
} }
void store(ByteBuffer buff) { void write(ByteBuffer buff) {
buff.put((byte) flags); buff.put((byte) flags);
buff.putLong(leftId); buff.putLong(leftId);
buff.putLong(rightId); buff.putLong(rightId);
store.getKeyType().write(buff, key); map.getKeyType().write(buff, key);
store.getValueType().write(buff, data); map.getValueType().write(buff, data);
} }
int length() { int length() {
return store.getKeyType().length(key) + return map.getKeyType().length(key) +
store.getValueType().length(data) + 17; map.getValueType().length(data) + 17;
} }
} }
/*
* 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.dev.store;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
/**
* A stored map.
*
* @param <K> the key class
* @param <V> the value class
*/
public class StoredMap<K, V> {
private final TreeMapStore store;
private final String name;
private final KeyType keyType;
private final ValueType valueType;
private Node root;
private StoredMap(TreeMapStore store, String name, Class<K> keyClass, Class<V> valueClass) {
this.store = store;
this.name = name;
if (keyClass == Integer.class) {
keyType = new IntegerType();
} else if (keyClass == String.class) {
keyType = new StringType();
} else {
throw new RuntimeException("Unsupported key class " + keyClass.toString());
}
if (valueClass == Integer.class) {
valueType = new IntegerType();
} else if (valueClass == String.class) {
valueType = new StringType();
} else {
throw new RuntimeException("Unsupported value class " + keyClass.toString());
}
}
static <K, V> StoredMap<K, V> open(TreeMapStore store, String name, Class<K> keyClass, Class<V> valueClass) {
return new StoredMap<K, V>(store, name, keyClass, valueClass);
}
public void put(K key, V data) {
if (!isChanged()) {
store.changed(name, this);
}
root = Node.put(this, root, key, data);
}
@SuppressWarnings("unchecked")
public V get(K key) {
Node n = Node.getNode(root, key);
return (V) (n == null ? null : n.getData());
}
public void remove(K key) {
if (!isChanged()) {
store.changed(name, this);
}
root = Node.remove(root, key);
}
boolean isChanged() {
return root != null && root.getId() < 0;
}
/**
* A value type.
*/
static interface ValueType {
int length(Object obj);
void write(ByteBuffer buff, Object x);
Object read(ByteBuffer buff);
}
/**
* A key type.
*/
static interface KeyType extends ValueType {
int compare(Object a, Object b);
}
int compare(Object a, Object b) {
return keyType.compare(a, b);
}
/**
* An integer type.
*/
static class IntegerType implements KeyType {
public int compare(Object a, Object b) {
return ((Integer) a).compareTo((Integer) b);
}
public int length(Object obj) {
return TreeMapStore.getVarIntLen((Integer) obj);
}
public Integer read(ByteBuffer buff) {
return TreeMapStore.readVarInt(buff);
}
public void write(ByteBuffer buff, Object x) {
TreeMapStore.writeVarInt(buff, (Integer) x);
}
}
/**
* A string type.
*/
static class StringType implements KeyType {
public int compare(Object a, Object b) {
return a.toString().compareTo(b.toString());
}
public int length(Object obj) {
try {
byte[] bytes = obj.toString().getBytes("UTF-8");
return TreeMapStore.getVarIntLen(bytes.length) + bytes.length;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String read(ByteBuffer buff) {
int len = TreeMapStore.readVarInt(buff);
byte[] bytes = new byte[len];
buff.get(bytes);
try {
return new String(bytes, "UTF-8");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void write(ByteBuffer buff, Object x) {
try {
byte[] bytes = x.toString().getBytes("UTF-8");
TreeMapStore.writeVarInt(buff, bytes.length);
buff.put(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
KeyType getKeyType() {
return keyType;
}
ValueType getValueType() {
return valueType;
}
long getTransaction() {
return store.getTransaction();
}
long nextTempNodeId() {
return store.nextTempNodeId();
}
public Node readNode(long id) {
return store.readNode(this, id);
}
void removeNode(long id) {
store.removeNode(id);
}
void setRoot(long rootPos) {
root = readNode(rootPos);
}
public Iterator<K> keyIterator(K from) {
return new Cursor(root, from);
}
/**
* A cursor to iterate over elements in ascending order.
*/
class Cursor implements Iterator<K> {
Node current;
ArrayList<Node> parents = new ArrayList<Node>();
Cursor(Node root, K from) {
min(root, from);
}
private void min(Node n, K key) {
while (n != null) {
int compare = key == null ? -1 : n.compare(key);
if (compare == 0) {
current = n;
return;
} else if (compare > 0) {
n = n.getRight();
} else {
parents.add(n);
n = n.getLeft();
}
}
if (parents.size() == 0) {
current = null;
return;
}
current = parents.remove(parents.size() - 1);
}
@SuppressWarnings("unchecked")
public K next() {
Node c = current;
if (c != null) {
fetchNext();
}
return c == null ? null : (K) c.getKey();
}
private void fetchNext() {
Node r = current.getRight();
if (r != null) {
min(r, null);
return;
}
if (parents.size() == 0) {
current = null;
return;
}
current = parents.remove(parents.size() - 1);
}
public boolean hasNext() {
return current != null;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
Node getRoot() {
return root;
}
String getName() {
return name;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论