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

A persistent multi-version map (work in progress) - implement complete java.util.Map interface

上级 7ef5bbc4
...@@ -150,23 +150,26 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -150,23 +150,26 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
return bounds; return bounds;
} }
public void put(K key, V value) { @SuppressWarnings("unchecked")
putOrAdd(key, value, false); public V put(K key, V value) {
return (V) putOrAdd(key, value, false);
} }
public void add(K key, V value) { public void add(K key, V value) {
putOrAdd(key, value, true); putOrAdd(key, value, true);
} }
public void putOrAdd(K key, V value, boolean alwaysAdd) { public Object putOrAdd(K key, V value, boolean alwaysAdd) {
checkWrite(); checkWrite();
long writeVersion = store.getCurrentVersion(); long writeVersion = store.getCurrentVersion();
Page p = root; Page p = root;
Object result;
if (p == null) { if (p == null) {
Object[] keys = { key }; Object[] keys = { key };
Object[] values = { value }; Object[] values = { value };
p = Page.create(this, writeVersion, 1, p = Page.create(this, writeVersion, 1,
keys, values, null, null, null, 1, 0); keys, values, null, null, null, 1, 0);
result = null;
} else if (alwaysAdd || get(key) == null) { } else if (alwaysAdd || get(key) == null) {
if (p.getKeyCount() > store.getMaxPageSize()) { if (p.getKeyCount() > store.getMaxPageSize()) {
// only possible if this is the root, else we would have split earlier // only possible if this is the root, else we would have split earlier
...@@ -183,43 +186,41 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -183,43 +186,41 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
totalCount, 0); totalCount, 0);
// now p is a node; continues // now p is a node; continues
} }
p = add(p, writeVersion, key, value); add(p, writeVersion, key, value);
result = null;
} else { } else {
p = set(p, writeVersion, key, value); result = set(p, writeVersion, key, value);
} }
setRoot(p); setRoot(p);
return result;
} }
protected Page set(Page p, long writeVersion, Object key, Object value) { protected Object set(Page p, long writeVersion, Object key, Object value) {
if (!p.isLeaf()) { if (!p.isLeaf()) {
for (int i = 0; i < p.getKeyCount(); i++) { for (int i = 0; i < p.getKeyCount(); i++) {
if (contains(p, i, key)) { if (contains(p, i, key)) {
Page c = p.getChildPage(i); Page c = p.getChildPage(i).copyOnWrite(writeVersion);
Page c2 = set(c, writeVersion, key, value); Object result = set(c, writeVersion, key, value);
if (c != c2) { if (result != null) {
p = p.copyOnWrite(writeVersion); p.setChild(i, c);
p.setChild(i, c2); return result;
break;
} }
} }
} }
} else { } else {
for (int i = 0; i < p.getKeyCount(); i++) { for (int i = 0; i < p.getKeyCount(); i++) {
if (keyType.equals(p.getKey(i), key)) { if (keyType.equals(p.getKey(i), key)) {
p = p.copyOnWrite(writeVersion); return p.setValue(i, value);
p.setValue(i, value);
break;
} }
} }
} }
return p; return null;
} }
protected Page add(Page p, long writeVersion, Object key, Object value) { protected void add(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) { if (p.isLeaf()) {
p = p.copyOnWrite(writeVersion);
p.insertLeaf(p.getKeyCount(), key, value); p.insertLeaf(p.getKeyCount(), key, value);
return p; return;
} }
// p is a node // p is a node
int index = -1; int index = -1;
...@@ -241,25 +242,23 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> { ...@@ -241,25 +242,23 @@ public class MVRTreeMap<K, V> extends MVMap<K, V> {
} }
} }
} }
Page c = p.getChildPage(index); Page c = p.getChildPage(index).copyOnWrite(writeVersion);
if (c.getKeyCount() >= store.getMaxPageSize()) { if (c.getKeyCount() >= store.getMaxPageSize()) {
// split on the way down // split on the way down
c = c.copyOnWrite(writeVersion);
Page split = split(c, writeVersion); Page split = split(c, writeVersion);
p = p.copyOnWrite(writeVersion); p = p.copyOnWrite(writeVersion);
p.setKey(index, getBounds(c)); p.setKey(index, getBounds(c));
p.setChild(index, c); p.setChild(index, c);
p.insertNode(index, getBounds(split), split); p.insertNode(index, getBounds(split), split);
// now we are not sure where to add // now we are not sure where to add
return add(p, writeVersion, key, value); add(p, writeVersion, key, value);
return;
} }
Page c2 = add(c, writeVersion, key, value); add(c, writeVersion, key, value);
p = p.copyOnWrite(writeVersion);
Object bounds = p.getKey(index); Object bounds = p.getKey(index);
keyType.increaseBounds(bounds, key); keyType.increaseBounds(bounds, key);
p.setKey(index, bounds); p.setKey(index, bounds);
p.setChild(index, c2); p.setChild(index, c);
return p;
} }
private Page split(Page p, long writeVersion) { private Page split(Page p, long writeVersion) {
......
...@@ -56,12 +56,12 @@ public class TestMVStore extends TestBase { ...@@ -56,12 +56,12 @@ public class TestMVStore extends TestBase {
s.setMaxPageSize(6); s.setMaxPageSize(6);
MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
for (int i = 0; i < 60; i++) { for (int i = 0; i < 60; i++) {
m.put(i, "Hello"); m.put(i, "Hi");
} }
s.commit(); s.commit();
s.store(); s.store();
for (int i = 20; i < 40; i++) { for (int i = 20; i < 40; i++) {
m.put(i, "Hello"); assertEquals("Hi", m.put(i, "Hello"));
} }
s.commit(); s.commit();
for (int i = 10; i < 15; i++) { for (int i = 10; i < 15; i++) {
...@@ -318,7 +318,7 @@ public class TestMVStore extends TestBase { ...@@ -318,7 +318,7 @@ public class TestMVStore extends TestBase {
s.store(); s.store();
assertEquals("1/1///", m.get("map.data")); assertEquals("1/1///", m.get("map.data"));
assertTrue(m.containsKey("chunk.1")); assertTrue(m.containsKey("chunk.1"));
data.put("1", "Hallo"); assertEquals("Hello", data.put("1", "Hallo"));
s.store(); s.store();
assertEquals("1/1///", m.get("map.data")); assertEquals("1/1///", m.get("map.data"));
assertTrue(m.get("root.1").length() > 0); assertTrue(m.get("root.1").length() > 0);
...@@ -341,7 +341,7 @@ public class TestMVStore extends TestBase { ...@@ -341,7 +341,7 @@ public class TestMVStore extends TestBase {
MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class);
// t = System.currentTimeMillis(); // t = System.currentTimeMillis();
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
m.put(i, "Hello World"); assertNull(m.put(i, "Hello World"));
} }
// System.out.println("put: " + (System.currentTimeMillis() - t)); // System.out.println("put: " + (System.currentTimeMillis() - t));
// t = System.currentTimeMillis(); // t = System.currentTimeMillis();
...@@ -404,7 +404,7 @@ public class TestMVStore extends TestBase { ...@@ -404,7 +404,7 @@ public class TestMVStore extends TestBase {
// p.startCollecting(); // p.startCollecting();
// long t = System.currentTimeMillis(); // long t = System.currentTimeMillis();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
m.put(i, "hello " + i); assertNull(m.put(i, "hello " + i));
assertEquals("hello " + i, m.get(i)); assertEquals("hello " + i, m.get(i));
} }
// System.out.println("put: " + (System.currentTimeMillis() - t)); // System.out.println("put: " + (System.currentTimeMillis() - t));
...@@ -515,6 +515,7 @@ public class TestMVStore extends TestBase { ...@@ -515,6 +515,7 @@ public class TestMVStore extends TestBase {
Random r = new Random(1); Random r = new Random(1);
int operationCount = 1000; int operationCount = 1000;
int maxValue = 30; int maxValue = 30;
Integer expected, got;
for (int i = 0; i < operationCount; i++) { for (int i = 0; i < operationCount; i++) {
int k = r.nextInt(maxValue); int k = r.nextInt(maxValue);
int v = r.nextInt(); int v = r.nextInt();
...@@ -522,14 +523,19 @@ public class TestMVStore extends TestBase { ...@@ -522,14 +523,19 @@ public class TestMVStore extends TestBase {
switch (r.nextInt(3)) { switch (r.nextInt(3)) {
case 0: case 0:
log(i + ": put " + k + " = " + v); log(i + ": put " + k + " = " + v);
m.put(k, v); expected = map.put(k, v);
map.put(k, v); got = m.put(k, v);
if (expected == null) {
assertNull(got);
} else {
assertEquals(expected, got);
}
compareAll = true; compareAll = true;
break; break;
case 1: case 1:
log(i + ": remove " + k); log(i + ": remove " + k);
Integer expected = map.remove(k); expected = map.remove(k);
Integer got = m.remove(k); got = m.remove(k);
if (expected == null) { if (expected == null) {
assertNull(got); assertNull(got);
} else { } else {
...@@ -553,8 +559,8 @@ public class TestMVStore extends TestBase { ...@@ -553,8 +559,8 @@ public class TestMVStore extends TestBase {
Iterator<Integer> itExpected = map.keySet().iterator(); Iterator<Integer> itExpected = map.keySet().iterator();
while (itExpected.hasNext()) { while (itExpected.hasNext()) {
assertTrue(it.hasNext()); assertTrue(it.hasNext());
Integer expected = itExpected.next(); expected = itExpected.next();
Integer got = it.next(); got = it.next();
assertEquals(expected, got); assertEquals(expected, got);
} }
assertFalse(it.hasNext()); assertFalse(it.hasNext());
......
...@@ -6,8 +6,11 @@ ...@@ -6,8 +6,11 @@
*/ */
package org.h2.dev.store.btree; package org.h2.dev.store.btree;
import java.util.AbstractMap;
import java.util.AbstractSet; import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
...@@ -17,7 +20,7 @@ import java.util.TreeMap; ...@@ -17,7 +20,7 @@ import java.util.TreeMap;
* @param <K> the key class * @param <K> the key class
* @param <V> the value class * @param <V> the value class
*/ */
public class MVMap<K, V> { public class MVMap<K, V> extends AbstractMap<K, V> {
protected final MVStore store; protected final MVStore store;
protected Page root; protected Page root;
...@@ -51,16 +54,20 @@ public class MVMap<K, V> { ...@@ -51,16 +54,20 @@ public class MVMap<K, V> {
* *
* @param key the key * @param key the key
* @param value the value * @param value the value
* @return the old value if the key existed, or null otherwise
*/ */
public void put(K key, V value) { @SuppressWarnings("unchecked")
public V put(K key, V value) {
checkWrite(); checkWrite();
long writeVersion = store.getCurrentVersion(); long writeVersion = store.getCurrentVersion();
Page p = root; Page p = root;
Object result;
if (p == null) { if (p == null) {
Object[] keys = { key }; Object[] keys = { key };
Object[] values = { value }; Object[] values = { value };
p = Page.create(this, writeVersion, 1, p = Page.create(this, writeVersion, 1,
keys, values, null, null, null, 1, 0); keys, values, null, null, null, 1, 0);
result = null;
} else { } else {
p = p.copyOnWrite(writeVersion); p = p.copyOnWrite(writeVersion);
if (p.getKeyCount() > store.getMaxPageSize()) { if (p.getKeyCount() > store.getMaxPageSize()) {
...@@ -76,9 +83,10 @@ public class MVMap<K, V> { ...@@ -76,9 +83,10 @@ public class MVMap<K, V> {
keys, null, children, childrenPages, counts, totalCount, 0); keys, null, children, childrenPages, counts, totalCount, 0);
// now p is a node; insert continues // now p is a node; insert continues
} }
put(p, writeVersion, key, value); result = put(p, writeVersion, key, value);
} }
setRoot(p); setRoot(p);
return (V) result;
} }
/** /**
...@@ -90,16 +98,15 @@ public class MVMap<K, V> { ...@@ -90,16 +98,15 @@ public class MVMap<K, V> {
* @param key the key * @param key the key
* @param value the value (may not be null) * @param value the value (may not be null)
*/ */
protected void put(Page p, long writeVersion, Object key, Object value) { protected Object put(Page p, long writeVersion, Object key, Object value) {
if (p.isLeaf()) { if (p.isLeaf()) {
int index = p.binarySearch(key); int index = p.binarySearch(key);
if (index < 0) { if (index < 0) {
index = -index - 1; index = -index - 1;
p.insertLeaf(index, key, value); p.insertLeaf(index, key, value);
} else { return null;
p.setValue(index, value);
} }
return; return p.setValue(index, value);
} }
// p is a node // p is a node
int index = p.binarySearch(key); int index = p.binarySearch(key);
...@@ -108,8 +115,7 @@ public class MVMap<K, V> { ...@@ -108,8 +115,7 @@ public class MVMap<K, V> {
} else { } else {
index++; index++;
} }
Page cOld = p.getChildPage(index); Page c = p.getChildPage(index).copyOnWrite(writeVersion);
Page c = cOld.copyOnWrite(writeVersion);
if (c.getKeyCount() >= store.getMaxPageSize()) { if (c.getKeyCount() >= store.getMaxPageSize()) {
// split on the way down // split on the way down
int at = c.getKeyCount() / 2; int at = c.getKeyCount() / 2;
...@@ -118,14 +124,11 @@ public class MVMap<K, V> { ...@@ -118,14 +124,11 @@ public class MVMap<K, V> {
p.setChild(index, split); p.setChild(index, split);
p.insertNode(index, k, c); p.insertNode(index, k, c);
// now we are not sure where to add // now we are not sure where to add
put(p, writeVersion, key, value); return put(p, writeVersion, key, value);
return;
}
long oldSize = c.getTotalCount();
put(c, writeVersion, key, value);
if (cOld != c || oldSize != c.getTotalCount()) {
p.setChild(index, c);
} }
Object result = put(c, writeVersion, key, value);
p.setChild(index, c);
return result;
} }
/** /**
...@@ -312,9 +315,9 @@ public class MVMap<K, V> { ...@@ -312,9 +315,9 @@ public class MVMap<K, V> {
* Remove a key-value pair, if the key exists. * Remove a key-value pair, if the key exists.
* *
* @param key the key * @param key the key
* @return the old value if the key existed * @return the old value if the key existed, or null otherwise
*/ */
public V remove(K key) { public V remove(Object key) {
checkWrite(); checkWrite();
Page p = root; Page p = root;
if (p == null) { if (p == null) {
...@@ -468,6 +471,14 @@ public class MVMap<K, V> { ...@@ -468,6 +471,14 @@ public class MVMap<K, V> {
return c; return c;
} }
public Set<Map.Entry<K, V>> entrySet() {
HashMap<K, V> map = new HashMap<K, V>();
for (K k : keySet()) {
map.put(k, get(k));
}
return map.entrySet();
}
public Set<K> keySet() { public Set<K> keySet() {
checkOpen(); checkOpen();
final Page root = this.root; final Page root = this.root;
...@@ -582,6 +593,10 @@ public class MVMap<K, V> { ...@@ -582,6 +593,10 @@ public class MVMap<K, V> {
return id; return id;
} }
public boolean equals(Object o) {
return this == o;
}
public int size() { public int size() {
long size = getSize(); long size = getSize();
return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size; return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size;
...@@ -591,10 +606,6 @@ public class MVMap<K, V> { ...@@ -591,10 +606,6 @@ public class MVMap<K, V> {
return root == null ? 0 : root.getTotalCount(); return root == null ? 0 : root.getTotalCount();
} }
public boolean equals(Object o) {
return this == o;
}
long getCreateVersion() { long getCreateVersion() {
return createVersion; return createVersion;
} }
......
...@@ -38,27 +38,25 @@ header: ...@@ -38,27 +38,25 @@ header:
blockSize=4096 blockSize=4096
TODO: TODO:
- rename commit to incrementVersion - test with very small chunks, possibly speed up very small transactions
- implement complete java.util.Map interface - check what happens on concurrent reads and 1 write; multiple writes
- support all objects (using serialization)
- concurrent iterator (when to call commit; read on first hasNext())
- compact: use total max length instead of page count (liveCount)
- support background writes (store old version)
- avoid using java.util.Properties (it allocates quite a lot of memory)
- support large binaries
- support database version / schema version
- limited support for writing to old versions (branches) - limited support for writing to old versions (branches)
- atomic test-and-set (when supporting concurrent writes) - atomic test-and-set (when supporting concurrent writes)
- support background writes (store old version)
- file header could be a regular chunk, end of file the second - file header could be a regular chunk, end of file the second
- possibly split chunk data into immutable and mutable - possibly split chunk data into immutable and mutable
- test with very small chunks, possibly speed up very small transactions
- compact: use total max length instead of page count (liveCount)
- check what happens on concurrent reads and 1 write; multiple writes
- concurrent iterator (when to call commit)
- support large binaries
- support stores that span multiple files (chunks stored in other files) - support stores that span multiple files (chunks stored in other files)
- triggers - triggers
- support database version / schema version
- implement more counted b-tree (skip, get positions) - implement more counted b-tree (skip, get positions)
- merge pages if small - merge pages if small
- r-tree: add missing features (NN search for example) - r-tree: add missing features (NN search for example)
- compression: maybe hash table reset speeds up compression - compression: maybe hash table reset speeds up compression
- avoid using java.util.Properties (it allocates quite a lot of memory)
- support all objects (using serialization)
*/ */
......
...@@ -346,12 +346,14 @@ public class Page { ...@@ -346,12 +346,14 @@ public class Page {
keys[index] = key; keys[index] = key;
} }
public void setValue(int index, Object value) { public Object setValue(int index, Object value) {
Object old = values[index];
if ((sharedFlags & SHARED_VALUES) != 0) { if ((sharedFlags & SHARED_VALUES) != 0) {
values = Arrays.copyOf(values, values.length); values = Arrays.copyOf(values, values.length);
sharedFlags &= ~SHARED_VALUES; sharedFlags &= ~SHARED_VALUES;
} }
values[index] = value; values[index] = value;
return old;
} }
/** /**
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论