提交 23cd2f75 authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore changes: abstract storage to support off-heap storage; rename getSize to…

MVStore changes: abstract storage to support off-heap storage; rename getSize to sizeAsLong; avoid importing java.beans package to speed up starting the JVM
上级 380b76a5
...@@ -232,12 +232,12 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex { ...@@ -232,12 +232,12 @@ public class SpatialTreeIndex extends BaseIndex implements SpatialIndex {
@Override @Override
public long getRowCount(Session session) { public long getRowCount(Session session) {
return treeMap.getSize(); return treeMap.sizeAsLong();
} }
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
return treeMap.getSize(); return treeMap.sizeAsLong();
} }
@Override @Override
......
...@@ -11,6 +11,7 @@ import java.io.IOException; ...@@ -11,6 +11,7 @@ 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.nio.charset.Charset;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
...@@ -121,6 +122,11 @@ public class DataUtils { ...@@ -121,6 +122,11 @@ public class DataUtils {
* The estimated number of bytes used per child entry. * The estimated number of bytes used per child entry.
*/ */
public static final int PAGE_MEMORY_CHILD = 16; public static final int PAGE_MEMORY_CHILD = 16;
/**
* Name of the character encoding format.
*/
public static final Charset UTF8 = Charset.forName("UTF-8");
/** /**
* An 0-size byte array. * An 0-size byte array.
...@@ -131,7 +137,7 @@ public class DataUtils { ...@@ -131,7 +137,7 @@ public class DataUtils {
* The maximum byte to grow a buffer at a time. * The maximum byte to grow a buffer at a time.
*/ */
private static final int MAX_GROW = 16 * 1024 * 1024; private static final int MAX_GROW = 16 * 1024 * 1024;
/** /**
* Get the length of the variable size int. * Get the length of the variable size int.
* *
...@@ -731,7 +737,7 @@ public class DataUtils { ...@@ -731,7 +737,7 @@ public class DataUtils {
* @return the error code, or 0 if none * @return the error code, or 0 if none
*/ */
public static int getErrorCode(String m) { public static int getErrorCode(String m) {
if (m.endsWith("]")) { if (m != null && m.endsWith("]")) {
int dash = m.lastIndexOf('/'); int dash = m.lastIndexOf('/');
if (dash >= 0) { if (dash >= 0) {
String s = m.substring(dash + 1, m.length() - 1); String s = m.substring(dash + 1, m.length() - 1);
...@@ -797,4 +803,43 @@ public class DataUtils { ...@@ -797,4 +803,43 @@ public class DataUtils {
return temp; return temp;
} }
/**
* Parse a string as a number.
*
* @param x the number
* @param defaultValue if x is null
* @return the parsed value
* @throws IllegalStateException if parsing fails
*/
public static long parseLong(String x, long defaultValue) {
if (x == null) {
return defaultValue;
}
try {
return Long.parseLong(x);
} catch (NumberFormatException e) {
throw newIllegalStateException(ERROR_FILE_CORRUPT,
"Error parsing the value {0} as a long", x, e);
}
}
/**
* Try to parse a string as a number.
*
* @param x the number
* @param defaultValue if x is null
* @param errorValue if parsing fails
* @return the parsed value if parsing is possible
*/
public static long parseLong(String x, long defaultValue, long errorValue) {
if (x == null) {
return defaultValue;
}
try {
return Long.parseLong(x);
} catch (NumberFormatException e) {
return errorValue;
}
}
} }
/*
* Copyright 2004-2013 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.mvstore;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import org.h2.mvstore.cache.FilePathCache;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathCrypt;
import org.h2.store.fs.FilePathNio;
/**
* The storage mechanism of the MVStore.
*/
public class FileStore {
private final String fileName;
private boolean readOnly;
private FileChannel file;
private FileLock fileLock;
private long fileSize;
private long readCount;
private long writeCount;
public FileStore(String fileName, boolean readOnly) {
if (fileName != null && fileName.indexOf(':') < 0) {
// NIO is used, unless a different file system is specified
// the following line is to ensure the NIO file system is compiled
FilePathNio.class.getName();
fileName = "nio:" + fileName;
}
this.fileName = fileName;
this.readOnly = readOnly;
}
@Override
public String toString() {
return fileName;
}
public void readFully(long pos, ByteBuffer dst) {
readCount++;
DataUtils.readFully(file, pos, dst);
}
public void writeFully(long pos, ByteBuffer src) {
writeCount++;
fileSize = Math.max(fileSize, pos + src.remaining());
DataUtils.writeFully(file, pos, src);
}
/**
* Mark the space within the file as unused.
*
* @param pos
* @param length
*/
public void free(long pos, int length) {
}
public void open(char[] encryptionKey) {
FilePath f = FilePath.get(fileName);
FilePath parent = f.getParent();
if (!parent.exists()) {
throw DataUtils.newIllegalArgumentException("Directory does not exist: {0}", parent);
}
if (f.exists() && !f.canWrite()) {
readOnly = true;
}
try {
file = f.open(readOnly ? "r" : "rw");
if (encryptionKey != null) {
byte[] password = FilePathCrypt.getPasswordBytes(encryptionKey);
file = new FilePathCrypt.FileCrypt(fileName, password, file);
}
file = FilePathCache.wrap(file);
fileSize = file.size();
try {
if (readOnly) {
fileLock = file.tryLock(0, Long.MAX_VALUE, true);
} else {
fileLock = file.tryLock();
}
} catch (OverlappingFileLockException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_LOCKED, "The file is locked: {0}", fileName, e);
}
if (fileLock == null) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_LOCKED, "The file is locked: {0}", fileName);
}
} catch (IOException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_READING_FAILED,
"Could not open file {0}", fileName, e);
}
}
public void close() {
try {
if (fileLock != null) {
fileLock.release();
fileLock = null;
}
file.close();
} catch (Exception e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Closing failed for file {0}", fileName, e);
} finally {
file = null;
}
}
public void sync() {
try {
file.force(true);
} catch (IOException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Could not sync file {0}", fileName, e);
}
}
public long size() {
return fileSize;
}
public void truncate(long size) {
try {
file.truncate(size);
fileSize = Math.min(fileSize, size);
} catch (IOException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Could not truncate file {0} to size {1}",
fileName, size, e);
}
}
/**
* Get the file instance in use. The application may read from the file (for
* example for online backup), but not write to it or truncate it.
*
* @return the file
*/
public FileChannel getFile() {
return file;
}
/**
* Get the number of write operations since this store was opened.
* For file based stores, this is the number of file write operations.
*
* @return the number of write operations
*/
public long getWriteCount() {
return writeCount;
}
/**
* Get the number of read operations since this store was opened.
* For file based stores, this is the number of file read operations.
*
* @return the number of read operations
*/
public long getReadCount() {
return readCount;
}
public boolean isReadOnly() {
return readOnly;
}
}
...@@ -982,11 +982,27 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -982,11 +982,27 @@ public class MVMap<K, V> extends AbstractMap<K, V>
return this == o; return this == o;
} }
/**
* Get the number of entries, as a integer. Integer.MAX_VALUE is returned if
* there are more than this entries.
*
* @return the number of entries, as an integer
*/
@Override @Override
public int size() { public int size() {
long size = getSize(); long size = sizeAsLong();
return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size; return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size;
} }
/**
* Get the number of entries, as a long.
*
* @return the number of entries
*/
public long sizeAsLong() {
checkOpen();
return root.getTotalCount();
}
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
...@@ -994,11 +1010,6 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -994,11 +1010,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
return 0 == (root.isLeaf() ? root.getKeyCount() : root.getChildPageCount()); return 0 == (root.isLeaf() ? root.getKeyCount() : root.getChildPageCount());
} }
public long getSize() {
checkOpen();
return root.getTotalCount();
}
public long getCreateVersion() { public long getCreateVersion() {
return createVersion; return createVersion;
} }
...@@ -1033,7 +1044,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1033,7 +1044,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
(version == writeVersion || (version == writeVersion ||
r.getVersion() >= 0 || r.getVersion() >= 0 ||
version <= createVersion || version <= createVersion ||
store.getFile() == null)) { store.getFileStore() == null)) {
newest = r; newest = r;
} else { } else {
// find the newest page that has a getVersion() <= version // find the newest page that has a getVersion() <= version
......
...@@ -6,12 +6,8 @@ ...@@ -6,12 +6,8 @@
*/ */
package org.h2.mvstore; package org.h2.mvstore;
import java.beans.ExceptionListener; import java.lang.Thread.UncaughtExceptionHandler;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -23,13 +19,8 @@ import java.util.Set; ...@@ -23,13 +19,8 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import org.h2.compress.CompressLZF; import org.h2.compress.CompressLZF;
import org.h2.compress.Compressor; import org.h2.compress.Compressor;
import org.h2.engine.Constants;
import org.h2.mvstore.cache.CacheLongKeyLIRS; import org.h2.mvstore.cache.CacheLongKeyLIRS;
import org.h2.mvstore.cache.FilePathCache;
import org.h2.mvstore.type.StringDataType; import org.h2.mvstore.type.StringDataType;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathCrypt;
import org.h2.store.fs.FilePathNio;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.New; import org.h2.util.New;
...@@ -67,9 +58,6 @@ MVStore: ...@@ -67,9 +58,6 @@ MVStore:
- defragment (re-creating maps, specially those with small pages) - defragment (re-creating maps, specially those with small pages)
- chunk header: store changed chunk data as row; maybe after the root - chunk header: store changed chunk data as row; maybe after the root
- chunk checksum (header, last page, 2 bytes per page?) - chunk checksum (header, last page, 2 bytes per page?)
- is there a better name for the file header,
if it's no longer always at the beginning of a file? store header?
root pointer?
- on insert, if the child page is already full, don't load and modify it - on insert, if the child page is already full, don't load and modify it
split directly (specially for leaves with one large entry) split directly (specially for leaves with one large entry)
- maybe let a chunk point to a list of potential next chunks - maybe let a chunk point to a list of potential next chunks
...@@ -127,6 +115,7 @@ MVStore: ...@@ -127,6 +115,7 @@ MVStore:
with free space handling, retention time / flush, with free space handling, retention time / flush,
possibly one file per chunk to support SSD trim on file system level, possibly one file per chunk to support SSD trim on file system level,
and free up memory for off-heap storage) and free up memory for off-heap storage)
- Test with OSGi
*/ */
...@@ -141,29 +130,33 @@ public class MVStore { ...@@ -141,29 +130,33 @@ public class MVStore {
public static final boolean ASSERT = false; public static final boolean ASSERT = false;
/** /**
* The block size (physical sector size) of the disk. The file header is * The block size (physical sector size) of the disk. The store header is
* written twice, one copy in each block, to ensure it survives a crash. * written twice, one copy in each block, to ensure it survives a crash.
*/ */
static final int BLOCK_SIZE = 4 * 1024; static final int BLOCK_SIZE = 4 * 1024;
private static final int FORMAT_WRITE = 1; private static final int FORMAT_WRITE = 1;
private static final int FORMAT_READ = 1; private static final int FORMAT_READ = 1;
/** /**
* The background thread, if any. * The background thread, if any.
*/ */
volatile Thread backgroundThread; volatile Thread backgroundThread;
/**
* The free spaces between the chunks. The first block to use is block 2
* (the first two blocks are the store header).
*/
private final FreeSpaceBitSet freeSpace = new FreeSpaceBitSet(2, BLOCK_SIZE);
private volatile boolean reuseSpace = true;
private boolean closed; private boolean closed;
private final String fileName; private FileStore fileStore;
private final char[] filePassword;
private final int pageSplitSize; private final int pageSplitSize;
private FileChannel file;
private FileLock fileLock;
private long fileSize;
private long rootChunkStart; private long rootChunkStart;
/** /**
...@@ -181,12 +174,6 @@ public class MVStore { ...@@ -181,12 +174,6 @@ public class MVStore {
private final ConcurrentHashMap<Integer, Chunk> chunks = private final ConcurrentHashMap<Integer, Chunk> chunks =
new ConcurrentHashMap<Integer, Chunk>(); new ConcurrentHashMap<Integer, Chunk>();
/**
* The free spaces between the chunks. The first block to use is block 2
* (the first two blocks are the file header).
*/
private FreeSpaceBitSet freeSpace = new FreeSpaceBitSet(2, BLOCK_SIZE);
/** /**
* The map of temporarily removed pages. The key is the unsaved version, the * The map of temporarily removed pages. The key is the unsaved version, the
* value is the map of chunks. The maps of chunks contains the number of * value is the map of chunks. The maps of chunks contains the number of
...@@ -199,22 +186,23 @@ public class MVStore { ...@@ -199,22 +186,23 @@ public class MVStore {
private final ConcurrentHashMap<Integer, MVMap<?, ?>> maps = private final ConcurrentHashMap<Integer, MVMap<?, ?>> maps =
new ConcurrentHashMap<Integer, MVMap<?, ?>>(); new ConcurrentHashMap<Integer, MVMap<?, ?>>();
private HashMap<String, String> fileHeader = New.hashMap(); private HashMap<String, String> storeHeader = New.hashMap();
private ByteBuffer writeBuffer; private ByteBuffer writeBuffer;
private boolean readOnly;
private int lastMapId; private int lastMapId;
private volatile boolean reuseSpace = true;
private long retainVersion = -1; private long retainVersion = -1;
private final Compressor compressor = new CompressLZF(); /**
* Whether to compress new pages. Even if disabled, the store may contain
* (old) compressed pages.
*/
private final boolean compress; private final boolean compress;
private final Compressor compressor = new CompressLZF();
private final ExceptionListener backgroundExceptionListener; private final UncaughtExceptionHandler backgroundExceptionHandler;
private long currentVersion; private long currentVersion;
...@@ -222,8 +210,7 @@ public class MVStore { ...@@ -222,8 +210,7 @@ public class MVStore {
* The version of the last stored chunk. * The version of the last stored chunk.
*/ */
private long lastStoredVersion; private long lastStoredVersion;
private int fileReadCount;
private int fileWriteCount;
private int unsavedPageCount; private int unsavedPageCount;
private int unsavedPageCountMax; private int unsavedPageCountMax;
...@@ -257,40 +244,99 @@ public class MVStore { ...@@ -257,40 +244,99 @@ public class MVStore {
*/ */
private int writeDelay; private int writeDelay;
/**
* Create and open the store.
*
* @throws IllegalStateException if the file is corrupt, or an exception
* occurred while opening
* @throws IllegalArgumentException if the directory does not exist
*/
MVStore(HashMap<String, Object> config) { MVStore(HashMap<String, Object> config) {
String f = (String) config.get("fileName");
if (f != null && f.indexOf(':') < 0) {
// NIO is used, unless a different file system is specified
// the following line is to ensure the NIO file system is compiled
FilePathNio.class.getName();
f = "nio:" + f;
}
this.fileName = f;
this.readOnly = config.containsKey("readOnly");
this.compress = config.containsKey("compress"); this.compress = config.containsKey("compress");
Object o = config.get("pageSplitSize"); Object o = config.get("pageSplitSize");
pageSplitSize = o == null ? 6 * 1024 : (Integer) o; pageSplitSize = o == null ? 6 * 1024 : (Integer) o;
o = config.get("backgroundExceptionListener"); o = config.get("backgroundExceptionHandler");
this.backgroundExceptionListener = (ExceptionListener) o; this.backgroundExceptionHandler = (UncaughtExceptionHandler) o;
if (fileName != null) { meta = new MVMapConcurrent<String, String>(StringDataType.INSTANCE, StringDataType.INSTANCE);
o = config.get("cacheSize"); HashMap<String, String> c = New.hashMap();
int mb = o == null ? 16 : (Integer) o; c.put("id", "0");
int maxMemoryBytes = mb * 1024 * 1024; c.put("createVersion", Long.toString(currentVersion));
int averageMemory = pageSplitSize / 2; meta.init(this, c);
int segmentCount = 16; String f = (String) config.get("fileName");
int stackMoveDistance = maxMemoryBytes / averageMemory * 2 / 100; if (f == null) {
cache = new CacheLongKeyLIRS<Page>(
maxMemoryBytes, averageMemory, segmentCount, stackMoveDistance);
filePassword = (char[]) config.get("encrypt");
o = config.get("writeBufferSize");
mb = o == null ? 4 : (Integer) o;
int writeBufferSize = mb * 1024 * 1024;
int div = pageSplitSize;
unsavedPageCountMax = writeBufferSize / (div == 0 ? 1 : div);
} else {
cache = null; cache = null;
filePassword = null; return;
} }
boolean readOnly = config.containsKey("readOnly");
fileStore = new FileStore(f, readOnly);
o = config.get("cacheSize");
int mb = o == null ? 16 : (Integer) o;
int maxMemoryBytes = mb * 1024 * 1024;
int averageMemory = pageSplitSize / 2;
int segmentCount = 16;
int stackMoveDistance = maxMemoryBytes / averageMemory * 2 / 100;
cache = new CacheLongKeyLIRS<Page>(
maxMemoryBytes, averageMemory, segmentCount, stackMoveDistance);
o = config.get("writeBufferSize");
mb = o == null ? 4 : (Integer) o;
int writeBufferSize = mb * 1024 * 1024;
int div = pageSplitSize;
unsavedPageCountMax = writeBufferSize / (div == 0 ? 1 : div);
char[] encryptionKey = (char[]) config.get("encryptionKey");
try {
fileStore.open(encryptionKey);
if (fileStore.size() == 0) {
creationTime = 0;
creationTime = getTime();
lastStoreTime = creationTime;
storeHeader.put("H", "3");
storeHeader.put("blockSize", "" + BLOCK_SIZE);
storeHeader.put("format", "" + FORMAT_WRITE);
storeHeader.put("creationTime", "" + creationTime);
writeStoreHeader();
} else {
readStoreHeader();
long format = DataUtils.parseLong(storeHeader.get("format"), 0);
if (format > FORMAT_WRITE && !fileStore.isReadOnly()) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_UNSUPPORTED_FORMAT,
"The write format {0} is larger than the supported format {1}, " +
"and the file was not opened in read-only mode",
format, FORMAT_WRITE);
}
format = DataUtils.parseLong(storeHeader.get("formatRead"), format);
if (format > FORMAT_READ) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_UNSUPPORTED_FORMAT,
"The read format {0} is larger than the supported format {1}",
format, FORMAT_READ);
}
if (rootChunkStart > 0) {
readMeta();
}
}
long rollback = DataUtils.parseLong(meta.get("rollbackOnOpen"), -1);
if (rollback != -1) {
rollbackTo(rollback);
}
} catch (IllegalStateException e) {
try {
closeStore(false);
} catch (Exception e2) {
// ignore
}
throw e;
} finally {
if (encryptionKey != null) {
Arrays.fill(encryptionKey, (char) 0);
}
}
lastStoreTime = getTime();
this.lastCommittedVersion = currentVersion;
// setWriteDelay starts the thread, but only if
// the parameter is different than the current value
setWriteDelay(1000);
} }
/** /**
...@@ -303,9 +349,7 @@ public class MVStore { ...@@ -303,9 +349,7 @@ public class MVStore {
public static MVStore open(String fileName) { public static MVStore open(String fileName) {
HashMap<String, Object> config = New.hashMap(); HashMap<String, Object> config = New.hashMap();
config.put("fileName", fileName); config.put("fileName", fileName);
MVStore s = new MVStore(config); return new MVStore(config);
s.open();
return s;
} }
/** /**
...@@ -320,7 +364,7 @@ public class MVStore { ...@@ -320,7 +364,7 @@ public class MVStore {
<T extends MVMap<?, ?>> T openMapVersion(long version, int mapId, MVMap<?, ?> template) { <T extends MVMap<?, ?>> T openMapVersion(long version, int mapId, MVMap<?, ?> template) {
MVMap<String, String> oldMeta = getMetaMap(version); MVMap<String, String> oldMeta = getMetaMap(version);
String r = oldMeta.get("root." + mapId); String r = oldMeta.get("root." + mapId);
long rootPos = r == null ? 0 : Long.parseLong(r); long rootPos = DataUtils.parseLong(r, 0);
MVMap<?, ?> m = template.openReadOnly(); MVMap<?, ?> m = template.openReadOnly();
m.setRootPos(rootPos, version); m.setRootPos(rootPos, version);
return (T) m; return (T) m;
...@@ -461,132 +505,6 @@ public class MVStore { ...@@ -461,132 +505,6 @@ public class MVStore {
markChanged(meta); markChanged(meta);
} }
/**
* Open the store.
*
* @throws IllegalStateException if the file is corrupt, or an exception
* occurred while opening
* @throws IllegalArgumentException if the directory does not exist
*/
void open() {
meta = new MVMapConcurrent<String, String>(StringDataType.INSTANCE, StringDataType.INSTANCE);
HashMap<String, String> c = New.hashMap();
c.put("id", "0");
c.put("createVersion", Long.toString(currentVersion));
meta.init(this, c);
if (fileName == null) {
return;
}
FilePath parent = FilePath.get(fileName).getParent();
if (!parent.exists()) {
throw DataUtils.newIllegalArgumentException("Directory does not exist: {0}", parent);
}
try {
if (readOnly) {
openFile(true);
} else if (!openFile(false)) {
readOnly = true;
openFile(true);
}
} finally {
if (filePassword != null) {
Arrays.fill(filePassword, (char) 0);
}
}
lastStoreTime = getTime();
String r = meta.get("rollbackOnOpen");
if (r != null) {
long rollback = Long.parseLong(r);
rollbackTo(rollback);
}
this.lastCommittedVersion = currentVersion;
// setWriteDelay starts the thread, but only if
// the parameter is different than the current value
setWriteDelay(1000);
}
/**
* Try to open the file in read or write mode.
*
* @return if opening the file was successful, and false if the file could
* not be opened in write mode because the write file format is too
* high (in which case the file can be opened in read-only mode)
* @throw IllegalStateException if the file could not be opened
* because of an IOException or file format error
*/
private boolean openFile(boolean readOnly) {
IllegalStateException exception;
try {
FilePath f = FilePath.get(fileName);
if (f.exists() && !f.canWrite()) {
return false;
}
file = f.open(readOnly ? "r" : "rw");
if (filePassword != null) {
byte[] password = FilePathCrypt.getPasswordBytes(filePassword);
file = new FilePathCrypt.FileCrypt(fileName, password, file);
}
file = FilePathCache.wrap(file);
try {
if (readOnly) {
fileLock = file.tryLock(0, Long.MAX_VALUE, true);
} else {
fileLock = file.tryLock();
}
} catch (OverlappingFileLockException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_LOCKED, "The file is locked: {0}", fileName, e);
}
if (fileLock == null) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_LOCKED, "The file is locked: {0}", fileName);
}
fileSize = file.size();
if (fileSize == 0) {
creationTime = 0;
creationTime = getTime();
lastStoreTime = creationTime;
fileHeader.put("H", "3");
fileHeader.put("blockSize", "" + BLOCK_SIZE);
fileHeader.put("format", "" + FORMAT_WRITE);
fileHeader.put("creationTime", "" + creationTime);
writeFileHeader();
} else {
readFileHeader();
int formatWrite = Integer.parseInt(fileHeader.get("format"));
String x = fileHeader.get("formatRead");
int formatRead = x == null ? formatWrite : Integer.parseInt(x);
if (formatRead > FORMAT_READ) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_UNSUPPORTED_FORMAT,
"The file format {0} is larger than the supported format {1}",
formatRead, FORMAT_READ);
}
if (formatWrite > FORMAT_WRITE && !readOnly) {
file.close();
return false;
}
if (rootChunkStart > 0) {
readMeta();
}
}
return true;
} catch (IOException e) {
exception = DataUtils.newIllegalStateException(
DataUtils.ERROR_READING_FAILED,
"Could not open file {0}", fileName, e);
} catch (IllegalStateException e) {
exception = e;
}
try {
closeFile(false);
} catch (Exception e2) {
// ignore
}
throw exception;
}
private void readMeta() { private void readMeta() {
Chunk header = readChunkHeader(rootChunkStart); Chunk header = readChunkHeader(rootChunkStart);
lastChunkId = header.id; lastChunkId = header.id;
...@@ -630,7 +548,7 @@ public class MVStore { ...@@ -630,7 +548,7 @@ public class MVStore {
} }
} }
private void readFileHeader() { private void readStoreHeader() {
// we don't have a valid header yet // we don't have a valid header yet
currentVersion = -1; currentVersion = -1;
// we don't know which chunk is the newest // we don't know which chunk is the newest
...@@ -638,14 +556,12 @@ public class MVStore { ...@@ -638,14 +556,12 @@ public class MVStore {
// read the last block of the file, and then the two first blocks // read the last block of the file, and then the two first blocks
ByteBuffer buff = ByteBuffer.allocate(3 * BLOCK_SIZE); ByteBuffer buff = ByteBuffer.allocate(3 * BLOCK_SIZE);
buff.limit(BLOCK_SIZE); buff.limit(BLOCK_SIZE);
fileReadCount++; fileStore.readFully(fileStore.size() - BLOCK_SIZE, buff);
DataUtils.readFully(file, fileSize - BLOCK_SIZE, buff);
buff.limit(3 * BLOCK_SIZE); buff.limit(3 * BLOCK_SIZE);
buff.position(BLOCK_SIZE); buff.position(BLOCK_SIZE);
fileReadCount++; fileStore.readFully(0, buff);
DataUtils.readFully(file, 0, buff);
for (int i = 0; i < 3 * BLOCK_SIZE; i += BLOCK_SIZE) { for (int i = 0; i < 3 * BLOCK_SIZE; i += BLOCK_SIZE) {
String s = new String(buff.array(), i, BLOCK_SIZE, Constants.UTF8) String s = new String(buff.array(), i, BLOCK_SIZE, DataUtils.UTF8)
.trim(); .trim();
HashMap<String, String> m; HashMap<String, String> m;
try { try {
...@@ -664,7 +580,7 @@ public class MVStore { ...@@ -664,7 +580,7 @@ public class MVStore {
check = -1; check = -1;
} }
s = s.substring(0, s.lastIndexOf("fletcher") - 1); s = s.substring(0, s.lastIndexOf("fletcher") - 1);
byte[] bytes = s.getBytes(Constants.UTF8); byte[] bytes = s.getBytes(DataUtils.UTF8);
int checksum = DataUtils.getFletcher32(bytes, bytes.length / 2 * 2); int checksum = DataUtils.getFletcher32(bytes, bytes.length / 2 * 2);
if (check != checksum) { if (check != checksum) {
continue; continue;
...@@ -672,7 +588,7 @@ public class MVStore { ...@@ -672,7 +588,7 @@ public class MVStore {
long chunk = Long.parseLong(m.get("chunk")); long chunk = Long.parseLong(m.get("chunk"));
if (chunk > newestChunk) { if (chunk > newestChunk) {
newestChunk = chunk; newestChunk = chunk;
fileHeader = m; storeHeader = m;
rootChunkStart = Long.parseLong(m.get("rootChunk")); rootChunkStart = Long.parseLong(m.get("rootChunk"));
creationTime = Long.parseLong(m.get("creationTime")); creationTime = Long.parseLong(m.get("creationTime"));
lastMapId = Integer.parseInt(m.get("lastMapId")); lastMapId = Integer.parseInt(m.get("lastMapId"));
...@@ -681,41 +597,39 @@ public class MVStore { ...@@ -681,41 +597,39 @@ public class MVStore {
} }
if (currentVersion < 0) { if (currentVersion < 0) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT, "File header is corrupt: {0}", fileName); DataUtils.ERROR_FILE_CORRUPT, "Store header is corrupt: {0}", fileStore);
} }
setWriteVersion(currentVersion); setWriteVersion(currentVersion);
lastStoredVersion = -1; lastStoredVersion = -1;
} }
private byte[] getFileHeaderBytes() { private byte[] getStoreHeaderBytes() {
StringBuilder buff = new StringBuilder(); StringBuilder buff = new StringBuilder();
fileHeader.put("lastMapId", "" + lastMapId); storeHeader.put("lastMapId", "" + lastMapId);
fileHeader.put("chunk", "" + lastChunkId); storeHeader.put("chunk", "" + lastChunkId);
fileHeader.put("rootChunk", "" + rootChunkStart); storeHeader.put("rootChunk", "" + rootChunkStart);
fileHeader.put("version", "" + currentVersion); storeHeader.put("version", "" + currentVersion);
DataUtils.appendMap(buff, fileHeader); DataUtils.appendMap(buff, storeHeader);
byte[] bytes = buff.toString().getBytes(Constants.UTF8); byte[] bytes = buff.toString().getBytes(DataUtils.UTF8);
int checksum = DataUtils.getFletcher32(bytes, bytes.length / 2 * 2); int checksum = DataUtils.getFletcher32(bytes, bytes.length / 2 * 2);
DataUtils.appendMap(buff, "fletcher", Integer.toHexString(checksum)); DataUtils.appendMap(buff, "fletcher", Integer.toHexString(checksum));
bytes = buff.toString().getBytes(Constants.UTF8); bytes = buff.toString().getBytes(DataUtils.UTF8);
if (bytes.length > BLOCK_SIZE) { if (bytes.length > BLOCK_SIZE) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_UNSUPPORTED_FORMAT, DataUtils.ERROR_UNSUPPORTED_FORMAT,
"File header too large: {0}", buff); "Store header too large: {0}", buff);
} }
return bytes; return bytes;
} }
private void writeFileHeader() { private void writeStoreHeader() {
byte[] bytes = getFileHeaderBytes(); byte[] bytes = getStoreHeaderBytes();
ByteBuffer header = ByteBuffer.allocate(2 * BLOCK_SIZE); ByteBuffer header = ByteBuffer.allocate(2 * BLOCK_SIZE);
header.put(bytes); header.put(bytes);
header.position(BLOCK_SIZE); header.position(BLOCK_SIZE);
header.put(bytes); header.put(bytes);
header.rewind(); header.rewind();
fileWriteCount++; fileStore.writeFully(0, header);
DataUtils.writeFully(file, 0, header);
fileSize = Math.max(fileSize, 2 * BLOCK_SIZE);
} }
/** /**
...@@ -727,7 +641,7 @@ public class MVStore { ...@@ -727,7 +641,7 @@ public class MVStore {
if (closed) { if (closed) {
return; return;
} }
if (!readOnly) { if (fileStore != null && !fileStore.isReadOnly()) {
stopBackgroundThread(); stopBackgroundThread();
if (hasUnsavedChanges() || lastCommittedVersion != currentVersion) { if (hasUnsavedChanges() || lastCommittedVersion != currentVersion) {
rollbackTo(lastCommittedVersion); rollbackTo(lastCommittedVersion);
...@@ -735,7 +649,7 @@ public class MVStore { ...@@ -735,7 +649,7 @@ public class MVStore {
store(false); store(false);
} }
} }
closeFile(true); closeStore(true);
} }
/** /**
...@@ -744,15 +658,15 @@ public class MVStore { ...@@ -744,15 +658,15 @@ public class MVStore {
*/ */
public void closeImmediately() { public void closeImmediately() {
try { try {
closeFile(false); closeStore(false);
} catch (Exception e) { } catch (Exception e) {
if (backgroundExceptionListener != null) { if (backgroundExceptionHandler != null) {
backgroundExceptionListener.exceptionThrown(e); backgroundExceptionHandler.uncaughtException(null, e);
} }
} }
} }
private void closeFile(boolean shrinkIfPossible) { private void closeStore(boolean shrinkIfPossible) {
if (closed) { if (closed) {
return; return;
} }
...@@ -761,33 +675,25 @@ public class MVStore { ...@@ -761,33 +675,25 @@ public class MVStore {
// could result in a deadlock // could result in a deadlock
stopBackgroundThread(); stopBackgroundThread();
closed = true; closed = true;
if (file == null) { if (fileStore == null) {
return; return;
} }
synchronized (this) { synchronized (this) {
if (shrinkIfPossible) {
shrinkFileIfPossible(0);
}
for (MVMap<?, ?> m : New.arrayList(maps.values())) {
m.close();
}
meta = null;
chunks.clear();
freeSpace.clear();
cache.clear();
maps.clear();
try { try {
if (shrinkIfPossible) { fileStore.close();
shrinkFileIfPossible(0);
}
if (fileLock != null) {
fileLock.release();
fileLock = null;
}
file.close();
for (MVMap<?, ?> m : New.arrayList(maps.values())) {
m.close();
}
meta = null;
chunks.clear();
freeSpace.clear();
cache.clear();
maps.clear();
} catch (Exception e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Closing failed for file {0}", fileName, e);
} finally { } finally {
file = null; fileStore = null;
} }
} }
} }
...@@ -865,6 +771,9 @@ public class MVStore { ...@@ -865,6 +771,9 @@ public class MVStore {
if (closed) { if (closed) {
return currentVersion; return currentVersion;
} }
if (fileStore == null) {
return incrementVersion();
}
if (currentStoreVersion >= 0) { if (currentStoreVersion >= 0) {
// store is possibly called within store, if the meta map changed // store is possibly called within store, if the meta map changed
return currentVersion; return currentVersion;
...@@ -872,15 +781,12 @@ public class MVStore { ...@@ -872,15 +781,12 @@ public class MVStore {
if (!hasUnsavedChanges()) { if (!hasUnsavedChanges()) {
return currentVersion; return currentVersion;
} }
if (readOnly) { if (fileStore.isReadOnly()) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED, "This store is read-only"); DataUtils.ERROR_WRITING_FAILED, "This store is read-only");
} }
int currentUnsavedPageCount = unsavedPageCount; int currentUnsavedPageCount = unsavedPageCount;
currentStoreVersion = currentVersion; currentStoreVersion = currentVersion;
if (file == null) {
return incrementVersion();
}
long storeVersion = currentStoreVersion; long storeVersion = currentStoreVersion;
long version = ++currentVersion; long version = ++currentVersion;
long time = getTime(); long time = getTime();
...@@ -968,22 +874,22 @@ public class MVStore { ...@@ -968,22 +874,22 @@ public class MVStore {
int chunkLength = buff.position(); int chunkLength = buff.position();
// round to the next block, // round to the next block,
// and one additional block for the file header // and one additional block for the store header
int length = MathUtils.roundUpInt(chunkLength, BLOCK_SIZE) + BLOCK_SIZE; int length = MathUtils.roundUpInt(chunkLength, BLOCK_SIZE) + BLOCK_SIZE;
if (length > buff.capacity()) { if (length > buff.capacity()) {
buff = DataUtils.ensureCapacity(buff, length - buff.capacity()); buff = DataUtils.ensureCapacity(buff, length - buff.capacity());
} }
buff.limit(length); buff.limit(length);
long fileSizeUsed = getFileSizeUsed(); long end = getEndPosition();
long filePos; long filePos;
if (reuseSpace) { if (reuseSpace) {
filePos = freeSpace.allocate(length); filePos = freeSpace.allocate(length);
} else { } else {
filePos = fileSizeUsed; filePos = end;
freeSpace.markUsed(fileSizeUsed, length); freeSpace.markUsed(end, length);
} }
boolean storeAtEndOfFile = filePos + length >= fileSizeUsed; boolean storeAtEndOfFile = filePos + length >= end;
// free up the space of unused chunks now // free up the space of unused chunks now
for (Chunk x : removedChunks) { for (Chunk x : removedChunks) {
...@@ -1000,21 +906,19 @@ public class MVStore { ...@@ -1000,21 +906,19 @@ public class MVStore {
revertTemp(storeVersion); revertTemp(storeVersion);
buff.position(buff.limit() - BLOCK_SIZE); buff.position(buff.limit() - BLOCK_SIZE);
byte[] header = getFileHeaderBytes(); byte[] header = getStoreHeaderBytes();
buff.put(header); buff.put(header);
// fill the header with zeroes // fill the header with zeroes
buff.put(new byte[BLOCK_SIZE - header.length]); buff.put(new byte[BLOCK_SIZE - header.length]);
buff.position(0); buff.position(0);
fileWriteCount++; fileStore.writeFully(filePos, buff);
DataUtils.writeFully(file, filePos, buff);
fileSize = Math.max(fileSize, filePos + buff.position());
releaseWriteBuffer(buff); releaseWriteBuffer(buff);
// overwrite the header if required // overwrite the header if required
if (!storeAtEndOfFile) { if (!storeAtEndOfFile) {
writeFileHeader(); writeStoreHeader();
shrinkFileIfPossible(1); shrinkFileIfPossible(1);
} }
...@@ -1154,29 +1058,27 @@ public class MVStore { ...@@ -1154,29 +1058,27 @@ public class MVStore {
* @param minPercent the minimum percentage to save * @param minPercent the minimum percentage to save
*/ */
private void shrinkFileIfPossible(int minPercent) { private void shrinkFileIfPossible(int minPercent) {
long used = getFileSizeUsed(); long end = getEndPosition();
if (used >= fileSize) { long fileSize = fileStore.size();
if (end >= fileSize) {
return; return;
} }
if (minPercent > 0 && fileSize - used < BLOCK_SIZE) { if (minPercent > 0 && fileSize - end < BLOCK_SIZE) {
return; return;
} }
int savedPercent = (int) (100 - (used * 100 / fileSize)); int savedPercent = (int) (100 - (end * 100 / fileSize));
if (savedPercent < minPercent) { if (savedPercent < minPercent) {
return; return;
} }
try { fileStore.truncate(end);
file.truncate(used);
} catch (IOException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Could not truncate file {0} to size {1}",
fileName, used, e);
}
fileSize = used;
} }
private long getFileSizeUsed() { /**
* Get the position of the last used byte.
*
* @return the position
*/
private long getEndPosition() {
long size = 2 * BLOCK_SIZE; long size = 2 * BLOCK_SIZE;
for (Chunk c : chunks.values()) { for (Chunk c : chunks.values()) {
if (c.start == Long.MAX_VALUE) { if (c.start == Long.MAX_VALUE) {
...@@ -1210,9 +1112,8 @@ public class MVStore { ...@@ -1210,9 +1112,8 @@ public class MVStore {
} }
private Chunk readChunkHeader(long start) { private Chunk readChunkHeader(long start) {
fileReadCount++;
ByteBuffer buff = ByteBuffer.allocate(40); ByteBuffer buff = ByteBuffer.allocate(40);
DataUtils.readFully(file, start, buff); fileStore.readFully(start, buff);
buff.rewind(); buff.rewind();
return Chunk.fromHeader(buff, start); return Chunk.fromHeader(buff, start);
} }
...@@ -1263,22 +1164,20 @@ public class MVStore { ...@@ -1263,22 +1164,20 @@ public class MVStore {
int length = MathUtils.roundUpInt(c.length, BLOCK_SIZE) + BLOCK_SIZE; int length = MathUtils.roundUpInt(c.length, BLOCK_SIZE) + BLOCK_SIZE;
buff = DataUtils.ensureCapacity(buff, length); buff = DataUtils.ensureCapacity(buff, length);
buff.limit(length); buff.limit(length);
DataUtils.readFully(file, c.start, buff); fileStore.readFully(c.start, buff);
long pos = getFileSizeUsed(); long end = getEndPosition();
freeSpace.markUsed(pos, length); freeSpace.markUsed(end, length);
freeSpace.free(c.start, length); freeSpace.free(c.start, length);
c.start = pos; c.start = end;
buff.position(0); buff.position(0);
c.writeHeader(buff); c.writeHeader(buff);
buff.position(buff.limit() - BLOCK_SIZE); buff.position(buff.limit() - BLOCK_SIZE);
byte[] header = getFileHeaderBytes(); byte[] header = getStoreHeaderBytes();
buff.put(header); buff.put(header);
// fill the header with zeroes // fill the header with zeroes
buff.put(new byte[BLOCK_SIZE - header.length]); buff.put(new byte[BLOCK_SIZE - header.length]);
buff.position(0); buff.position(0);
fileWriteCount++; fileStore.writeFully(end, buff);
DataUtils.writeFully(file, pos, buff);
fileSize = Math.max(fileSize, pos + buff.position());
releaseWriteBuffer(buff); releaseWriteBuffer(buff);
meta.put("chunk." + c.id, c.asString()); meta.put("chunk." + c.id, c.asString());
} }
...@@ -1288,7 +1187,7 @@ public class MVStore { ...@@ -1288,7 +1187,7 @@ public class MVStore {
reuseSpace = false; reuseSpace = false;
store(); store();
syncFile(); sync();
// now re-use the empty space // now re-use the empty space
reuseSpace = true; reuseSpace = true;
...@@ -1297,28 +1196,26 @@ public class MVStore { ...@@ -1297,28 +1196,26 @@ public class MVStore {
int length = MathUtils.roundUpInt(c.length, BLOCK_SIZE) + BLOCK_SIZE; int length = MathUtils.roundUpInt(c.length, BLOCK_SIZE) + BLOCK_SIZE;
buff = DataUtils.ensureCapacity(buff, length); buff = DataUtils.ensureCapacity(buff, length);
buff.limit(length); buff.limit(length);
DataUtils.readFully(file, c.start, buff); fileStore.readFully(c.start, buff);
long pos = freeSpace.allocate(length); long pos = freeSpace.allocate(length);
freeSpace.free(c.start, length); freeSpace.free(c.start, length);
buff.position(0); buff.position(0);
c.start = pos; c.start = pos;
c.writeHeader(buff); c.writeHeader(buff);
buff.position(buff.limit() - BLOCK_SIZE); buff.position(buff.limit() - BLOCK_SIZE);
byte[] header = getFileHeaderBytes(); byte[] header = getStoreHeaderBytes();
buff.put(header); buff.put(header);
// fill the header with zeroes // fill the header with zeroes
buff.put(new byte[BLOCK_SIZE - header.length]); buff.put(new byte[BLOCK_SIZE - header.length]);
buff.position(0); buff.position(0);
fileWriteCount++; fileStore.writeFully(pos, buff);
DataUtils.writeFully(file, pos, buff);
fileSize = Math.max(fileSize, pos + buff.position());
releaseWriteBuffer(buff); releaseWriteBuffer(buff);
meta.put("chunk." + c.id, c.asString()); meta.put("chunk." + c.id, c.asString());
} }
// update the metadata (within the file) // update the metadata (within the file)
store(); store();
syncFile(); sync();
shrinkFileIfPossible(0); shrinkFileIfPossible(0);
reuseSpace = oldReuse; reuseSpace = oldReuse;
...@@ -1328,17 +1225,11 @@ public class MVStore { ...@@ -1328,17 +1225,11 @@ public class MVStore {
} }
/** /**
* Force all changes to be written to the file. The default implementation * Force all changes to be written to the storage. The default
* calls FileChannel.force(true). * implementation calls FileChannel.force(true).
*/ */
public void syncFile() { public void sync() {
try { fileStore.sync();
file.force(true);
} catch (IOException e) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Could not sync file {0}", fileName, e);
}
} }
/** /**
...@@ -1435,7 +1326,7 @@ public class MVStore { ...@@ -1435,7 +1326,7 @@ public class MVStore {
private void copyLive(Chunk chunk, ArrayList<Chunk> old) { private void copyLive(Chunk chunk, ArrayList<Chunk> old) {
ByteBuffer buff = ByteBuffer.allocate(chunk.length); ByteBuffer buff = ByteBuffer.allocate(chunk.length);
DataUtils.readFully(file, chunk.start, buff); fileStore.readFully(chunk.start, buff);
Chunk.fromHeader(buff, chunk.start); Chunk.fromHeader(buff, chunk.start);
int chunkLength = chunk.length; int chunkLength = chunk.length;
markMetaChanged(); markMetaChanged();
...@@ -1502,8 +1393,7 @@ public class MVStore { ...@@ -1502,8 +1393,7 @@ public class MVStore {
} }
long filePos = c.start; long filePos = c.start;
filePos += DataUtils.getPageOffset(pos); filePos += DataUtils.getPageOffset(pos);
fileReadCount++; p = Page.read(fileStore, map, pos, filePos, fileStore.size());
p = Page.read(file, map, pos, filePos, fileSize);
cache.put(pos, p, p.getMemory()); cache.put(pos, p, p.getMemory());
} }
return p; return p;
...@@ -1626,10 +1516,16 @@ public class MVStore { ...@@ -1626,10 +1516,16 @@ public class MVStore {
this.retainVersion = retainVersion; this.retainVersion = retainVersion;
} }
/**
* Get the oldest version to retain in memory.
*
* @return the version
*/
public long getRetainVersion() { public long getRetainVersion() {
long v = retainVersion; long v = retainVersion;
if (currentStoreVersion >= -1) { long storeVersion = currentStoreVersion;
v = Math.min(v, currentStoreVersion); if (storeVersion > -1) {
v = Math.min(v, storeVersion);
} }
return v; return v;
} }
...@@ -1812,17 +1708,15 @@ public class MVStore { ...@@ -1812,17 +1708,15 @@ public class MVStore {
lastChunkId--; lastChunkId--;
} }
rootChunkStart = last.start; rootChunkStart = last.start;
writeFileHeader(); writeStoreHeader();
// need to write the header at the end of the file as well, // need to write the header at the end of the file as well,
// so that the old end header is not used // so that the old end header is not used
byte[] bytes = getFileHeaderBytes(); byte[] bytes = getStoreHeaderBytes();
ByteBuffer header = ByteBuffer.allocate(BLOCK_SIZE); ByteBuffer header = ByteBuffer.allocate(BLOCK_SIZE);
header.put(bytes); header.put(bytes);
header.rewind(); header.rewind();
fileWriteCount++; fileStore.writeFully(fileStore.size(), header);
DataUtils.writeFully(file, fileSize, header); readStoreHeader();
fileSize += BLOCK_SIZE;
readFileHeader();
readMeta(); readMeta();
} }
for (MVMap<?, ?> m : New.arrayList(maps.values())) { for (MVMap<?, ?> m : New.arrayList(maps.values())) {
...@@ -1879,53 +1773,23 @@ public class MVStore { ...@@ -1879,53 +1773,23 @@ public class MVStore {
} }
/** /**
* Get the number of file write operations since this store was opened. * Get the file store.
* *
* @return the number of write operations * @return the file store
*/ */
public int getFileWriteCount() { public FileStore getFileStore() {
return fileWriteCount; return fileStore;
} }
/** /**
* Get the number of file read operations since this store was opened. * Get the store header. This data is for informational purposes only. The
*
* @return the number of read operations
*/
public int getFileReadCount() {
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 * data is subject to change in future versions. The data should not be
* modified (doing so may corrupt the store). * modified (doing so may corrupt the store).
* *
* @return the file header * @return the store header
*/
public Map<String, String> getFileHeader() {
return fileHeader;
}
/**
* Get the file instance in use, if a file is used. The application may read
* from the file (for example for online backup), but not write to it or
* truncate it.
*
* @return the file, or null
*/ */
public FileChannel getFile() { public Map<String, String> getStoreHeader() {
checkOpen(); return storeHeader;
return file;
} }
private void checkOpen() { private void checkOpen() {
...@@ -1992,8 +1856,8 @@ public class MVStore { ...@@ -1992,8 +1856,8 @@ public class MVStore {
try { try {
store(true); store(true);
} catch (Exception e) { } catch (Exception e) {
if (backgroundExceptionListener != null) { if (backgroundExceptionHandler != null) {
backgroundExceptionListener.exceptionThrown(e); backgroundExceptionHandler.uncaughtException(null, e);
} }
} }
} }
...@@ -2009,10 +1873,6 @@ public class MVStore { ...@@ -2009,10 +1873,6 @@ public class MVStore {
} }
} }
public boolean isReadOnly() {
return readOnly;
}
public boolean isClosed() { public boolean isClosed() {
return closed; return closed;
} }
...@@ -2053,7 +1913,7 @@ public class MVStore { ...@@ -2053,7 +1913,7 @@ public class MVStore {
return; return;
} }
writeDelay = millis; writeDelay = millis;
if (file == null) { if (fileStore == null) {
return; return;
} }
stopBackgroundThread(); stopBackgroundThread();
...@@ -2061,7 +1921,7 @@ public class MVStore { ...@@ -2061,7 +1921,7 @@ public class MVStore {
if (millis > 0) { if (millis > 0) {
int sleep = Math.max(1, millis / 10); int sleep = Math.max(1, millis / 10);
Writer w = new Writer(this, sleep); Writer w = new Writer(this, sleep);
Thread t = new Thread(w, "MVStore writer " + fileName); Thread t = new Thread(w, "MVStore writer " + fileStore.toString());
t.setDaemon(true); t.setDaemon(true);
t.start(); t.start();
backgroundThread = t; backgroundThread = t;
...@@ -2136,7 +1996,7 @@ public class MVStore { ...@@ -2136,7 +1996,7 @@ public class MVStore {
* @return this * @return this
*/ */
public Builder encryptionKey(char[] password) { public Builder encryptionKey(char[] password) {
return set("encrypt", password); return set("encryptionKey", password);
} }
/** /**
...@@ -2215,12 +2075,12 @@ public class MVStore { ...@@ -2215,12 +2075,12 @@ public class MVStore {
* Set the listener to be used for exceptions that occur in the background * Set the listener to be used for exceptions that occur in the background
* thread. * thread.
* *
* @param backgroundExceptionListener the listener * @param exceptionHandler the handler
* @return this * @return this
*/ */
public Builder backgroundExceptionListener( public Builder backgroundExceptionHandler(
ExceptionListener backgroundExceptionListener) { Thread.UncaughtExceptionHandler exceptionHandler) {
return set("backgroundExceptionListener", backgroundExceptionListener); return set("backgroundExceptionHandler", exceptionHandler);
} }
/** /**
...@@ -2229,9 +2089,7 @@ public class MVStore { ...@@ -2229,9 +2089,7 @@ public class MVStore {
* @return the opened store * @return the opened store
*/ */
public MVStore open() { public MVStore open() {
MVStore s = new MVStore(config); return new MVStore(config);
s.open();
return s;
} }
@Override @Override
......
...@@ -7,7 +7,6 @@ ...@@ -7,7 +7,6 @@
package org.h2.mvstore; package org.h2.mvstore;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays; import java.util.Arrays;
import org.h2.compress.Compressor; import org.h2.compress.Compressor;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
...@@ -163,7 +162,7 @@ public class Page { ...@@ -163,7 +162,7 @@ public class Page {
* @param fileSize the file size (to avoid reading past EOF) * @param fileSize the file size (to avoid reading past EOF)
* @return the page * @return the page
*/ */
static Page read(FileChannel file, MVMap<?, ?> map, static Page read(FileStore fileStore, MVMap<?, ?> map,
long pos, long filePos, long fileSize) { long pos, long filePos, long fileSize) {
ByteBuffer buff; ByteBuffer buff;
int maxLength = DataUtils.getPageMaxLength(pos); int maxLength = DataUtils.getPageMaxLength(pos);
...@@ -171,12 +170,12 @@ public class Page { ...@@ -171,12 +170,12 @@ public class Page {
int length = maxLength; int length = maxLength;
if (maxLength == Integer.MAX_VALUE) { if (maxLength == Integer.MAX_VALUE) {
buff = ByteBuffer.allocate(128); buff = ByteBuffer.allocate(128);
DataUtils.readFully(file, filePos, buff); fileStore.readFully(filePos, buff);
maxLength = buff.getInt(); maxLength = buff.getInt();
//read the first bytes again //read the first bytes again
} }
buff = ByteBuffer.allocate(length); buff = ByteBuffer.allocate(length);
DataUtils.readFully(file, filePos, buff); fileStore.readFully(filePos, buff);
Page p = new Page(map, 0); Page p = new Page(map, 0);
p.pos = pos; p.pos = pos;
int chunkId = DataUtils.getPageChunkId(pos); int chunkId = DataUtils.getPageChunkId(pos);
......
...@@ -198,7 +198,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -198,7 +198,7 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) { public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
try { try {
long cost = 10 * (dataMap.map.getSize() + Constants.COST_ROW_OFFSET); long cost = 10 * (dataMap.map.sizeAsLong() + Constants.COST_ROW_OFFSET);
return cost; return cost;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
...@@ -256,13 +256,13 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -256,13 +256,13 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public long getRowCount(Session session) { public long getRowCount(Session session) {
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
return map.getSize(); return map.sizeAsLong();
} }
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
try { try {
return dataMap.map.getSize(); return dataMap.map.sizeAsLong();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
......
...@@ -165,7 +165,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -165,7 +165,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) { public double getCost(Session session, int[] masks, TableFilter filter, SortOrder sortOrder) {
try { try {
return 10 * getCostRangeIndex(masks, dataMap.map.getSize(), filter, sortOrder); return 10 * getCostRangeIndex(masks, dataMap.map.sizeAsLong(), filter, sortOrder);
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -213,7 +213,7 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -213,7 +213,7 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public boolean needRebuild() { public boolean needRebuild() {
try { try {
return dataMap.map.getSize() == 0; return dataMap.map.sizeAsLong() == 0;
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
...@@ -222,13 +222,13 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -222,13 +222,13 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public long getRowCount(Session session) { public long getRowCount(Session session) {
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
return map.getSize(); return map.sizeAsLong();
} }
@Override @Override
public long getRowCountApproximation() { public long getRowCountApproximation() {
try { try {
return dataMap.map.getSize(); return dataMap.map.sizeAsLong();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw DbException.get(ErrorCode.OBJECT_CLOSED); throw DbException.get(ErrorCode.OBJECT_CLOSED);
} }
......
...@@ -6,9 +6,8 @@ ...@@ -6,9 +6,8 @@
*/ */
package org.h2.mvstore.db; package org.h2.mvstore.db;
import java.beans.ExceptionListener;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -72,10 +71,10 @@ public class MVTableEngine implements TableEngine { ...@@ -72,10 +71,10 @@ public class MVTableEngine implements TableEngine {
} }
builder.encryptionKey(password); builder.encryptionKey(password);
} }
builder.backgroundExceptionListener(new ExceptionListener() { builder.backgroundExceptionHandler(new UncaughtExceptionHandler() {
@Override @Override
public void exceptionThrown(Exception e) { public void uncaughtException(Thread t, Throwable e) {
db.setBackgroundException(DbException.convert(e)); db.setBackgroundException(DbException.convert(e));
} }
...@@ -168,7 +167,7 @@ public class MVTableEngine implements TableEngine { ...@@ -168,7 +167,7 @@ public class MVTableEngine implements TableEngine {
* Store all pending changes. * Store all pending changes.
*/ */
public void store() { public void store() {
if (store.isReadOnly()) { if (store.getFileStore().isReadOnly()) {
return; return;
} }
store.commit(); store.commit();
...@@ -230,7 +229,7 @@ public class MVTableEngine implements TableEngine { ...@@ -230,7 +229,7 @@ public class MVTableEngine implements TableEngine {
} }
public InputStream getInputStream() { public InputStream getInputStream() {
return new FileChannelInputStream(store.getFile(), false); return new FileChannelInputStream(store.getFileStore().getFile(), false);
} }
/** /**
...@@ -238,11 +237,7 @@ public class MVTableEngine implements TableEngine { ...@@ -238,11 +237,7 @@ public class MVTableEngine implements TableEngine {
*/ */
public void sync() { public void sync() {
store(); store();
try { store.sync();
store.getFile().force(true);
} catch (IOException e) {
throw DbException.convertIOException(e, "Could not sync");
}
} }
/** /**
...@@ -257,7 +252,7 @@ public class MVTableEngine implements TableEngine { ...@@ -257,7 +252,7 @@ public class MVTableEngine implements TableEngine {
store.setRetentionTime(0); store.setRetentionTime(0);
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
while (store.compact(90)) { while (store.compact(90)) {
store.syncFile(); store.sync();
long time = System.currentTimeMillis() - start; long time = System.currentTimeMillis() - start;
if (time > maxCompactTime) { if (time > maxCompactTime) {
break; break;
...@@ -275,7 +270,7 @@ public class MVTableEngine implements TableEngine { ...@@ -275,7 +270,7 @@ public class MVTableEngine implements TableEngine {
*/ */
public void close(long maxCompactTime) { public void close(long maxCompactTime) {
if (!store.isClosed()) { if (!store.isClosed()) {
if (!store.isReadOnly()) { if (!store.getFileStore().isReadOnly()) {
store.store(); store.store();
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
while (store.compact(90)) { while (store.compact(90)) {
......
...@@ -111,10 +111,8 @@ public class TransactionStore { ...@@ -111,10 +111,8 @@ public class TransactionStore {
private synchronized void init() { private synchronized void init() {
String s = settings.get(LAST_TRANSACTION_ID); String s = settings.get(LAST_TRANSACTION_ID);
if (s != null) { lastTransactionId = DataUtils.parseLong(s, 0);
lastTransactionId = Long.parseLong(s); lastTransactionIdStored = lastTransactionId;
lastTransactionIdStored = lastTransactionId;
}
Long lastKey = preparedTransactions.lastKey(); Long lastKey = preparedTransactions.lastKey();
if (lastKey != null && lastKey.longValue() > lastTransactionId) { if (lastKey != null && lastKey.longValue() > lastTransactionId) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
...@@ -789,7 +787,7 @@ public class TransactionStore { ...@@ -789,7 +787,7 @@ public class TransactionStore {
* *
* @return the size * @return the size
*/ */
public long getSize() { public long sizeAsLong() {
// TODO this method is very slow // TODO this method is very slow
long size = 0; long size = 0;
Cursor<K> cursor = map.keyIterator(null); Cursor<K> cursor = map.keyIterator(null);
......
...@@ -14,12 +14,10 @@ import java.sql.ResultSetMetaData; ...@@ -14,12 +14,10 @@ import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils; import org.h2.mvstore.DataUtils;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
import org.h2.result.SortOrder; import org.h2.result.SortOrder;
import org.h2.store.Data;
import org.h2.store.DataHandler; import org.h2.store.DataHandler;
import org.h2.store.LobStorageFrontend; import org.h2.store.LobStorageFrontend;
import org.h2.tools.SimpleResultSet; import org.h2.tools.SimpleResultSet;
...@@ -155,7 +153,6 @@ public class ValueDataType implements DataType { ...@@ -155,7 +153,6 @@ public class ValueDataType implements DataType {
} }
private ByteBuffer writeValue(ByteBuffer buff, Value v) { private ByteBuffer writeValue(ByteBuffer buff, Value v) {
int start = buff.position();
if (v == ValueNull.INSTANCE) { if (v == ValueNull.INSTANCE) {
buff.put((byte) 0); buff.put((byte) 0);
return buff; return buff;
...@@ -426,12 +423,6 @@ public class ValueDataType implements DataType { ...@@ -426,12 +423,6 @@ public class ValueDataType implements DataType {
default: default:
DbException.throwInternalError("type=" + v.getType()); DbException.throwInternalError("type=" + v.getType());
} }
if (SysProperties.CHECK2) {
if (buff.position() - start != Data.getValueLen(v, handler)) {
throw DbException
.throwInternalError("value size error: got " + (buff.position() - start) + " expected " + Data.getValueLen(v, handler));
}
}
return buff; return buff;
} }
......
...@@ -600,126 +600,126 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -600,126 +600,126 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
beforeTest(); beforeTest();
// db // db
new TestScriptSimple().runTest(this); // new TestScriptSimple().runTest(this);
new TestScript().runTest(this); // new TestScript().runTest(this);
new TestAlter().runTest(this); // new TestAlter().runTest(this);
new TestAlterSchemaRename().runTest(this); // new TestAlterSchemaRename().runTest(this);
new TestAutoRecompile().runTest(this); // new TestAutoRecompile().runTest(this);
new TestBitField().runTest(this); // new TestBitField().runTest(this);
new TestBackup().runTest(this); // new TestBackup().runTest(this);
new TestBigDb().runTest(this); // new TestBigDb().runTest(this);
new TestBigResult().runTest(this); // new TestBigResult().runTest(this);
new TestCases().runTest(this); // new TestCases().runTest(this);
new TestCheckpoint().runTest(this); // new TestCheckpoint().runTest(this);
new TestCluster().runTest(this); // new TestCluster().runTest(this);
new TestCompatibility().runTest(this); // new TestCompatibility().runTest(this);
new TestCsv().runTest(this); // new TestCsv().runTest(this);
new TestDateStorage().runTest(this); // new TestDateStorage().runTest(this);
new TestDeadlock().runTest(this); // new TestDeadlock().runTest(this);
new TestDrop().runTest(this); // new TestDrop().runTest(this);
new TestEncryptedDb().runTest(this); // new TestEncryptedDb().runTest(this);
new TestExclusive().runTest(this); // new TestExclusive().runTest(this);
new TestFullText().runTest(this); // new TestFullText().runTest(this);
new TestFunctionOverload().runTest(this); // new TestFunctionOverload().runTest(this);
new TestFunctions().runTest(this); // new TestFunctions().runTest(this);
new TestInit().runTest(this); // new TestInit().runTest(this);
new TestIndex().runTest(this); // new TestIndex().runTest(this);
new TestLargeBlob().runTest(this); // new TestLargeBlob().runTest(this);
new TestLinkedTable().runTest(this); // new TestLinkedTable().runTest(this);
new TestListener().runTest(this); // new TestListener().runTest(this);
new TestLob().runTest(this); // new TestLob().runTest(this);
new TestMemoryUsage().runTest(this); // new TestMemoryUsage().runTest(this);
new TestMultiConn().runTest(this); // new TestMultiConn().runTest(this);
new TestMultiDimension().runTest(this); // new TestMultiDimension().runTest(this);
new TestMultiThread().runTest(this); // new TestMultiThread().runTest(this);
new TestMultiThreadedKernel().runTest(this); // new TestMultiThreadedKernel().runTest(this);
new TestOpenClose().runTest(this); // new TestOpenClose().runTest(this);
new TestOptimizations().runTest(this); // new TestOptimizations().runTest(this);
new TestOutOfMemory().runTest(this); // new TestOutOfMemory().runTest(this);
new TestPowerOff().runTest(this); // new TestPowerOff().runTest(this);
new TestQueryCache().runTest(this); // new TestQueryCache().runTest(this);
new TestReadOnly().runTest(this); // new TestReadOnly().runTest(this);
new TestRecursiveQueries().runTest(this); // new TestRecursiveQueries().runTest(this);
new TestRights().runTest(this); // new TestRights().runTest(this);
new TestRunscript().runTest(this); // new TestRunscript().runTest(this);
new TestSQLInjection().runTest(this); // new TestSQLInjection().runTest(this);
new TestSessionsLocks().runTest(this); // new TestSessionsLocks().runTest(this);
new TestSelectCountNonNullColumn().runTest(this); // new TestSelectCountNonNullColumn().runTest(this);
new TestSequence().runTest(this); // new TestSequence().runTest(this);
new TestShow().runTest(this); // new TestShow().runTest(this);
new TestSpaceReuse().runTest(this); // new TestSpaceReuse().runTest(this);
new TestSpatial().runTest(this); // new TestSpatial().runTest(this);
new TestSpeed().runTest(this); // new TestSpeed().runTest(this);
new TestTableEngines().runTest(this); // new TestTableEngines().runTest(this);
new TestTempTables().runTest(this); // new TestTempTables().runTest(this);
new TestTransaction().runTest(this); // new TestTransaction().runTest(this);
new TestTriggersConstraints().runTest(this); // new TestTriggersConstraints().runTest(this);
new TestTwoPhaseCommit().runTest(this); // new TestTwoPhaseCommit().runTest(this);
new TestView().runTest(this); // new TestView().runTest(this);
new TestViewAlterTable().runTest(this); // new TestViewAlterTable().runTest(this);
new TestViewDropView().runTest(this); // new TestViewDropView().runTest(this);
//
// jaqu // // jaqu
new AliasMapTest().runTest(this); // new AliasMapTest().runTest(this);
new AnnotationsTest().runTest(this); // new AnnotationsTest().runTest(this);
new ClobTest().runTest(this); // new ClobTest().runTest(this);
new ModelsTest().runTest(this); // new ModelsTest().runTest(this);
new SamplesTest().runTest(this); // new SamplesTest().runTest(this);
new UpdateTest().runTest(this); // new UpdateTest().runTest(this);
//
// jdbc // // jdbc
new TestBatchUpdates().runTest(this); // new TestBatchUpdates().runTest(this);
new TestCallableStatement().runTest(this); // new TestCallableStatement().runTest(this);
new TestCancel().runTest(this); // new TestCancel().runTest(this);
new TestDatabaseEventListener().runTest(this); // new TestDatabaseEventListener().runTest(this);
new TestDriver().runTest(this); // new TestDriver().runTest(this);
new TestJavaObject().runTest(this); // new TestJavaObject().runTest(this);
new TestJavaObjectSerializer().runTest(this); // new TestJavaObjectSerializer().runTest(this);
new TestUrlJavaObjectSerializer().runTest(this); // new TestUrlJavaObjectSerializer().runTest(this);
//
new TestLimitUpdates().runTest(this); // new TestLimitUpdates().runTest(this);
new TestLobApi().runTest(this); // new TestLobApi().runTest(this);
new TestManyJdbcObjects().runTest(this); // new TestManyJdbcObjects().runTest(this);
new TestMetaData().runTest(this); // new TestMetaData().runTest(this);
new TestNativeSQL().runTest(this); // new TestNativeSQL().runTest(this);
new TestPreparedStatement().runTest(this); // new TestPreparedStatement().runTest(this);
new TestResultSet().runTest(this); // new TestResultSet().runTest(this);
new TestStatement().runTest(this); // new TestStatement().runTest(this);
new TestTransactionIsolation().runTest(this); // new TestTransactionIsolation().runTest(this);
new TestUpdatableResultSet().runTest(this); // new TestUpdatableResultSet().runTest(this);
new TestZloty().runTest(this); // new TestZloty().runTest(this);
//
// jdbcx // // jdbcx
new TestConnectionPool().runTest(this); // new TestConnectionPool().runTest(this);
new TestDataSource().runTest(this); // new TestDataSource().runTest(this);
new TestXA().runTest(this); // new TestXA().runTest(this);
new TestXASimple().runTest(this); // new TestXASimple().runTest(this);
//
// server // // server
new TestAutoServer().runTest(this); // new TestAutoServer().runTest(this);
new TestNestedLoop().runTest(this); // new TestNestedLoop().runTest(this);
new TestWeb().runTest(this); // new TestWeb().runTest(this);
//
// mvcc & row level locking // // mvcc & row level locking
new TestMvcc1().runTest(this); // new TestMvcc1().runTest(this);
new TestMvcc2().runTest(this); // new TestMvcc2().runTest(this);
new TestMvcc3().runTest(this); // new TestMvcc3().runTest(this);
new TestMvccMultiThreaded().runTest(this); // new TestMvccMultiThreaded().runTest(this);
new TestRowLocks().runTest(this); // new TestRowLocks().runTest(this);
//
// synth // // synth
new TestBtreeIndex().runTest(this); // new TestBtreeIndex().runTest(this);
new TestDiskFull().runTest(this); // new TestDiskFull().runTest(this);
new TestCrashAPI().runTest(this); // new TestCrashAPI().runTest(this);
new TestFuzzOptimizations().runTest(this); // new TestFuzzOptimizations().runTest(this);
new TestLimit().runTest(this); // new TestLimit().runTest(this);
new TestRandomSQL().runTest(this); // new TestRandomSQL().runTest(this);
new TestRandomCompare().runTest(this); // new TestRandomCompare().runTest(this);
new TestKillRestart().runTest(this); // new TestKillRestart().runTest(this);
new TestKillRestartMulti().runTest(this); // new TestKillRestartMulti().runTest(this);
new TestMultiThreaded().runTest(this); // new TestMultiThreaded().runTest(this);
new TestOuterJoins().runTest(this); // new TestOuterJoins().runTest(this);
new TestNestedJoins().runTest(this); // new TestNestedJoins().runTest(this);
afterTest(); afterTest();
} }
......
...@@ -121,7 +121,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -121,7 +121,7 @@ public class TestConcurrent extends TestMVStore {
s.store(); s.store();
map.clear(); map.clear();
s.store(); s.store();
long len = s.getFile().size(); long len = s.getFileStore().size();
if (len > 1024 * 1024) { if (len > 1024 * 1024) {
// slow down writing a lot // slow down writing a lot
Thread.sleep(200); Thread.sleep(200);
...@@ -136,7 +136,7 @@ public class TestConcurrent extends TestMVStore { ...@@ -136,7 +136,7 @@ public class TestConcurrent extends TestMVStore {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
// System.out.println("test " + i); // System.out.println("test " + i);
s.setReuseSpace(false); s.setReuseSpace(false);
byte[] buff = readFileSlowly(s.getFile(), s.getFile().size()); byte[] buff = readFileSlowly(s.getFileStore().getFile(), s.getFileStore().size());
s.setReuseSpace(true); s.setReuseSpace(true);
FileOutputStream out = new FileOutputStream(fileNameRestore); FileOutputStream out = new FileOutputStream(fileNameRestore);
out.write(buff); out.write(buff);
......
...@@ -5,8 +5,7 @@ ...@@ -5,8 +5,7 @@
*/ */
package org.h2.test.store; package org.h2.test.store;
import java.beans.ExceptionListener; import java.lang.Thread.UncaughtExceptionHandler;
import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.util.Iterator; import java.util.Iterator;
...@@ -96,13 +95,11 @@ public class TestMVStore extends TestBase { ...@@ -96,13 +95,11 @@ public class TestMVStore extends TestBase {
private void testNewerWriteVersion() throws Exception { private void testNewerWriteVersion() throws Exception {
String fileName = getBaseDir() + "/testNewerWriteVersion.h3"; String fileName = getBaseDir() + "/testNewerWriteVersion.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
char[] passwordChars;
passwordChars = "007".toCharArray();
MVStore s = new MVStore.Builder(). MVStore s = new MVStore.Builder().
encryptionKey(passwordChars). encryptionKey("007".toCharArray()).
fileName(fileName). fileName(fileName).
open(); open();
Map<String, String> header = s.getFileHeader(); Map<String, String> header = s.getStoreHeader();
assertEquals("1", header.get("format")); assertEquals("1", header.get("format"));
header.put("formatRead", "1"); header.put("formatRead", "1");
header.put("format", "2"); header.put("format", "2");
...@@ -111,15 +108,36 @@ public class TestMVStore extends TestBase { ...@@ -111,15 +108,36 @@ public class TestMVStore extends TestBase {
s.store(); s.store();
s.close(); s.close();
passwordChars = "007".toCharArray(); try {
s = new MVStore.Builder().
encryptionKey("007".toCharArray()).
fileName(fileName).
open();
fail();
} catch (IllegalStateException e) {
assertEquals(DataUtils.ERROR_UNSUPPORTED_FORMAT,
DataUtils.getErrorCode(e.getMessage()));
}
s = new MVStore.Builder(). s = new MVStore.Builder().
encryptionKey(passwordChars). encryptionKey("007".toCharArray()).
readOnly().
fileName(fileName).
open();
assertTrue(s.getFileStore().isReadOnly());
m = s.openMap("data");
assertEquals("Hello World", m.get(0));
s.close();
FileUtils.setReadOnly(fileName);
s = new MVStore.Builder().
encryptionKey("007".toCharArray()).
fileName(fileName). fileName(fileName).
open(); open();
assertTrue(s.isReadOnly()); assertTrue(s.getFileStore().isReadOnly());
m = s.openMap("data"); m = s.openMap("data");
assertEquals("Hello World", m.get(0)); assertEquals("Hello World", m.get(0));
s.close(); s.close();
} }
private void testCompactFully() throws Exception { private void testCompactFully() throws Exception {
...@@ -139,9 +157,9 @@ public class TestMVStore extends TestBase { ...@@ -139,9 +157,9 @@ public class TestMVStore extends TestBase {
m.removeMap(); m.removeMap();
s.store(); s.store();
} }
long sizeOld = s.getFile().size(); long sizeOld = s.getFileStore().size();
s.compactMoveChunks(); s.compactMoveChunks();
long sizeNew = s.getFile().size(); long sizeNew = s.getFileStore().size();
assertTrue("old: " + sizeOld + " new: " + sizeNew, sizeNew < sizeOld); assertTrue("old: " + sizeOld + " new: " + sizeNew, sizeNew < sizeOld);
s.close(); s.close();
} }
...@@ -150,13 +168,13 @@ public class TestMVStore extends TestBase { ...@@ -150,13 +168,13 @@ public class TestMVStore extends TestBase {
String fileName = getBaseDir() + "/testBackgroundExceptionListener.h3"; String fileName = getBaseDir() + "/testBackgroundExceptionListener.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s; MVStore s;
final AtomicReference<Exception> exRef = new AtomicReference<Exception>(); final AtomicReference<Throwable> exRef = new AtomicReference<Throwable>();
s = new MVStore.Builder(). s = new MVStore.Builder().
fileName(fileName). fileName(fileName).
backgroundExceptionListener(new ExceptionListener() { backgroundExceptionHandler(new UncaughtExceptionHandler() {
@Override @Override
public void exceptionThrown(Exception e) { public void uncaughtException(Thread t, Throwable e) {
exRef.set(e); exRef.set(e);
} }
...@@ -165,7 +183,7 @@ public class TestMVStore extends TestBase { ...@@ -165,7 +183,7 @@ public class TestMVStore extends TestBase {
s.setWriteDelay(2); s.setWriteDelay(2);
MVMap<Integer, String> m; MVMap<Integer, String> m;
m = s.openMap("data"); m = s.openMap("data");
s.getFile().close(); s.getFileStore().getFile().close();
m.put(1, "Hello"); m.put(1, "Hello");
s.commit(); s.commit();
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
...@@ -174,7 +192,7 @@ public class TestMVStore extends TestBase { ...@@ -174,7 +192,7 @@ public class TestMVStore extends TestBase {
} }
Thread.sleep(1); Thread.sleep(1);
} }
Exception e = exRef.get(); Throwable e = exRef.get();
assertTrue(e != null); assertTrue(e != null);
assertEquals(DataUtils.ERROR_WRITING_FAILED, DataUtils.getErrorCode(e.getMessage())); assertEquals(DataUtils.ERROR_WRITING_FAILED, DataUtils.getErrorCode(e.getMessage()));
...@@ -217,7 +235,7 @@ public class TestMVStore extends TestBase { ...@@ -217,7 +235,7 @@ public class TestMVStore extends TestBase {
FileUtils.delete(fileName); FileUtils.delete(fileName);
} }
private void testWriteBuffer() throws IOException { private void testWriteBuffer() {
String fileName = getBaseDir() + "/testWriteBuffer.h3"; String fileName = getBaseDir() + "/testWriteBuffer.h3";
FileUtils.delete(fileName); FileUtils.delete(fileName);
MVStore s; MVStore s;
...@@ -234,7 +252,7 @@ public class TestMVStore extends TestBase { ...@@ -234,7 +252,7 @@ public class TestMVStore extends TestBase {
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
m.put(i, data); m.put(i, data);
} }
long size = s.getFile().size(); long size = s.getFileStore().size();
assertTrue("last:" + lastSize + " now: " + size, size > lastSize); assertTrue("last:" + lastSize + " now: " + size, size > lastSize);
lastSize = size; lastSize = size;
s.close(); s.close();
...@@ -379,7 +397,7 @@ public class TestMVStore extends TestBase { ...@@ -379,7 +397,7 @@ public class TestMVStore extends TestBase {
s = new MVStore.Builder(). s = new MVStore.Builder().
fileName(fileName). fileName(fileName).
encryptionKey(passwordChars).open(); encryptionKey(passwordChars).open();
assertTrue(s.isReadOnly()); assertTrue(s.getFileStore().isReadOnly());
FileUtils.delete(fileName); FileUtils.delete(fileName);
assertFalse(FileUtils.exists(fileName)); assertFalse(FileUtils.exists(fileName));
...@@ -393,7 +411,7 @@ public class TestMVStore extends TestBase { ...@@ -393,7 +411,7 @@ public class TestMVStore extends TestBase {
s = openStore(fileName); s = openStore(fileName);
m = s.openMap("test"); m = s.openMap("test");
m.put(1, 1); m.put(1, 1);
Map<String, String> header = s.getFileHeader(); Map<String, String> header = s.getStoreHeader();
int format = Integer.parseInt(header.get("format")); int format = Integer.parseInt(header.get("format"));
assertEquals(1, format); assertEquals(1, format);
header.put("format", Integer.toString(format + 1)); header.put("format", Integer.toString(format + 1));
...@@ -483,7 +501,7 @@ public class TestMVStore extends TestBase { ...@@ -483,7 +501,7 @@ public class TestMVStore extends TestBase {
} }
} }
assertEquals(expectedReadsForCacheSize[cacheSize], assertEquals(expectedReadsForCacheSize[cacheSize],
s.getFileReadCount()); s.getFileStore().getReadCount());
s.close(); s.close();
} }
...@@ -506,10 +524,10 @@ public class TestMVStore extends TestBase { ...@@ -506,10 +524,10 @@ public class TestMVStore extends TestBase {
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
// expected // expected
} }
assertFalse(s.isReadOnly()); assertFalse(s.getFileStore().isReadOnly());
s.close(); s.close();
s = new MVStore.Builder().fileName(fileName).readOnly().open(); s = new MVStore.Builder().fileName(fileName).readOnly().open();
assertTrue(s.isReadOnly()); assertTrue(s.getFileStore().isReadOnly());
s.close(); s.close();
} }
...@@ -517,17 +535,17 @@ public class TestMVStore extends TestBase { ...@@ -517,17 +535,17 @@ public class TestMVStore extends TestBase {
String fileName = getBaseDir() + "/testFileHeader.h3"; String fileName = getBaseDir() + "/testFileHeader.h3";
MVStore s = openStore(fileName); MVStore s = openStore(fileName);
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
assertEquals("3", s.getFileHeader().get("H")); assertEquals("3", s.getStoreHeader().get("H"));
long creationTime = Long.parseLong(s.getFileHeader() long creationTime = Long.parseLong(s.getStoreHeader()
.get("creationTime")); .get("creationTime"));
assertTrue(Math.abs(time - creationTime) < 100); assertTrue(Math.abs(time - creationTime) < 100);
s.getFileHeader().put("test", "123"); s.getStoreHeader().put("test", "123");
MVMap<Integer, Integer> map = s.openMap("test"); MVMap<Integer, Integer> map = s.openMap("test");
map.put(10, 100); map.put(10, 100);
s.store(); s.store();
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
assertEquals("123", s.getFileHeader().get("test")); assertEquals("123", s.getStoreHeader().get("test"));
s.close(); s.close();
} }
...@@ -545,7 +563,7 @@ public class TestMVStore extends TestBase { ...@@ -545,7 +563,7 @@ public class TestMVStore extends TestBase {
s.compact(50); s.compact(50);
map.put(10, 100); map.put(10, 100);
s.store(); s.store();
FilePath f = FilePath.get(s.getFileName()); FilePath f = FilePath.get(fileName);
s.close(); s.close();
int blockSize = 4 * 1024; int blockSize = 4 * 1024;
// test corrupt file headers // test corrupt file headers
...@@ -990,7 +1008,7 @@ public class TestMVStore extends TestBase { ...@@ -990,7 +1008,7 @@ public class TestMVStore extends TestBase {
assertEquals(1000, m.size()); assertEquals(1000, m.size());
assertEquals(286, s.getUnsavedPageCount()); assertEquals(286, s.getUnsavedPageCount());
s.store(); s.store();
assertEquals(2, s.getFileWriteCount()); assertEquals(2, s.getFileStore().getWriteCount());
s.close(); s.close();
s = openStore(fileName); s = openStore(fileName);
...@@ -999,8 +1017,8 @@ public class TestMVStore extends TestBase { ...@@ -999,8 +1017,8 @@ public class TestMVStore extends TestBase {
assertEquals(0, m.size()); assertEquals(0, m.size());
s.store(); s.store();
// ensure only nodes are read, but not leaves // ensure only nodes are read, but not leaves
assertEquals(42, s.getFileReadCount()); assertEquals(42, s.getFileStore().getReadCount());
assertEquals(1, s.getFileWriteCount()); assertEquals(1, s.getFileStore().getWriteCount());
s.close(); s.close();
} }
...@@ -1574,7 +1592,7 @@ public class TestMVStore extends TestBase { ...@@ -1574,7 +1592,7 @@ public class TestMVStore extends TestBase {
s.close(); s.close();
} }
private void testLargerThan2G() throws IOException { private void testLargerThan2G() {
if (!config.big) { if (!config.big) {
return; return;
} }
...@@ -1592,7 +1610,7 @@ public class TestMVStore extends TestBase { ...@@ -1592,7 +1610,7 @@ public class TestMVStore extends TestBase {
store.setRetainVersion(version); store.setRetainVersion(version);
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
if (time - last > 2000) { if (time - last > 2000) {
long mb = store.getFile().size() / 1024 / 1024; long mb = store.getFileStore().size() / 1024 / 1024;
trace(mb + "/4500"); trace(mb + "/4500");
if (mb > 4500) { if (mb > 4500) {
break; break;
......
...@@ -530,7 +530,7 @@ public class TestMVTableEngine extends TestBase { ...@@ -530,7 +530,7 @@ public class TestMVTableEngine extends TestBase {
FileUtils.setReadOnly(getBaseDir() + "/mvstore.h2.db"); FileUtils.setReadOnly(getBaseDir() + "/mvstore.h2.db");
conn = getConnection(dbName); conn = getConnection(dbName);
Database db = (Database) ((JdbcConnection) conn).getSession().getDataHandler(); Database db = (Database) ((JdbcConnection) conn).getSession().getDataHandler();
assertTrue(db.getMvStore().getStore().isReadOnly()); assertTrue(db.getMvStore().getStore().getFileStore().isReadOnly());
conn.close(); conn.close();
FileUtils.deleteRecursive(getBaseDir(), true); FileUtils.deleteRecursive(getBaseDir(), true);
} }
......
...@@ -487,8 +487,8 @@ public class TestTransactionStore extends TestBase { ...@@ -487,8 +487,8 @@ public class TestTransactionStore extends TestBase {
size++; size++;
} }
buff.append('\n'); buff.append('\n');
if (size != map.getSize()) { if (size != map.sizeAsLong()) {
assertEquals(size, map.getSize()); assertEquals(size, map.sizeAsLong());
} }
} }
int x = r.nextInt(rowCount); int x = r.nextInt(rowCount);
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论