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

A persistent multi-version map: open the file in exclusive or shared mode; allow…

A persistent multi-version map: open the file in exclusive or shared mode; allow to read and update the file header.
上级 d6ffbca2
...@@ -405,7 +405,7 @@ class FileDisk extends FileBase { ...@@ -405,7 +405,7 @@ class FileDisk extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return file.getChannel().tryLock(); return file.getChannel().tryLock(position, size, shared);
} }
public void implCloseChannel() throws IOException { public void implCloseChannel() throws IOException {
......
...@@ -216,7 +216,7 @@ class FilePathMemLZF extends FilePathMem { ...@@ -216,7 +216,7 @@ class FilePathMemLZF extends FilePathMem {
*/ */
class FileMem extends FileBase { class FileMem extends FileBase {
private final FileMemData data; final FileMemData data;
private final boolean readOnly; private final boolean readOnly;
private long pos; private long pos;
...@@ -282,8 +282,29 @@ class FileMem extends FileBase { ...@@ -282,8 +282,29 @@ class FileMem extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
if (shared) {
if (!data.lockShared()) {
return null; return null;
} }
} else {
if (!data.lockExclusive()) {
return null;
}
}
FileLock lock = new FileLock(null, position, size, shared) {
@Override
public boolean isValid() {
return true;
}
@Override
public void release() throws IOException {
data.unlock();
}
};
return lock;
}
public String toString() { public String toString() {
return data.getName(); return data.getName();
...@@ -314,6 +335,8 @@ class FileMemData { ...@@ -314,6 +335,8 @@ class FileMemData {
private byte[][] data; private byte[][] data;
private long lastModified; private long lastModified;
private boolean isReadOnly; private boolean isReadOnly;
private boolean isLockedExclusive;
private int sharedLockCount;
static { static {
byte[] n = new byte[BLOCK_SIZE]; byte[] n = new byte[BLOCK_SIZE];
...@@ -322,6 +345,37 @@ class FileMemData { ...@@ -322,6 +345,37 @@ class FileMemData {
System.arraycopy(BUFFER, 0, COMPRESSED_EMPTY_BLOCK, 0, len); System.arraycopy(BUFFER, 0, COMPRESSED_EMPTY_BLOCK, 0, len);
} }
FileMemData(String name, boolean compress) {
this.name = name;
this.compress = compress;
data = new byte[0][];
lastModified = System.currentTimeMillis();
}
synchronized boolean lockExclusive() {
if (sharedLockCount > 0 || isLockedExclusive) {
return false;
}
isLockedExclusive = true;
return true;
}
synchronized boolean lockShared() {
if (isLockedExclusive) {
return false;
}
sharedLockCount++;
return true;
}
synchronized void unlock() {
if (isLockedExclusive) {
isLockedExclusive = false;
} else {
sharedLockCount = Math.max(0, sharedLockCount - 1);
}
}
/** /**
* This small cache compresses the data if an element leaves the cache. * This small cache compresses the data if an element leaves the cache.
*/ */
...@@ -371,13 +425,7 @@ class FileMemData { ...@@ -371,13 +425,7 @@ class FileMemData {
} }
return false; return false;
} }
}
FileMemData(String name, boolean compress) {
this.name = name;
this.compress = compress;
data = new byte[0][];
lastModified = System.currentTimeMillis();
} }
private static void compressLater(byte[][] data, int page) { private static void compressLater(byte[][] data, int page) {
......
...@@ -98,7 +98,7 @@ class FileNio extends FileBase { ...@@ -98,7 +98,7 @@ class FileNio extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return channel.tryLock(); return channel.tryLock(position, size, shared);
} }
public String toString() { public String toString() {
......
...@@ -233,7 +233,7 @@ class FileNioMapped extends FileBase { ...@@ -233,7 +233,7 @@ class FileNioMapped extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return file.getChannel().tryLock(); return file.getChannel().tryLock(position, size, shared);
} }
} }
...@@ -172,7 +172,7 @@ class FileRec extends FileBase { ...@@ -172,7 +172,7 @@ class FileRec extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return channel.tryLock(); return channel.tryLock(position, size, shared);
} }
} }
...@@ -370,7 +370,7 @@ class FileSplit extends FileBase { ...@@ -370,7 +370,7 @@ class FileSplit extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return list[0].tryLock(); return list[0].tryLock(position, size, shared);
} }
public String toString() { public String toString() {
......
...@@ -316,6 +316,19 @@ class FileZip extends FileBase { ...@@ -316,6 +316,19 @@ class FileZip extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
if (shared) {
return new FileLock(null, position, size, shared) {
@Override
public boolean isValid() {
return true;
}
@Override
public void release() throws IOException {
// ignore
}};
}
return null; return null;
} }
......
...@@ -10,6 +10,7 @@ import java.util.Map; ...@@ -10,6 +10,7 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import org.h2.dev.store.btree.MVMap; import org.h2.dev.store.btree.MVMap;
import org.h2.dev.store.btree.MVStore; import org.h2.dev.store.btree.MVStore;
import org.h2.dev.store.btree.MVStoreBuilder;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.Task; import org.h2.util.Task;
...@@ -34,7 +35,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -34,7 +35,7 @@ public class TestConcurrent extends TestMVStore {
} }
private void testConcurrentIterate() { private void testConcurrentIterate() {
MVStore s = MVStore.open(null, new TestMapFactory()); MVStore s = MVStoreBuilder.inMemory().with(new TestMapFactory()).open();
s.setMaxPageSize(3); s.setMaxPageSize(3);
final MVMap<Integer, Integer> map = s.openMap("test"); final MVMap<Integer, Integer> map = s.openMap("test");
final int len = 10; final int len = 10;
......
...@@ -8,6 +8,7 @@ package org.h2.test.store; ...@@ -8,6 +8,7 @@ package org.h2.test.store;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import org.h2.dev.store.btree.DataUtils; import org.h2.dev.store.btree.DataUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
...@@ -27,6 +28,7 @@ public class TestDataUtils extends TestBase { ...@@ -27,6 +28,7 @@ public class TestDataUtils extends TestBase {
} }
public void test() throws Exception { public void test() throws Exception {
testFletcher();
testMap(); testMap();
testMaxShortVarIntVarLong(); testMaxShortVarIntVarLong();
testVarIntVarLong(); testVarIntVarLong();
...@@ -35,6 +37,29 @@ public class TestDataUtils extends TestBase { ...@@ -35,6 +37,29 @@ public class TestDataUtils extends TestBase {
testEncodeLength(); testEncodeLength();
} }
private void testFletcher() throws Exception {
byte[] data = new byte[10000];
for (int i = 0; i < 10000; i += 1000) {
assertEquals(-1, DataUtils.getFletcher32(data, i));
}
Arrays.fill(data, (byte) 255);
for (int i = 0; i < 10000; i += 1000) {
assertEquals(-1, DataUtils.getFletcher32(data, i));
}
long last = 0;
for (int i = 1; i < 255; i++) {
Arrays.fill(data, (byte) i);
for (int j = 0; j < 10; j += 2) {
int x = DataUtils.getFletcher32(data, j);
assertTrue(x != last);
last = x;
}
}
Arrays.fill(data, (byte) 10);
assertEquals(0x1e1e1414, DataUtils.getFletcher32(data, 10000));
assertEquals(0x1e3fa7ed, DataUtils.getFletcher32("Fletcher32".getBytes(), 10));
}
private void testMap() { private void testMap() {
StringBuilder buff = new StringBuilder(); StringBuilder buff = new StringBuilder();
DataUtils.appendMap(buff, "", ""); DataUtils.appendMap(buff, "", "");
......
...@@ -64,7 +64,7 @@ public class TestMVRTree extends TestMVStore { ...@@ -64,7 +64,7 @@ public class TestMVRTree extends TestMVStore {
SpatialKey k = new SpatialKey(i, x - p, x + p, y - p, y + p); SpatialKey k = new SpatialKey(i, x - p, x + p, y - p, y + p);
r.add(k, "" + i); r.add(k, "" + i);
if (i > 0 && (i % len / 10) == 0) { if (i > 0 && (i % len / 10) == 0) {
s.save(); s.store();
} }
if (i > 0 && (i % 10000) == 0) { if (i > 0 && (i % 10000) == 0) {
render(r, getBaseDir() + "/test.png"); render(r, getBaseDir() + "/test.png");
...@@ -72,7 +72,7 @@ public class TestMVRTree extends TestMVStore { ...@@ -72,7 +72,7 @@ public class TestMVRTree extends TestMVStore {
} }
// System.out.println(prof.getTop(5)); // System.out.println(prof.getTop(5));
// System.out.println("add: " + (System.currentTimeMillis() - t)); // System.out.println("add: " + (System.currentTimeMillis() - t));
s.save(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
r = s.openMap("data", "r", "s2", ""); r = s.openMap("data", "r", "s2", "");
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
*/ */
package org.h2.test.store; package org.h2.test.store;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
...@@ -13,6 +16,8 @@ import java.util.TreeMap; ...@@ -13,6 +16,8 @@ import java.util.TreeMap;
import org.h2.dev.store.btree.Cursor; import org.h2.dev.store.btree.Cursor;
import org.h2.dev.store.btree.MVMap; import org.h2.dev.store.btree.MVMap;
import org.h2.dev.store.btree.MVStore; import org.h2.dev.store.btree.MVStore;
import org.h2.dev.store.btree.MVStoreBuilder;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.New; import org.h2.util.New;
...@@ -31,10 +36,14 @@ public class TestMVStore extends TestBase { ...@@ -31,10 +36,14 @@ public class TestMVStore extends TestBase {
TestBase.createCaller().init().test(); TestBase.createCaller().init().test();
} }
public void test() throws InterruptedException { public void test() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true);
testConcurrentOpen();
testFileHeader();
testFileHeaderCorruption();
testIndexSkip(); testIndexSkip();
testMinMaxNextKey(); testMinMaxNextKey();
testSetting(); testStoreVersion();
testIterateOldVersion(); testIterateOldVersion();
testObjects(); testObjects();
testExample(); testExample();
...@@ -58,6 +67,80 @@ public class TestMVStore extends TestBase { ...@@ -58,6 +67,80 @@ public class TestMVStore extends TestBase {
testSimple(); testSimple();
} }
private void testConcurrentOpen() {
String fileName = getBaseDir() + "/testConcurrentOpen.h3";
MVStore s = MVStoreBuilder.fileBased(fileName).open();
try {
MVStore s1 = MVStoreBuilder.fileBased(fileName).open();
s1.close();
fail();
} catch (Exception e) {
// expected
}
try {
MVStore s1 = MVStoreBuilder.fileBased(fileName).readOnly().open();
s1.close();
fail();
} catch (Exception e) {
// expected
}
s.close();
}
private void testFileHeader() {
String fileName = getBaseDir() + "/testFileHeader.h3";
MVStore s = openStore(fileName);
assertEquals("3", s.getFileHeader().get("H"));
s.getFileHeader().put("test", "123");
MVMap<Integer, Integer> map = s.openMap("test");
map.put(10, 100);
s.store();
s.close();
s = openStore(fileName);
assertEquals("123", s.getFileHeader().get("test"));
s.close();
}
private void testFileHeaderCorruption() throws IOException {
String fileName = getBaseDir() + "/testFileHeader.h3";
MVStore s = openStore(fileName);
MVMap<Integer, Integer> map = s.openMap("test");
map.put(10, 100);
FilePath f = FilePath.get(s.getFileName());
s.close();
int blockSize = 4 * 1024;
// test corrupt file headers
for (int i = 0; i <= blockSize; i += blockSize) {
FileChannel fc = f.open("rw");
ByteBuffer buff = ByteBuffer.allocate(4 * 1024);
fc.read(buff, i);
String h = new String(buff.array(), "UTF-8").trim();
int idx = h.indexOf("fletcher:");
int old = Character.digit(h.charAt(idx + "fletcher:".length()), 16);
int bad = (old + 1) & 15;
buff.put(idx + "fletcher:".length(),
(byte) Character.forDigit(bad, 16));
buff.rewind();
fc.write(buff, i);
fc.close();
if (i == 0) {
// if the first header is corrupt, the second
// header should be used
s = openStore(fileName);
map = s.openMap("test");
s.close();
} else {
// both headers are corrupt
try {
s = openStore(fileName);
fail();
} catch (Exception e) {
// expected
}
}
}
}
private void testIndexSkip() { private void testIndexSkip() {
MVStore s = openStore(null); MVStore s = openStore(null);
s.setMaxPageSize(4); s.setMaxPageSize(4);
...@@ -167,37 +250,30 @@ public class TestMVStore extends TestBase { ...@@ -167,37 +250,30 @@ public class TestMVStore extends TestBase {
} }
} }
private void testSetting() { private void testStoreVersion() {
String fileName = getBaseDir() + "/testSetting.h3"; String fileName = getBaseDir() + "/testStoreVersion.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s = MVStore.open(fileName); MVStore s = MVStore.open(fileName);
assertEquals(0, s.getCurrentVersion()); assertEquals(0, s.getCurrentVersion());
assertEquals(0, s.getStoreVersion()); assertEquals(0, s.getStoreVersion());
s.setStoreVersion(1); s.setStoreVersion(1);
assertEquals(null, s.getSetting("hello"));
s.setSetting("test", "Hello");
assertEquals("Hello", s.getSetting("test"));
s.close(); s.close();
s = MVStore.open(fileName); s = MVStore.open(fileName);
assertEquals(0, s.getCurrentVersion()); assertEquals(0, s.getCurrentVersion());
assertEquals(0, s.getStoreVersion()); assertEquals(0, s.getStoreVersion());
s.setStoreVersion(1); s.setStoreVersion(1);
assertEquals(null, s.getSetting("hello")); s.store();
s.setSetting("test", "Hello");
assertEquals("Hello", s.getSetting("test"));
s.save();
s.close(); s.close();
s = MVStore.open(fileName); s = MVStore.open(fileName);
assertEquals(1, s.getCurrentVersion()); assertEquals(1, s.getCurrentVersion());
assertEquals(1, s.getStoreVersion()); assertEquals(1, s.getStoreVersion());
assertEquals("Hello", s.getSetting("test"));
s.close(); s.close();
} }
private void testIterateOldVersion() { private void testIterateOldVersion() {
MVStore s; MVStore s;
Map<Integer, Integer> map; Map<Integer, Integer> map;
s = MVStore.open(null, new TestMapFactory()); s = MVStoreBuilder.inMemory().with(new TestMapFactory()).open();
map = s.openMap("test"); map = s.openMap("test");
int len = 100; int len = 100;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
...@@ -222,14 +298,14 @@ public class TestMVStore extends TestBase { ...@@ -222,14 +298,14 @@ public class TestMVStore extends TestBase {
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s; MVStore s;
Map<Object, Object> map; Map<Object, Object> map;
s = MVStore.open(fileName, new TestMapFactory()); s = MVStoreBuilder.fileBased(fileName).with(new TestMapFactory()).open();
map = s.openMap("test"); map = s.openMap("test");
map.put(1, "Hello"); map.put(1, "Hello");
map.put("2", 200); map.put("2", 200);
map.put(new Object[1], new Object[]{1, "2"}); map.put(new Object[1], new Object[]{1, "2"});
s.save(); s.store();
s.close(); s.close();
s = MVStore.open(fileName, new TestMapFactory()); s = MVStoreBuilder.fileBased(fileName).with(new TestMapFactory()).open();
map = s.openMap("test"); map = s.openMap("test");
assertEquals("Hello", map.get(1).toString()); assertEquals("Hello", map.get(1).toString());
assertEquals(200, ((Integer) map.get("2")).intValue()); assertEquals(200, ((Integer) map.get("2")).intValue());
...@@ -273,7 +349,7 @@ public class TestMVStore extends TestBase { ...@@ -273,7 +349,7 @@ public class TestMVStore extends TestBase {
map.openVersion(oldVersion); map.openVersion(oldVersion);
// store the newest data to disk // store the newest data to disk
s.save(); s.store();
// print the old version (can be done // print the old version (can be done
// concurrently with further modifications) // concurrently with further modifications)
...@@ -301,7 +377,7 @@ public class TestMVStore extends TestBase { ...@@ -301,7 +377,7 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
Integer x = m.get("value"); Integer x = m.get("value");
m.put("value", x == null ? 0 : x + 1); m.put("value", x == null ? 0 : x + 1);
s.save(); s.store();
} }
s.close(); s.close();
} }
...@@ -320,7 +396,7 @@ public class TestMVStore extends TestBase { ...@@ -320,7 +396,7 @@ public class TestMVStore extends TestBase {
m.put(i, "Hi"); m.put(i, "Hi");
} }
s.incrementVersion(); s.incrementVersion();
s.save(); s.store();
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"));
} }
...@@ -373,7 +449,7 @@ public class TestMVStore extends TestBase { ...@@ -373,7 +449,7 @@ public class TestMVStore extends TestBase {
assertTrue(mOld.isReadOnly()); assertTrue(mOld.isReadOnly());
s.getCurrentVersion(); s.getCurrentVersion();
s.setRetainChunk(0); s.setRetainChunk(0);
long old2 = s.save(); long old2 = s.store();
// the old version is still available // the old version is still available
assertEquals("Hello", mOld.get("1")); assertEquals("Hello", mOld.get("1"));
...@@ -381,7 +457,7 @@ public class TestMVStore extends TestBase { ...@@ -381,7 +457,7 @@ public class TestMVStore extends TestBase {
m.put("1", "Hi"); m.put("1", "Hi");
assertEquals("Welt", m.remove("2")); assertEquals("Welt", m.remove("2"));
s.save(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
...@@ -404,13 +480,13 @@ public class TestMVStore extends TestBase { ...@@ -404,13 +480,13 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
m.put(i, "Hello World"); m.put(i, "Hello World");
} }
s.save(); s.store();
s.close(); s.close();
long len = FileUtils.size(fileName); long len = FileUtils.size(fileName);
s = openStore(fileName); s = openStore(fileName);
m = s.openMap("data", Integer.class, String.class); m = s.openMap("data", Integer.class, String.class);
m.clear(); m.clear();
s.save(); s.store();
s.compact(100); s.compact(100);
s.close(); s.close();
long len2 = FileUtils.size(fileName); long len2 = FileUtils.size(fileName);
...@@ -431,18 +507,18 @@ public class TestMVStore extends TestBase { ...@@ -431,18 +507,18 @@ public class TestMVStore extends TestBase {
} }
assertEquals(1000, m.size()); assertEquals(1000, m.size());
assertEquals(281, s.getUnsavedPageCount()); assertEquals(281, s.getUnsavedPageCount());
s.save(); s.store();
assertEquals(3, s.getWriteCount()); assertEquals(3, s.getFileWriteCount());
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
m = s.openMap("data", Integer.class, String.class); m = s.openMap("data", Integer.class, String.class);
m.clear(); m.clear();
assertEquals(0, m.size()); assertEquals(0, m.size());
s.save(); s.store();
// ensure only nodes are read, but not leaves // ensure only nodes are read, but not leaves
assertEquals(34, s.getReadCount()); assertEquals(34, s.getFileReadCount());
assertEquals(2, s.getWriteCount()); assertEquals(2, s.getFileWriteCount());
s.close(); s.close();
} }
...@@ -463,7 +539,7 @@ public class TestMVStore extends TestBase { ...@@ -463,7 +539,7 @@ public class TestMVStore extends TestBase {
assertEquals(1, s.incrementVersion()); assertEquals(1, s.incrementVersion());
s.rollbackTo(1); s.rollbackTo(1);
assertEquals("Hello", m.get("1")); assertEquals("Hello", m.get("1"));
long v2 = s.save(); long v2 = s.store();
assertEquals(2, v2); assertEquals(2, v2);
assertEquals(2, s.getCurrentVersion()); assertEquals(2, s.getCurrentVersion());
assertFalse(s.hasUnsavedChanges()); assertFalse(s.hasUnsavedChanges());
...@@ -487,7 +563,7 @@ public class TestMVStore extends TestBase { ...@@ -487,7 +563,7 @@ 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"));
assertEquals(2, s.save()); assertEquals(2, s.store());
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
...@@ -505,7 +581,7 @@ public class TestMVStore extends TestBase { ...@@ -505,7 +581,7 @@ public class TestMVStore extends TestBase {
m.put("1", "Hallo"); m.put("1", "Hallo");
s.incrementVersion(); s.incrementVersion();
assertEquals(3, s.getCurrentVersion()); assertEquals(3, s.getCurrentVersion());
long v4 = s.save(); long v4 = s.store();
assertEquals(4, v4); assertEquals(4, v4);
assertEquals(4, s.getCurrentVersion()); assertEquals(4, s.getCurrentVersion());
s.close(); s.close();
...@@ -514,7 +590,7 @@ public class TestMVStore extends TestBase { ...@@ -514,7 +590,7 @@ public class TestMVStore extends TestBase {
s.setRetainChunk(0); s.setRetainChunk(0);
m = s.openMap("data", String.class, String.class); m = s.openMap("data", String.class, String.class);
m.put("1", "Hello"); m.put("1", "Hello");
s.save(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
...@@ -583,11 +659,11 @@ public class TestMVStore extends TestBase { ...@@ -583,11 +659,11 @@ public class TestMVStore extends TestBase {
MVMap<String, String> data = s.openMap("data", String.class, String.class); MVMap<String, String> data = s.openMap("data", String.class, String.class);
data.put("1", "Hello"); data.put("1", "Hello");
data.put("2", "World"); data.put("2", "World");
s.save(); s.store();
assertEquals("1/0///", 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.save(); s.store();
assertEquals("1/0///", 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"));
...@@ -649,10 +725,10 @@ public class TestMVStore extends TestBase { ...@@ -649,10 +725,10 @@ public class TestMVStore extends TestBase {
m.put(i, o); m.put(i, o);
i++; i++;
if (i % 10000 == 0) { if (i % 10000 == 0) {
s.save(); s.store();
} }
} }
s.save(); s.store();
s.close(); s.close();
// System.out.println(prof.getTop(5)); // System.out.println(prof.getTop(5));
// System.out.println("store time " + (System.currentTimeMillis() - t)); // System.out.println("store time " + (System.currentTimeMillis() - t));
...@@ -680,7 +756,7 @@ public class TestMVStore extends TestBase { ...@@ -680,7 +756,7 @@ public class TestMVStore extends TestBase {
// p = new Profiler(); // p = new Profiler();
//p.startCollecting(); //p.startCollecting();
// t = System.currentTimeMillis(); // t = System.currentTimeMillis();
s.save(); s.store();
// System.out.println("store: " + (System.currentTimeMillis() - t)); // System.out.println("store: " + (System.currentTimeMillis() - t));
// System.out.println(p.getTop(5)); // System.out.println(p.getTop(5));
assertEquals("hello 0", m.remove(0)); assertEquals("hello 0", m.remove(0));
...@@ -688,7 +764,7 @@ public class TestMVStore extends TestBase { ...@@ -688,7 +764,7 @@ public class TestMVStore extends TestBase {
for (int i = 1; i < count; i++) { for (int i = 1; i < count; i++) {
assertEquals("hello " + i, m.get(i)); assertEquals("hello " + i, m.get(i));
} }
s.save(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
m = s.openMap("data", Integer.class, String.class); m = s.openMap("data", Integer.class, String.class);
...@@ -699,7 +775,7 @@ public class TestMVStore extends TestBase { ...@@ -699,7 +775,7 @@ public class TestMVStore extends TestBase {
for (int i = 1; i < count; i++) { for (int i = 1; i < count; i++) {
m.remove(i); m.remove(i);
} }
s.save(); s.store();
assertNull(m.get(0)); assertNull(m.get(0));
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
assertNull(m.get(i)); assertNull(m.get(i));
...@@ -717,7 +793,7 @@ public class TestMVStore extends TestBase { ...@@ -717,7 +793,7 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
m.put(j + i, "Hello " + j); m.put(j + i, "Hello " + j);
} }
s.save(); s.store();
s.compact(80); s.compact(80);
s.close(); s.close();
long len = FileUtils.size(fileName); long len = FileUtils.size(fileName);
...@@ -735,7 +811,7 @@ public class TestMVStore extends TestBase { ...@@ -735,7 +811,7 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
m.remove(i); m.remove(i);
} }
s.save(); s.store();
s.compact(80); s.compact(80);
s.close(); s.close();
// len = FileUtils.size(fileName); // len = FileUtils.size(fileName);
...@@ -758,12 +834,12 @@ public class TestMVStore extends TestBase { ...@@ -758,12 +834,12 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
m.put(i, "Hello"); m.put(i, "Hello");
} }
s.save(); s.store();
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
assertEquals("Hello", m.get(i)); assertEquals("Hello", m.get(i));
assertEquals("Hello", m.remove(i)); assertEquals("Hello", m.remove(i));
} }
s.save(); s.store();
s.close(); s.close();
long len = FileUtils.size(fileName); long len = FileUtils.size(fileName);
if (initialLength == 0) { if (initialLength == 0) {
...@@ -866,7 +942,7 @@ public class TestMVStore extends TestBase { ...@@ -866,7 +942,7 @@ public class TestMVStore extends TestBase {
si.put("Test", 10); si.put("Test", 10);
MVMap<String, String> ss = s.openMap("stringString", String.class, String.class); MVMap<String, String> ss = s.openMap("stringString", String.class, String.class);
ss.put("Hello", "World"); ss.put("Hello", "World");
s.save(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
is = s.openMap("intString", Integer.class, String.class); is = s.openMap("intString", Integer.class, String.class);
...@@ -890,7 +966,7 @@ public class TestMVStore extends TestBase { ...@@ -890,7 +966,7 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
m.put(i, "hello " + i); m.put(i, "hello " + i);
} }
s.save(); s.store();
it = m.keyIterator(null); it = m.keyIterator(null);
it.next(); it.next();
assertThrows(UnsupportedOperationException.class, it).remove(); assertThrows(UnsupportedOperationException.class, it).remove();
...@@ -921,7 +997,7 @@ public class TestMVStore extends TestBase { ...@@ -921,7 +997,7 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
m.put(i, "hello " + i); m.put(i, "hello " + i);
} }
s.save(); s.store();
// closing twice should be fine // closing twice should be fine
s.close(); s.close();
s.close(); s.close();
...@@ -935,14 +1011,14 @@ public class TestMVStore extends TestBase { ...@@ -935,14 +1011,14 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
m.put(i, "hello " + i); m.put(i, "hello " + i);
} }
s.save(); s.store();
assertEquals("hello 0", m.remove(0)); assertEquals("hello 0", m.remove(0));
assertNull(m.get(0)); assertNull(m.get(0));
for (int i = 1; i < 3; i++) { for (int i = 1; i < 3; i++) {
assertEquals("hello " + i, m.get(i)); assertEquals("hello " + i, m.get(i));
} }
s.save(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
...@@ -961,11 +1037,12 @@ public class TestMVStore extends TestBase { ...@@ -961,11 +1037,12 @@ public class TestMVStore extends TestBase {
* @return the store * @return the store
*/ */
protected static MVStore openStore(String fileName) { protected static MVStore openStore(String fileName) {
MVStore store = MVStore.open(fileName, new TestMapFactory()); MVStore store = MVStoreBuilder.fileBased(fileName).with(new TestMapFactory()).open();
store.setMaxPageSize(1000); store.setMaxPageSize(1000);
return store; return store;
} }
/** /**
* Log the message. * Log the message.
* *
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
*/ */
package org.h2.test.store; package org.h2.test.store;
import org.h2.compress.CompressLZF;
import org.h2.compress.Compressor;
import org.h2.dev.store.btree.MVMap; import org.h2.dev.store.btree.MVMap;
import org.h2.dev.store.btree.MVStore; import org.h2.dev.store.btree.MVStore;
import org.h2.dev.store.btree.DataType; import org.h2.dev.store.btree.DataType;
...@@ -59,9 +57,4 @@ public class TestMapFactory implements MapFactory { ...@@ -59,9 +57,4 @@ public class TestMapFactory implements MapFactory {
throw new RuntimeException("Unsupported object class " + objectClass.toString()); throw new RuntimeException("Unsupported object class " + objectClass.toString());
} }
@Override
public Compressor buildCompressor() {
return new CompressLZF();
}
} }
...@@ -293,7 +293,7 @@ class FileDebug extends FileBase { ...@@ -293,7 +293,7 @@ class FileDebug extends FileBase {
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
debug("tryLock"); debug("tryLock");
return channel.tryLock(); return channel.tryLock(position, size, shared);
} }
} }
...@@ -209,7 +209,7 @@ class FileUnstable extends FileBase { ...@@ -209,7 +209,7 @@ class FileUnstable extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return channel.tryLock(); return channel.tryLock(position, size, shared);
} }
} }
\ No newline at end of file
...@@ -188,7 +188,7 @@ class FileCrypt extends FileBase { ...@@ -188,7 +188,7 @@ class FileCrypt extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
return file.tryLock(); return file.tryLock(position, size, shared);
} }
public void implCloseChannel() throws IOException { public void implCloseChannel() throws IOException {
......
...@@ -397,6 +397,19 @@ class FileZip2 extends FileBase { ...@@ -397,6 +397,19 @@ class FileZip2 extends FileBase {
} }
public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException { public synchronized FileLock tryLock(long position, long size, boolean shared) throws IOException {
if (shared) {
return new FileLock(null, position, size, shared) {
@Override
public boolean isValid() {
return true;
}
@Override
public void release() throws IOException {
// ignore
}};
}
return null; return null;
} }
......
...@@ -11,6 +11,8 @@ import java.io.IOException; ...@@ -11,6 +11,8 @@ import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import org.h2.util.New; import org.h2.util.New;
...@@ -425,7 +427,23 @@ public class DataUtils { ...@@ -425,7 +427,23 @@ public class DataUtils {
} }
/** /**
* Append a key-value pair to the string buffer. Keys may not contain a * Append a map to the string builder, sorted by key.
*
* @param buff the target buffer
* @param map the map
* @return the string builder
*/
public static StringBuilder appendMap(StringBuilder buff, HashMap<String, ?> map) {
ArrayList<String> list = New.arrayList(map.keySet());
Collections.sort(list);
for (String k : list) {
appendMap(buff, k, map.get(k));
}
return buff;
}
/**
* Append a key-value pair to the string builder. Keys may not contain a
* colon. Values that contain a comma or a double quote are enclosed in * colon. Values that contain a comma or a double quote are enclosed in
* double quotes, with special characters escaped using a backslash. * double quotes, with special characters escaped using a backslash.
* *
...@@ -490,4 +508,26 @@ public class DataUtils { ...@@ -490,4 +508,26 @@ public class DataUtils {
return map; return map;
} }
/**
* Calculate the Fletcher32 checksum.
*
* @param bytes the bytes
* @param length the message length (must be a multiple of 2)
* @return the checksum
*/
public static int getFletcher32(byte[] bytes, int length) {
int s1 = 0xffff, s2 = 0xffff;
for (int i = 0; i < length;) {
for (int end = Math.min(i + 718, length); i < end;) {
int x = ((bytes[i++] & 0xff) << 8) | (bytes[i++] & 0xff);
s2 += s1 += x;
}
s1 = (s1 & 0xffff) + (s1 >>> 16);
s2 = (s2 & 0xffff) + (s2 >>> 16);
}
s1 = (s1 & 0xffff) + (s1 >>> 16);
s2 = (s2 & 0xffff) + (s2 >>> 16);
return (s2 << 16) | s1;
}
} }
...@@ -9,12 +9,14 @@ package org.h2.dev.store.btree; ...@@ -9,12 +9,14 @@ package org.h2.dev.store.btree;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import org.h2.compress.CompressLZF; import org.h2.compress.CompressLZF;
import org.h2.compress.Compressor; import org.h2.compress.Compressor;
import org.h2.dev.store.FilePathCache; import org.h2.dev.store.FilePathCache;
...@@ -32,7 +34,7 @@ header: (blockSize) bytes ...@@ -32,7 +34,7 @@ header: (blockSize) bytes
[ chunk ] * [ chunk ] *
(there are two headers for security) (there are two headers for security)
header: header:
H:3,blockSize=4096,... H:3,...
TODO: TODO:
- support custom fields in the file header (auto-server ip address,...) - support custom fields in the file header (auto-server ip address,...)
...@@ -62,6 +64,13 @@ TODO: ...@@ -62,6 +64,13 @@ TODO:
- compact: avoid processing pages using a counting bloom filter - compact: avoid processing pages using a counting bloom filter
- defragment (re-creating maps, specially those with small pages) - defragment (re-creating maps, specially those with small pages)
- write using ByteArrayOutputStream; remove DataType.getMaxLength - write using ByteArrayOutputStream; remove DataType.getMaxLength
- file header: check versionRead and versionWrite (and possibly rename; or rename version)
- chunk header: store changed chunk data as row; maybe after the root
- chunk checksum (header, last page, 2 bytes per page?)
- allow renaming maps
- file locking: solve problem that locks are shared for a VM
- online backup
- MapFactory is the wrong name (StorePlugin?) or is too flexible
*/ */
...@@ -80,6 +89,8 @@ public class MVStore { ...@@ -80,6 +89,8 @@ public class MVStore {
*/ */
public static final StringType STRING_TYPE = new StringType(); public static final StringType STRING_TYPE = new StringType();
static final int BLOCK_SIZE = 4 * 1024;
private final String fileName; private final String fileName;
private final MapFactory mapFactory; private final MapFactory mapFactory;
...@@ -88,8 +99,8 @@ public class MVStore { ...@@ -88,8 +99,8 @@ public class MVStore {
private int maxPageSize = 4 * 1024; private int maxPageSize = 4 * 1024;
private FileChannel file; private FileChannel file;
private FileLock fileLock;
private long fileSize; private long fileSize;
private final int blockSize = 4 * 1024;
private long rootChunkStart; private long rootChunkStart;
private final CacheLongKeyLIRS<Page> cache = CacheLongKeyLIRS.newInstance( private final CacheLongKeyLIRS<Page> cache = CacheLongKeyLIRS.newInstance(
...@@ -112,6 +123,11 @@ public class MVStore { ...@@ -112,6 +123,11 @@ public class MVStore {
* The set of maps with potentially unsaved changes. * The set of maps with potentially unsaved changes.
*/ */
private final HashMap<Integer, MVMap<?, ?>> mapsChanged = New.hashMap(); private final HashMap<Integer, MVMap<?, ?>> mapsChanged = New.hashMap();
private HashMap<String, String> fileHeader = New.hashMap();
private final boolean readOnly;
private int lastMapId; private int lastMapId;
private boolean reuseSpace = true; private boolean reuseSpace = true;
...@@ -121,37 +137,27 @@ public class MVStore { ...@@ -121,37 +137,27 @@ public class MVStore {
private Compressor compressor; private Compressor compressor;
private long currentVersion; private long currentVersion;
private int readCount; private int fileReadCount;
private int writeCount; private int fileWriteCount;
private int unsavedPageCount; private int unsavedPageCount;
private MVStore(String fileName, MapFactory mapFactory) { MVStore(HashMap<String, Object> config) {
this.fileName = fileName; this.fileName = (String) config.get("fileName");
this.mapFactory = mapFactory; this.mapFactory = (MapFactory) config.get("mapFactory");
this.compressor = mapFactory == null ? this.readOnly = "r".equals(config.get("openMode"));
new CompressLZF() : this.compressor = "0".equals(config.get("compression")) ? null : new CompressLZF();
mapFactory.buildCompressor();
}
/**
* Open a tree store.
*
* @param fileName the file name
* @return the store
*/
public static MVStore open(String fileName) {
return open(fileName, null);
} }
/** /**
* Open a tree store. * Open a store in exclusive mode.
* *
* @param fileName the file name (null for in-memory) * @param fileName the file name (null for in-memory)
* @param mapFactory the map factory
* @return the store * @return the store
*/ */
public static MVStore open(String fileName, MapFactory mapFactory) { public static MVStore open(String fileName) {
MVStore s = new MVStore(fileName, mapFactory); HashMap<String, Object> config = New.hashMap();
config.put("fileName", fileName);
MVStore s = new MVStore(config);
s.open(); s.open();
return s; return s;
} }
...@@ -270,7 +276,11 @@ public class MVStore { ...@@ -270,7 +276,11 @@ public class MVStore {
} }
/** /**
* Get the metadata map. It contains the following entries: * Get the metadata map. This data is for informational purposes only. The
* data is subject to change in future versions. The data should not be
* modified (doing so may corrupt the store).
* <p>
* It contains the following entries:
* *
* <pre> * <pre>
* map.{name} = {mapId}/{keyType}/{valueType} * map.{name} = {mapId}/{keyType}/{valueType}
...@@ -351,7 +361,7 @@ public class MVStore { ...@@ -351,7 +361,7 @@ public class MVStore {
mapsChanged.put(map.getId(), map); mapsChanged.put(map.getId(), map);
} }
private void open() { void open() {
meta = new MVMap<String, String>(this, 0, "meta", STRING_TYPE, STRING_TYPE, 0); meta = new MVMap<String, String>(this, 0, "meta", STRING_TYPE, STRING_TYPE, 0);
if (fileName == null) { if (fileName == null) {
return; return;
...@@ -360,14 +370,26 @@ public class MVStore { ...@@ -360,14 +370,26 @@ public class MVStore {
try { try {
log("file open"); log("file open");
file = FilePathCache.wrap(FilePath.get(fileName).open("rw")); file = FilePathCache.wrap(FilePath.get(fileName).open("rw"));
if (file.tryLock() == null) { if (readOnly) {
fileLock = file.tryLock(0, Long.MAX_VALUE, true);
if (fileLock == null) {
throw new RuntimeException("The file is locked: " + fileName);
}
} else {
fileLock = file.tryLock();
if (fileLock == null) {
throw new RuntimeException("The file is locked: " + fileName); throw new RuntimeException("The file is locked: " + fileName);
} }
}
fileSize = file.size(); fileSize = file.size();
if (fileSize == 0) { if (fileSize == 0) {
writeHeader(); fileHeader.put("H", "3");
fileHeader.put("blockSize", "" + BLOCK_SIZE);
fileHeader.put("format", "1");
fileHeader.put("formatRead", "1");
writeFileHeader();
} else { } else {
readHeader(); readFileHeader();
if (rootChunkStart > 0) { if (rootChunkStart > 0) {
readMeta(); readMeta();
} }
...@@ -400,38 +422,54 @@ public class MVStore { ...@@ -400,38 +422,54 @@ public class MVStore {
} }
} }
private void writeHeader() { private void readFileHeader() {
try { try {
ByteBuffer header = ByteBuffer.allocate(blockSize); byte[] headers = new byte[2 * BLOCK_SIZE];
String h = "H:3," + fileReadCount++;
"versionRead:1," + DataUtils.readFully(file, 0, ByteBuffer.wrap(headers));
"versionWrite:1," + for (int i = 0; i <= BLOCK_SIZE; i += BLOCK_SIZE) {
"blockSize:" + blockSize + "," + String s = new String(headers, i, BLOCK_SIZE, "UTF-8").trim();
"rootChunk:" + rootChunkStart + "," + fileHeader = DataUtils.parseMap(s);
"lastMapId:" + lastMapId + "," + rootChunkStart = Long.parseLong(fileHeader.get("rootChunk"));
"version:" + currentVersion; currentVersion = Long.parseLong(fileHeader.get("version"));
header.put(h.getBytes("UTF-8")); lastMapId = Integer.parseInt(fileHeader.get("lastMapId"));
header.rewind(); int check = (int) Long.parseLong(fileHeader.get("fletcher"), 16);
writeCount++; s = s.substring(0, s.lastIndexOf("fletcher") - 1) + " ";
DataUtils.writeFully(file, 0, header); byte[] bytes = s.getBytes("UTF-8");
header.rewind(); int checksum = DataUtils.getFletcher32(bytes,
DataUtils.writeFully(file, blockSize, header); bytes.length / 2 * 2);
fileSize = Math.max(fileSize, 2 * blockSize); if (check == checksum) {
return;
}
}
throw new RuntimeException("File header is corrupt");
} catch (Exception e) { } catch (Exception e) {
throw convert(e); throw convert(e);
} }
} }
private void readHeader() { private void writeFileHeader() {
try { try {
byte[] headers = new byte[blockSize * 2]; StringBuilder buff = new StringBuilder();
readCount++; fileHeader.put("lastMapId", "" + lastMapId);
file.read(ByteBuffer.wrap(headers), 0); fileHeader.put("rootChunk", "" + rootChunkStart);
String s = new String(headers, 0, blockSize, "UTF-8").trim(); fileHeader.put("version", "" + currentVersion);
HashMap<String, String> map = DataUtils.parseMap(s); DataUtils.appendMap(buff, fileHeader);
rootChunkStart = Long.parseLong(map.get("rootChunk")); byte[] bytes = (buff.toString() + " ").getBytes("UTF-8");
currentVersion = Long.parseLong(map.get("version")); int checksum = DataUtils.getFletcher32(bytes, bytes.length / 2 * 2);
lastMapId = Integer.parseInt(map.get("lastMapId")); DataUtils.appendMap(buff, "fletcher", Integer.toHexString(checksum));
bytes = buff.toString().getBytes("UTF-8");
if (bytes.length > BLOCK_SIZE) {
throw new IllegalArgumentException("File header too large: " + buff);
}
ByteBuffer header = ByteBuffer.allocate(2 * BLOCK_SIZE);
header.put(bytes);
header.position(BLOCK_SIZE);
header.put(bytes);
header.rewind();
fileWriteCount++;
DataUtils.writeFully(file, 0, header);
fileSize = Math.max(fileSize, 2 * BLOCK_SIZE);
} catch (Exception e) { } catch (Exception e) {
throw convert(e); throw convert(e);
} }
...@@ -449,6 +487,10 @@ public class MVStore { ...@@ -449,6 +487,10 @@ public class MVStore {
try { try {
shrinkFileIfPossible(0); shrinkFileIfPossible(0);
log("file close"); log("file close");
if (fileLock != null) {
fileLock.release();
fileLock = null;
}
file.close(); file.close();
for (MVMap<?, ?> m : New.arrayList(maps.values())) { for (MVMap<?, ?> m : New.arrayList(maps.values())) {
m.close(); m.close();
...@@ -477,16 +519,6 @@ public class MVStore { ...@@ -477,16 +519,6 @@ public class MVStore {
return chunks.get(DataUtils.getPageChunkId(pos)); return chunks.get(DataUtils.getPageChunkId(pos));
} }
private long getFilePosition(long pos) {
Chunk c = getChunk(pos);
if (c == null) {
throw new RuntimeException("Chunk " + DataUtils.getPageChunkId(pos) + " not found");
}
long filePos = c.start;
filePos += DataUtils.getPageOffset(pos);
return filePos;
}
/** /**
* Increment the current version. * Increment the current version.
* *
...@@ -503,7 +535,7 @@ public class MVStore { ...@@ -503,7 +535,7 @@ public class MVStore {
* *
* @return the new version (incremented if there were changes) * @return the new version (incremented if there were changes)
*/ */
public long save() { public long store() {
if (!hasUnsavedChanges()) { if (!hasUnsavedChanges()) {
return currentVersion; return currentVersion;
} }
...@@ -592,7 +624,7 @@ public class MVStore { ...@@ -592,7 +624,7 @@ public class MVStore {
c.writeHeader(buff); c.writeHeader(buff);
buff.rewind(); buff.rewind();
try { try {
writeCount++; fileWriteCount++;
DataUtils.writeFully(file, filePos, buff); DataUtils.writeFully(file, filePos, buff);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
...@@ -603,7 +635,7 @@ public class MVStore { ...@@ -603,7 +635,7 @@ public class MVStore {
long version = incrementVersion(); long version = incrementVersion();
// write the new version (after the commit) // write the new version (after the commit)
writeHeader(); writeFileHeader();
shrinkFileIfPossible(1); shrinkFileIfPossible(1);
unsavedPageCount = 0; unsavedPageCount = 0;
return version; return version;
...@@ -634,7 +666,7 @@ public class MVStore { ...@@ -634,7 +666,7 @@ public class MVStore {
if (used >= fileSize) { if (used >= fileSize) {
return; return;
} }
if (minPercent > 0 && fileSize - used < blockSize) { if (minPercent > 0 && fileSize - used < BLOCK_SIZE) {
return; return;
} }
int savedPercent = (int) (100 - (used * 100 / fileSize)); int savedPercent = (int) (100 - (used * 100 / fileSize));
...@@ -654,10 +686,10 @@ public class MVStore { ...@@ -654,10 +686,10 @@ public class MVStore {
if (c.start == Long.MAX_VALUE) { if (c.start == Long.MAX_VALUE) {
continue; continue;
} }
int last = (int) ((c.start + c.length) / blockSize); int last = (int) ((c.start + c.length) / BLOCK_SIZE);
min = Math.max(min, last + 1); min = Math.max(min, last + 1);
} }
return min * blockSize; return min * BLOCK_SIZE;
} }
private long allocateChunk(long length) { private long allocateChunk(long length) {
...@@ -671,11 +703,11 @@ public class MVStore { ...@@ -671,11 +703,11 @@ public class MVStore {
if (c.start == Long.MAX_VALUE) { if (c.start == Long.MAX_VALUE) {
continue; continue;
} }
int first = (int) (c.start / blockSize); int first = (int) (c.start / BLOCK_SIZE);
int last = (int) ((c.start + c.length) / blockSize); int last = (int) ((c.start + c.length) / BLOCK_SIZE);
set.set(first, last +1); set.set(first, last +1);
} }
int required = (int) (length / blockSize) + 1; int required = (int) (length / BLOCK_SIZE) + 1;
for (int i = 0; i < set.size(); i++) { for (int i = 0; i < set.size(); i++) {
if (!set.get(i)) { if (!set.get(i)) {
boolean ok = true; boolean ok = true;
...@@ -686,11 +718,11 @@ public class MVStore { ...@@ -686,11 +718,11 @@ public class MVStore {
} }
} }
if (ok) { if (ok) {
return i * blockSize; return i * BLOCK_SIZE;
} }
} }
} }
return set.size() * blockSize; return set.size() * BLOCK_SIZE;
} }
/** /**
...@@ -712,8 +744,8 @@ public class MVStore { ...@@ -712,8 +744,8 @@ public class MVStore {
private Chunk readChunkHeader(long start) { private Chunk readChunkHeader(long start) {
try { try {
readCount++; fileReadCount++;
ByteBuffer buff = ByteBuffer.wrap(new byte[32]); ByteBuffer buff = ByteBuffer.allocate(32);
DataUtils.readFully(file, start, buff); DataUtils.readFully(file, start, buff);
buff.rewind(); buff.rewind();
return Chunk.fromHeader(buff, start); return Chunk.fromHeader(buff, start);
...@@ -801,7 +833,7 @@ public class MVStore { ...@@ -801,7 +833,7 @@ public class MVStore {
copyLive(c, old); copyLive(c, old);
} }
save(); store();
return true; return true;
} }
...@@ -871,8 +903,13 @@ public class MVStore { ...@@ -871,8 +903,13 @@ public class MVStore {
Page readPage(MVMap<?, ?> map, long pos) { Page readPage(MVMap<?, ?> map, long pos) {
Page p = cache.get(pos); Page p = cache.get(pos);
if (p == null) { if (p == null) {
long filePos = getFilePosition(pos); Chunk c = getChunk(pos);
readCount++; if (c == null) {
throw new RuntimeException("Chunk " + DataUtils.getPageChunkId(pos) + " not found");
}
long filePos = c.start;
filePos += DataUtils.getPageOffset(pos);
fileReadCount++;
p = Page.read(file, map, pos, filePos, fileSize); p = Page.read(file, map, pos, filePos, fileSize);
cache.put(pos, p); cache.put(pos, p);
} }
...@@ -1038,21 +1075,26 @@ public class MVStore { ...@@ -1038,21 +1075,26 @@ public class MVStore {
unsavedPageCount++; unsavedPageCount++;
} }
/**
* Get the store version. The store version is usually used to upgrade the
* structure of the store after upgrading the application. Initially the
* store version is 0, until it is changed.
*
* @return the store version
*/
public int getStoreVersion() { public int getStoreVersion() {
String x = getSetting("storeVersion"); String x = meta.get("setting.storeVersion");
return x == null ? 0 : Integer.parseInt(x); return x == null ? 0 : Integer.parseInt(x);
} }
/**
* Update the store version. The setting is immediately
* persisted.
*
* @param version the new store version
*/
public void setStoreVersion(int version) { public void setStoreVersion(int version) {
setSetting("storeVersion", Integer.toString(version)); meta.put("setting.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);
} }
/** /**
...@@ -1092,8 +1134,8 @@ public class MVStore { ...@@ -1092,8 +1134,8 @@ public class MVStore {
last = chunks.get(lastChunkId); last = chunks.get(lastChunkId);
} }
rootChunkStart = last.start; rootChunkStart = last.start;
writeHeader(); writeFileHeader();
readHeader(); readFileHeader();
readMeta(); readMeta();
} }
} }
...@@ -1131,21 +1173,41 @@ public class MVStore { ...@@ -1131,21 +1173,41 @@ public class MVStore {
} }
/** /**
* Get the number of write operations since this store was opened. * Get the number of file write operations since this store was opened.
* *
* @return the number of write operations * @return the number of write operations
*/ */
public int getWriteCount() { public int getFileWriteCount() {
return writeCount; return fileWriteCount;
} }
/** /**
* Get the number of read operations since this store was opened. * Get the number of file read operations since this store was opened.
* *
* @return the number of read operations * @return the number of read operations
*/ */
public int getReadCount() { public int getFileReadCount() {
return readCount; return fileReadCount;
}
/**
* Get the file name, or null for in-memory stores.
*
* @return the file name
*/
public String getFileName() {
return fileName;
}
/**
* Get the file header. This data is for informational purposes only. The
* data is subject to change in future versions. The data should not be
* modified (doing so may corrupt the store).
*
* @return the file header
*/
public Map<String, String> getFileHeader() {
return fileHeader;
} }
} }
/*
* 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.btree;
import java.util.HashMap;
import org.h2.util.New;
/**
* A builder for an MVStore.
*/
public class MVStoreBuilder {
private HashMap<String, Object> config = New.hashMap();
/**
* Use the following file name. If the file does not exist, it is
* automatically created.
*
* @param fileName the file name
* @return this
*/
public static MVStoreBuilder fileBased(String fileName) {
return new MVStoreBuilder().fileName(fileName);
}
/**
* Open the store in-memory. In this case, no data may be saved.
*
* @return the store
*/
public static MVStoreBuilder inMemory() {
return new MVStoreBuilder();
}
private MVStoreBuilder set(String key, Object value) {
if (config.containsKey(key)) {
throw new IllegalArgumentException("Parameter " + config.get(key) + " is already set");
}
config.put(key, value);
return this;
}
private MVStoreBuilder fileName(String fileName) {
return set("fileName", fileName);
}
/**
* Open the file in read-only mode. In this case, a shared lock will be
* acquired to ensure the file is not concurrently opened in write mode.
* <p>
* If this option is not used, the file is locked exclusively.
* <p>
* Please note a store may only be opened once in every JVM (no matter
* whether it is opened in read-only or read-write mode), because each file
* may be locked only once in a process.
*
* @return this
*/
public MVStoreBuilder readOnly() {
return set("openMode", "r");
}
public MVStoreBuilder with(MapFactory mapFactory) {
int todoRemove;
return set("mapFactory", mapFactory);
}
/**
* Open the store.
*
* @return the opened store
*/
public MVStore open() {
MVStore s = new MVStore(config);
s.open();
return s;
}
public String toString() {
return DataUtils.appendMap(new StringBuilder(), config).toString();
}
public static MVStoreBuilder fromString(String s) {
HashMap<String, String> config = DataUtils.parseMap(s);
MVStoreBuilder builder = new MVStoreBuilder();
builder.config.putAll(config);
return builder;
}
}
...@@ -17,61 +17,58 @@ import org.h2.store.fs.FilePath; ...@@ -17,61 +17,58 @@ import org.h2.store.fs.FilePath;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
/** /**
* Convert a database file to a human-readable text dump. * Utility methods used in combination with the MVStore.
*/ */
public class Dump { public class MVStoreUtils {
private static int blockSize = 4 * 1024;
/** /**
* Runs this tool. * Runs this tool.
* Options are case sensitive. Supported options are: * Options are case sensitive. Supported options are:
* <table> * <table>
* <tr><td>[-file]</td> * <tr><td>[-dump &lt;dir&gt;]</td>
* <td>The database file name</td></tr> * <td>Dump the contends of the file</td></tr>
* </table> * </table>
* *
* @param args the command line arguments * @param args the command line arguments
*/ */
public static void main(String... args) { public static void main(String... args) throws IOException {
String fileName = "test.h3";
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
if ("-file".equals(args[i])) { if ("-dump".equals(args[i])) {
fileName = args[++i]; String fileName = args[++i];
dump(fileName, new PrintWriter(System.out));
} }
} }
dump(fileName, new PrintWriter(System.out));
} }
/** /**
* Dump the contents of the file. * Read the contents of the file and display them in a human-readable
* format.
* *
* @param fileName the name of the file * @param fileName the name of the file
* @param writer the print writer * @param writer the print writer
*/ */
public static void dump(String fileName, PrintWriter writer) { public static void dump(String fileName, PrintWriter writer) throws IOException {
if (!FileUtils.exists(fileName)) { if (!FileUtils.exists(fileName)) {
writer.println("File not found: " + fileName); writer.println("File not found: " + fileName);
return; return;
} }
FileChannel file = null; FileChannel file = null;
int blockSize = MVStore.BLOCK_SIZE;
try { try {
file = FilePath.get(fileName).open("r"); file = FilePath.get(fileName).open("r");
long fileLength = file.size(); long fileLength = file.size();
file.position(0);
byte[] header = new byte[blockSize]; byte[] header = new byte[blockSize];
file.read(ByteBuffer.wrap(header)); file.read(ByteBuffer.wrap(header), 0);
Properties prop = new Properties(); Properties prop = new Properties();
prop.load(new ByteArrayInputStream(header)); prop.load(new ByteArrayInputStream(header));
prop.load(new StringReader(new String(header, "UTF-8"))); prop.load(new StringReader(new String(header, "UTF-8")));
writer.println("file " + fileName); writer.println("file " + fileName);
writer.println(" length " + fileLength); writer.println(" length " + fileLength);
writer.println(" " + prop); writer.println(" " + prop);
ByteBuffer block = ByteBuffer.wrap(new byte[32]); ByteBuffer block = ByteBuffer.allocate(32);
for (long pos = 0; pos < fileLength;) { for (long pos = 0; pos < fileLength;) {
file.position(pos);
block.rewind(); block.rewind();
FileUtils.readFully(file, block); DataUtils.readFully(file, pos, block);
block.rewind(); block.rewind();
if (block.get() != 'c') { if (block.get() != 'c') {
pos += blockSize; pos += blockSize;
...@@ -89,8 +86,7 @@ public class Dump { ...@@ -89,8 +86,7 @@ public class Dump {
" root " + metaRootPos + " root " + metaRootPos +
" maxLengthLive " + maxLengthLive); " maxLengthLive " + maxLengthLive);
ByteBuffer chunk = ByteBuffer.allocate(chunkLength); ByteBuffer chunk = ByteBuffer.allocate(chunkLength);
file.position(pos); DataUtils.readFully(file, pos, chunk);
FileUtils.readFully(file, chunk);
int p = block.position(); int p = block.position();
pos = (pos + chunkLength + blockSize) / blockSize * blockSize; pos = (pos + chunkLength + blockSize) / blockSize * blockSize;
chunkLength -= p; chunkLength -= p;
...@@ -114,6 +110,7 @@ public class Dump { ...@@ -114,6 +110,7 @@ public class Dump {
} }
} catch (IOException e) { } catch (IOException e) {
writer.println("ERROR: " + e); writer.println("ERROR: " + e);
throw e;
} finally { } finally {
if (file != null) { if (file != null) {
try { try {
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
*/ */
package org.h2.dev.store.btree; package org.h2.dev.store.btree;
import org.h2.compress.Compressor;
/** /**
* A factory for maps and data types. * A factory for maps and data types.
...@@ -37,13 +36,6 @@ public interface MapFactory { ...@@ -37,13 +36,6 @@ public interface MapFactory {
*/ */
DataType buildDataType(String dataType); DataType buildDataType(String dataType);
/**
* Create a new compressor.
*
* @return the compressor
*/
Compressor buildCompressor();
/** /**
* Get the data type object for the given class. * Get the data type object for the given class.
* *
......
...@@ -130,12 +130,12 @@ public class Page { ...@@ -130,12 +130,12 @@ public class Page {
maxLength = (int) Math.min(fileSize - filePos, maxLength); maxLength = (int) Math.min(fileSize - filePos, maxLength);
int length = maxLength; int length = maxLength;
if (maxLength == Integer.MAX_VALUE) { if (maxLength == Integer.MAX_VALUE) {
buff = ByteBuffer.wrap(new byte[128]); buff = ByteBuffer.allocate(128);
DataUtils.readFully(file, filePos, buff); DataUtils.readFully(file, filePos, buff);
maxLength = buff.getInt(); maxLength = buff.getInt();
//read the first bytes again //read the first bytes again
} }
buff = ByteBuffer.wrap(new byte[length]); buff = ByteBuffer.allocate(length);
DataUtils.readFully(file, filePos, buff); DataUtils.readFully(file, filePos, buff);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
...@@ -664,9 +664,9 @@ public class Page { ...@@ -664,9 +664,9 @@ public class Page {
int compLen = pageLength + start - buff.position(); int compLen = pageLength + start - buff.position();
byte[] comp = new byte[compLen]; byte[] comp = new byte[compLen];
buff.get(comp); buff.get(comp);
byte[] exp = new byte[compLen + lenAdd]; int l = compLen + lenAdd;
compressor.expand(comp, 0, compLen, exp, 0, exp.length); buff = ByteBuffer.allocate(l);
buff = ByteBuffer.wrap(exp); compressor.expand(comp, 0, compLen, buff.array(), 0, l);
} }
DataType keyType = map.getKeyType(); DataType keyType = map.getKeyType();
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论