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