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

A persistent multi-version map: add store version and settings.

上级 670d256c
...@@ -31,6 +31,7 @@ public class TestMVStore extends TestBase { ...@@ -31,6 +31,7 @@ public class TestMVStore extends TestBase {
} }
public void test() throws InterruptedException { public void test() throws InterruptedException {
testSetting();
testIterateOldVersion(); testIterateOldVersion();
testObjects(); testObjects();
testExample(); testExample();
...@@ -54,6 +55,33 @@ public class TestMVStore extends TestBase { ...@@ -54,6 +55,33 @@ public class TestMVStore extends TestBase {
testSimple(); testSimple();
} }
private void testSetting() {
String fileName = getBaseDir() + "/testSetting.h3";
FileUtils.delete(fileName);
MVStore s = MVStore.open(fileName);
assertEquals(0, s.getCurrentVersion());
assertEquals(0, s.getStoreVersion());
s.setStoreVersion(1);
assertEquals(null, s.getSetting("hello"));
s.setSetting("test", "Hello");
assertEquals("Hello", s.getSetting("test"));
s.close();
s = MVStore.open(fileName);
assertEquals(0, s.getCurrentVersion());
assertEquals(0, s.getStoreVersion());
s.setStoreVersion(1);
assertEquals(null, s.getSetting("hello"));
s.setSetting("test", "Hello");
assertEquals("Hello", s.getSetting("test"));
s.store();
s.close();
s = MVStore.open(fileName);
assertEquals(1, s.getCurrentVersion());
assertEquals(1, s.getStoreVersion());
assertEquals("Hello", s.getSetting("test"));
s.close();
}
private void testIterateOldVersion() { private void testIterateOldVersion() {
MVStore s; MVStore s;
Map<Integer, Integer> map; Map<Integer, Integer> map;
...@@ -184,7 +212,8 @@ public class TestMVStore extends TestBase { ...@@ -184,7 +212,8 @@ public class TestMVStore extends TestBase {
for (int i = 20; i < 40; i++) { for (int i = 20; i < 40; i++) {
assertEquals("Hi", m.put(i, "Hello")); assertEquals("Hi", m.put(i, "Hello"));
} }
long old = s.incrementVersion(); long old = s.getCurrentVersion();
s.incrementVersion();
for (int i = 10; i < 15; i++) { for (int i = 10; i < 15; i++) {
m.put(i, "Hallo"); m.put(i, "Hallo");
} }
...@@ -313,7 +342,7 @@ public class TestMVStore extends TestBase { ...@@ -313,7 +342,7 @@ public class TestMVStore extends TestBase {
assertEquals(-1, s.getRetainChunk()); assertEquals(-1, s.getRetainChunk());
s.setRetainChunk(0); s.setRetainChunk(0);
assertEquals(0, s.getRetainChunk()); assertEquals(0, s.getRetainChunk());
assertEquals(1, s.getCurrentVersion()); assertEquals(0, s.getCurrentVersion());
assertFalse(s.hasUnsavedChanges()); assertFalse(s.hasUnsavedChanges());
MVMap<String, String> m = s.openMap("data", String.class, String.class); MVMap<String, String> m = s.openMap("data", String.class, String.class);
assertTrue(s.hasUnsavedChanges()); assertTrue(s.hasUnsavedChanges());
...@@ -324,12 +353,12 @@ public class TestMVStore extends TestBase { ...@@ -324,12 +353,12 @@ public class TestMVStore extends TestBase {
assertEquals("Hello", m.get("1")); assertEquals("Hello", m.get("1"));
long v2 = s.store(); long v2 = s.store();
assertEquals(2, v2); assertEquals(2, v2);
assertEquals(3, s.getCurrentVersion()); assertEquals(2, s.getCurrentVersion());
assertFalse(s.hasUnsavedChanges()); assertFalse(s.hasUnsavedChanges());
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
assertEquals(3, s.getCurrentVersion()); assertEquals(2, s.getCurrentVersion());
s.setRetainChunk(0); s.setRetainChunk(0);
meta = s.getMetaMap(); meta = s.getMetaMap();
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
...@@ -346,12 +375,12 @@ public class TestMVStore extends TestBase { ...@@ -346,12 +375,12 @@ public class TestMVStore extends TestBase {
assertNull(meta.get("map.data1")); assertNull(meta.get("map.data1"));
assertNull(m0.get("1")); assertNull(m0.get("1"));
assertEquals("Hello", m.get("1")); assertEquals("Hello", m.get("1"));
s.store(); assertEquals(2, s.store());
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
s.setRetainChunk(0); s.setRetainChunk(0);
assertEquals(3, s.getCurrentVersion()); assertEquals(2, s.getCurrentVersion());
meta = s.getMetaMap(); meta = s.getMetaMap();
assertTrue(meta.get("map.data") != null); assertTrue(meta.get("map.data") != null);
assertTrue(meta.get("map.data0") != null); assertTrue(meta.get("map.data0") != null);
...@@ -363,10 +392,10 @@ public class TestMVStore extends TestBase { ...@@ -363,10 +392,10 @@ public class TestMVStore extends TestBase {
assertFalse(m0.isReadOnly()); assertFalse(m0.isReadOnly());
m.put("1", "Hallo"); m.put("1", "Hallo");
s.incrementVersion(); s.incrementVersion();
assertEquals(4, s.getCurrentVersion()); assertEquals(3, s.getCurrentVersion());
long v4 = s.store(); long v4 = s.store();
assertEquals(4, v4); assertEquals(4, v4);
assertEquals(5, s.getCurrentVersion()); assertEquals(4, s.getCurrentVersion());
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
...@@ -395,12 +424,12 @@ public class TestMVStore extends TestBase { ...@@ -395,12 +424,12 @@ public class TestMVStore extends TestBase {
String fileName = getBaseDir() + "/testRollback.h3"; String fileName = getBaseDir() + "/testRollback.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
assertEquals(1, s.getCurrentVersion()); assertEquals(0, s.getCurrentVersion());
s.setMaxPageSize(5); s.setMaxPageSize(5);
MVMap<String, String> m = s.openMap("data", String.class, String.class); MVMap<String, String> m = s.openMap("data", String.class, String.class);
s.rollbackTo(0); s.rollbackTo(0);
assertTrue(m.isClosed()); assertTrue(m.isClosed());
assertEquals(1, s.getCurrentVersion()); assertEquals(0, s.getCurrentVersion());
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
MVMap<String, String> m0 = s.openMap("data0", String.class, String.class); MVMap<String, String> m0 = s.openMap("data0", String.class, String.class);
...@@ -411,7 +440,7 @@ public class TestMVStore extends TestBase { ...@@ -411,7 +440,7 @@ public class TestMVStore extends TestBase {
} }
long v1 = s.incrementVersion(); long v1 = s.incrementVersion();
assertEquals(1, v1); assertEquals(1, v1);
assertEquals(2, s.getCurrentVersion()); assertEquals(1, s.getCurrentVersion());
MVMap<String, String> m1 = s.openMap("data1", String.class, String.class); MVMap<String, String> m1 = s.openMap("data1", String.class, String.class);
assertEquals("Test", m2.get("1")); assertEquals("Test", m2.get("1"));
m.put("1", "Hallo"); m.put("1", "Hallo");
...@@ -421,7 +450,7 @@ public class TestMVStore extends TestBase { ...@@ -421,7 +450,7 @@ public class TestMVStore extends TestBase {
assertEquals("Hallo", m.get("1")); assertEquals("Hallo", m.get("1"));
assertEquals("Hallo", m1.get("1")); assertEquals("Hallo", m1.get("1"));
s.rollbackTo(v1); s.rollbackTo(v1);
assertEquals(2, s.getCurrentVersion()); assertEquals(1, s.getCurrentVersion());
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
assertEquals("Test", m2.get("" + i)); assertEquals("Test", m2.get("" + i));
} }
...@@ -443,11 +472,11 @@ public class TestMVStore extends TestBase { ...@@ -443,11 +472,11 @@ public class TestMVStore extends TestBase {
data.put("1", "Hello"); data.put("1", "Hello");
data.put("2", "World"); data.put("2", "World");
s.store(); s.store();
assertEquals("1/1///", m.get("map.data")); assertEquals("1/0///", m.get("map.data"));
assertTrue(m.containsKey("chunk.1")); assertTrue(m.containsKey("chunk.1"));
assertEquals("Hello", data.put("1", "Hallo")); assertEquals("Hello", data.put("1", "Hallo"));
s.store(); s.store();
assertEquals("1/1///", m.get("map.data")); assertEquals("1/0///", m.get("map.data"));
assertTrue(m.get("root.1").length() > 0); assertTrue(m.get("root.1").length() > 0);
assertTrue(m.containsKey("chunk.1")); assertTrue(m.containsKey("chunk.1"));
assertTrue(m.containsKey("chunk.2")); assertTrue(m.containsKey("chunk.2"));
...@@ -577,7 +606,7 @@ public class TestMVStore extends TestBase { ...@@ -577,7 +606,7 @@ public class TestMVStore extends TestBase {
m.put(j + i, "Hello " + j); m.put(j + i, "Hello " + j);
} }
s.store(); s.store();
// s.compact(80); s.compact(80);
s.close(); s.close();
long len = FileUtils.size(fileName); long len = FileUtils.size(fileName);
// System.out.println(" len:" + len); // System.out.println(" len:" + len);
......
...@@ -13,6 +13,7 @@ import java.util.HashMap; ...@@ -13,6 +13,7 @@ import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentMap;
/** /**
* A stored map. * A stored map.
...@@ -20,7 +21,8 @@ import java.util.Set; ...@@ -20,7 +21,8 @@ import java.util.Set;
* @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> extends AbstractMap<K, V> { public class MVMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V> {
/** /**
* The store. * The store.
...@@ -50,7 +52,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -50,7 +52,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
this.keyType = keyType; this.keyType = keyType;
this.valueType = valueType; this.valueType = valueType;
this.createVersion = createVersion; this.createVersion = createVersion;
this.root = Page.createEmpty(this, createVersion); this.root = Page.createEmpty(this, createVersion - 1);
} }
/** /**
...@@ -212,10 +214,12 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -212,10 +214,12 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
* Remove all entries, and close the map. * Remove all entries, and close the map.
*/ */
public void removeMap() { public void removeMap() {
checkWrite(); if (!store.isMetaMap(this)) {
root.removeAllRecursive(); checkWrite();
store.removeMap(name); root.removeAllRecursive();
close(); store.removeMap(name);
close();
}
} }
/** /**
...@@ -248,6 +252,69 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -248,6 +252,69 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
return result; return result;
} }
/**
* Add a key-value pair if it does not yet exist.
*
* @param key the key (may not be null)
* @return the old value if the key existed, or null otherwise
*/
public synchronized V putIfAbsent(K key, V value) {
V old = get(key);
if (old == null) {
put(key, value);
}
return old;
}
/**
* Remove a key-value pair if the value matches the stored one.
*
* @param key the key (may not be null)
* @param value the expected value
* @return true if the item was removed
*/
public synchronized boolean remove(Object key, Object value) {
V old = get(key);
if (old.equals(value)) {
remove(key);
return true;
}
return false;
}
/**
* Replace a value for an existing key, if the value matches.
*
* @param key the key (may not be null)
* @param oldValue the expected value
* @param newValue the new value
* @return true if the value was replaced
*/
public synchronized boolean replace(K key, V oldValue, V newValue) {
V old = get(key);
if (old.equals(oldValue)) {
put(key, newValue);
return true;
}
return false;
}
/**
* Replace a value for an existing key.
*
* @param key the key (may not be null)
* @param value the new value
* @return true if the value was replaced
*/
public synchronized V replace(K key, V value) {
V old = get(key);
if (old != null) {
put(key, value);
return old;
}
return null;
}
/** /**
* Remove a key-value pair. * Remove a key-value pair.
* *
...@@ -370,7 +437,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -370,7 +437,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
* @param rootPos the position, 0 for empty * @param rootPos the position, 0 for empty
*/ */
void setRootPos(long rootPos) { void setRootPos(long rootPos) {
root = rootPos == 0 ? Page.createEmpty(this, 0) : readPage(rootPos); root = rootPos == 0 ? Page.createEmpty(this, -1) : readPage(rootPos);
} }
/** /**
...@@ -382,7 +449,6 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -382,7 +449,6 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
public Iterator<K> keyIterator(K from) { public Iterator<K> keyIterator(K from) {
checkOpen(); checkOpen();
return new Cursor<K, V>(this, root, from); return new Cursor<K, V>(this, root, from);
// return new Cursor<K, V>(this, root, from);
} }
/** /**
...@@ -463,9 +529,9 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -463,9 +529,9 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
void rollbackTo(long version) { void rollbackTo(long version) {
checkWrite(); checkWrite();
removeUnusedOldVersions(); removeUnusedOldVersions();
if (version < createVersion) { if (version <= createVersion) {
removeMap(); removeMap();
} else if (root.getVersion() != version) { } else if (root.getVersion() >= version) {
// iterating in descending order - // iterating in descending order -
// this is not terribly efficient if there are many versions // this is not terribly efficient if there are many versions
ArrayList<Page> list = oldRoots; ArrayList<Page> list = oldRoots;
...@@ -474,7 +540,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> { ...@@ -474,7 +540,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> {
Page p = list.get(i); Page p = list.get(i);
root = p; root = p;
list.remove(i); list.remove(i);
if (p.getVersion() <= version) { if (p.getVersion() < version) {
break; break;
} }
} }
......
...@@ -35,8 +35,6 @@ header: ...@@ -35,8 +35,6 @@ header:
H:3,blockSize=4096,... H:3,blockSize=4096,...
TODO: TODO:
- support database version / app version
- atomic test-and-set (when supporting concurrent writes)
- possibly split chunk data into immutable and mutable - possibly split chunk data into immutable and mutable
- support stores that span multiple files (chunks stored in other files) - support stores that span multiple files (chunks stored in other files)
- triggers - triggers
...@@ -46,7 +44,7 @@ TODO: ...@@ -46,7 +44,7 @@ TODO:
- compression: maybe hash table reset speeds up compression - compression: maybe hash table reset speeds up compression
- use file level locking to ensure there is only one writer - use file level locking to ensure there is only one writer
- pluggable cache (specially for in-memory file systems) - pluggable cache (specially for in-memory file systems)
- store the factory class in the file header - maybe store the factory class in the file header
- support custom fields in the header - support custom fields in the header
- auto-server: store port in the header - auto-server: store port in the header
- recovery: keep a list of old chunks - recovery: keep a list of old chunks
...@@ -64,6 +62,7 @@ TODO: ...@@ -64,6 +62,7 @@ TODO:
- test and possibly improve compact operation (for large dbs) - test and possibly improve compact operation (for large dbs)
- support background writes (concurrent modification & store) - support background writes (concurrent modification & store)
- limited support for writing to old versions (branches) - limited support for writing to old versions (branches)
- support concurrent operations (including file I/O)
*/ */
...@@ -121,7 +120,7 @@ public class MVStore { ...@@ -121,7 +120,7 @@ public class MVStore {
private Compressor compressor; private Compressor compressor;
private long currentVersion = 1; private long currentVersion;
private int readCount; private int readCount;
private int writeCount; private int writeCount;
...@@ -317,6 +316,10 @@ public class MVStore { ...@@ -317,6 +316,10 @@ public class MVStore {
mapsChanged.remove(map.getId()); mapsChanged.remove(map.getId());
} }
boolean isMetaMap(MVMap<?, ?> map) {
return map == meta;
}
private String getDataType(Class<?> clazz) { private String getDataType(Class<?> clazz) {
if (clazz == String.class) { if (clazz == String.class) {
return ""; return "";
...@@ -483,10 +486,10 @@ public class MVStore { ...@@ -483,10 +486,10 @@ public class MVStore {
/** /**
* Increment the current version. * Increment the current version.
* *
* @return the old version * @return the new version
*/ */
public long incrementVersion() { public long incrementVersion() {
return currentVersion++; return ++currentVersion;
} }
/** /**
...@@ -494,7 +497,7 @@ public class MVStore { ...@@ -494,7 +497,7 @@ public class MVStore {
* there are no unsaved changes, otherwise it stores the data and increments * there are no unsaved changes, otherwise it stores the data and increments
* the current version. * the current version.
* *
* @return the version before the commit * @return the new version (incremented if there were changes)
*/ */
public long store() { public long store() {
if (!hasUnsavedChanges()) { if (!hasUnsavedChanges()) {
...@@ -514,7 +517,7 @@ public class MVStore { ...@@ -514,7 +517,7 @@ public class MVStore {
c.maxLengthLive = Long.MAX_VALUE; c.maxLengthLive = Long.MAX_VALUE;
c.start = Long.MAX_VALUE; c.start = Long.MAX_VALUE;
c.length = Integer.MAX_VALUE; c.length = Integer.MAX_VALUE;
c.version = currentVersion; c.version = currentVersion + 1;
chunks.put(c.id, c); chunks.put(c.id, c);
meta.put("chunk." + c.id, c.toString()); meta.put("chunk." + c.id, c.toString());
applyFreedChunks(); applyFreedChunks();
...@@ -1007,11 +1010,28 @@ public class MVStore { ...@@ -1007,11 +1010,28 @@ public class MVStore {
return true; return true;
} }
public int getStoreVersion() {
String x = getSetting("storeVersion");
return x == null ? 0 : Integer.parseInt(x);
}
public void setStoreVersion(int version) {
setSetting("storeVersion", Integer.toString(version));
}
public String getSetting(String key) {
return meta.get("setting." + key);
}
public void setSetting(String key, String value) {
meta.put("setting." + key, value);
}
/** /**
* Revert to the given version. All later changes (stored or not) are * Revert to the beginning of the given version. All later changes (stored
* forgotten. All maps that were created later are closed. A rollback to * or not) are forgotten. All maps that were created later are closed. A
* a version before the last stored version is immediately persisted. * rollback to a version before the last stored version is immediately
* Before this method returns, the current version is incremented. * persisted.
* *
* @param version the version to revert to * @param version the version to revert to
*/ */
...@@ -1050,7 +1070,7 @@ public class MVStore { ...@@ -1050,7 +1070,7 @@ public class MVStore {
} }
} }
for (MVMap<?, ?> m : maps.values()) { for (MVMap<?, ?> m : maps.values()) {
if (m.getCreateVersion() > version) { if (m.getCreateVersion() >= version) {
m.close(); m.close();
removeMap(m.getName()); removeMap(m.getName());
} else { } else {
...@@ -1061,7 +1081,7 @@ public class MVStore { ...@@ -1061,7 +1081,7 @@ public class MVStore {
} }
} }
} }
this.currentVersion = version + 1; this.currentVersion = version;
} }
private void revertTemp() { private void revertTemp() {
...@@ -1073,8 +1093,8 @@ public class MVStore { ...@@ -1073,8 +1093,8 @@ public class MVStore {
} }
/** /**
* Get the current version of the store. When a new store is created, the * Get the current version of the data. When a new store is created, the
* version is 1. For each commit, it is incremented by one. * version is 0. For each commit, it is incremented by one.
* *
* @return the version * @return the version
*/ */
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论