提交 769193eb authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: error codes; fix MVRTreeMap iterators

上级 aed6260d
...@@ -95,6 +95,7 @@ public class Chunk { ...@@ -95,6 +95,7 @@ public class Chunk {
static Chunk fromHeader(ByteBuffer buff, long start) { static Chunk fromHeader(ByteBuffer buff, long start) {
if (buff.get() != 'c') { if (buff.get() != 'c') {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"File corrupt reading chunk at position {0}", start); "File corrupt reading chunk at position {0}", start);
} }
int length = buff.getInt(); int length = buff.getInt();
......
...@@ -15,27 +15,19 @@ import java.util.Iterator; ...@@ -15,27 +15,19 @@ import java.util.Iterator;
*/ */
public class Cursor<K> implements Iterator<K> { public class Cursor<K> implements Iterator<K> {
protected final MVMap<K, ?> map; private final MVMap<K, ?> map;
protected final K from; private final K from;
protected CursorPos pos; private CursorPos pos;
protected K current; private K current;
private final Page root; private final Page root;
private boolean initialized; private boolean initialized;
protected Cursor(MVMap<K, ?> map, Page root, K from) { Cursor(MVMap<K, ?> map, Page root, K from) {
this.map = map; this.map = map;
this.root = root; this.root = root;
this.from = from; this.from = from;
} }
@Override
public K next() {
hasNext();
K c = current;
fetchNext();
return c;
}
@Override @Override
public boolean hasNext() { public boolean hasNext() {
if (!initialized) { if (!initialized) {
...@@ -46,6 +38,14 @@ public class Cursor<K> implements Iterator<K> { ...@@ -46,6 +38,14 @@ public class Cursor<K> implements Iterator<K> {
return current != null; return current != null;
} }
@Override
public K next() {
hasNext();
K c = current;
fetchNext();
return c;
}
/** /**
* Skip over that many entries. This method is relatively fast (for this map * Skip over that many entries. This method is relatively fast (for this map
* implementation) even if many entries need to be skipped. * implementation) even if many entries need to be skipped.
...@@ -82,7 +82,7 @@ public class Cursor<K> implements Iterator<K> { ...@@ -82,7 +82,7 @@ public class Cursor<K> implements Iterator<K> {
* @param p the page to start * @param p the page to start
* @param from the key to search * @param from the key to search
*/ */
protected void min(Page p, K from) { private void min(Page p, K from) {
while (true) { while (true) {
if (p.isLeaf()) { if (p.isLeaf()) {
int x = from == null ? 0 : p.binarySearch(from); int x = from == null ? 0 : p.binarySearch(from);
...@@ -107,7 +107,7 @@ public class Cursor<K> implements Iterator<K> { ...@@ -107,7 +107,7 @@ public class Cursor<K> implements Iterator<K> {
* Fetch the next entry if there is one. * Fetch the next entry if there is one.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void fetchNext() { private void fetchNext() {
while (pos != null) { while (pos != null) {
if (pos.index < pos.page.getKeyCount()) { if (pos.index < pos.page.getKeyCount()) {
current = (K) pos.page.getKey(pos.index++); current = (K) pos.page.getKey(pos.index++);
......
...@@ -24,6 +24,57 @@ import org.h2.util.New; ...@@ -24,6 +24,57 @@ import org.h2.util.New;
*/ */
public class DataUtils { public class DataUtils {
/**
* An error occurred while reading from the file.
*/
public static final int ERROR_READING_FAILED = 1;
/**
* An error occurred when trying to write to the file.
*/
public static final int ERROR_WRITING_FAILED = 2;
/**
* An internal error occurred. This could be a bug, or a memory corruption
* (for example caused by out of memory).
*/
public static final int ERROR_INTERNAL = 3;
/**
* The object is already closed.
*/
public static final int ERROR_CLOSED = 4;
/**
* The file format is not supported.
*/
public static final int ERROR_UNSUPPORTED_FORMAT = 5;
/**
* The file is corrupt or (for encrypted files) the encryption key is wrong.
*/
public static final int ERROR_FILE_CORRUPT = 6;
/**
* The file is locked.
*/
public static final int ERROR_FILE_LOCKED = 7;
/**
* An error occurred when serializing or de-serializing.
*/
public static final int ERROR_SERIALIZATION = 8;
/**
* The transaction store is corrupt.
*/
public static final int ERROR_TRANSACTION_CORRUPT = 100;
/**
* A lock timeout occurred.
*/
public static final int ERROR_TRANSACTION_LOCK_TIMEOUT = 101;
/** /**
* The type for leaf page. * The type for leaf page.
*/ */
...@@ -332,6 +383,7 @@ public class DataUtils { ...@@ -332,6 +383,7 @@ public class DataUtils {
dst.rewind(); dst.rewind();
} catch (IOException e) { } catch (IOException e) {
throw newIllegalStateException( throw newIllegalStateException(
ERROR_READING_FAILED,
"Reading from {0} failed; length {1} at {2}", "Reading from {0} failed; length {1} at {2}",
file, dst.remaining(), pos, e); file, dst.remaining(), pos, e);
} }
...@@ -353,6 +405,7 @@ public class DataUtils { ...@@ -353,6 +405,7 @@ public class DataUtils {
} while (src.remaining() > 0); } while (src.remaining() > 0);
} catch (IOException e) { } catch (IOException e) {
throw newIllegalStateException( throw newIllegalStateException(
ERROR_WRITING_FAILED,
"Writing to {0} failed; length {1} at {2}", "Writing to {0} failed; length {1} at {2}",
file, src.remaining(), pos, e); file, src.remaining(), pos, e);
} }
...@@ -518,13 +571,17 @@ public class DataUtils { ...@@ -518,13 +571,17 @@ public class DataUtils {
* *
* @param s the list * @param s the list
* @return the map * @return the map
* @throws IllegalStateException if parsing failed
*/ */
public static HashMap<String, String> parseMap(String s) { public static HashMap<String, String> parseMap(String s) {
HashMap<String, String> map = New.hashMap(); HashMap<String, String> map = New.hashMap();
for (int i = 0, size = s.length(); i < size;) { for (int i = 0, size = s.length(); i < size;) {
int startKey = i; int startKey = i;
i = s.indexOf(':', i); i = s.indexOf(':', i);
checkArgument(i >= 0, "Not a map"); if (i < 0) {
throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT, "Not a map: {0}", s);
}
String key = s.substring(startKey, i++); String key = s.substring(startKey, i++);
StringBuilder buff = new StringBuilder(); StringBuilder buff = new StringBuilder();
while (i < size) { while (i < size) {
...@@ -596,7 +653,7 @@ public class DataUtils { ...@@ -596,7 +653,7 @@ public class DataUtils {
public static IllegalArgumentException newIllegalArgumentException( public static IllegalArgumentException newIllegalArgumentException(
String message, Object... arguments) { String message, Object... arguments) {
return initCause(new IllegalArgumentException( return initCause(new IllegalArgumentException(
MessageFormat.format(message, arguments) + " " + getVersion()), formatMessage(0, message, arguments)),
arguments); arguments);
} }
...@@ -608,16 +665,18 @@ public class DataUtils { ...@@ -608,16 +665,18 @@ public class DataUtils {
*/ */
public static UnsupportedOperationException newUnsupportedOperationException( public static UnsupportedOperationException newUnsupportedOperationException(
String message) { String message) {
return new UnsupportedOperationException(message + " " + getVersion()); return new UnsupportedOperationException(formatMessage(0, message));
} }
/** /**
* Create a new ConcurrentModificationException. * Create a new ConcurrentModificationException.
* *
* @param message the message
* @return the exception * @return the exception
*/ */
public static ConcurrentModificationException newConcurrentModificationException() { public static ConcurrentModificationException newConcurrentModificationException(
return new ConcurrentModificationException(getVersion()); String message) {
return new ConcurrentModificationException(formatMessage(0, message));
} }
/** /**
...@@ -628,9 +687,9 @@ public class DataUtils { ...@@ -628,9 +687,9 @@ public class DataUtils {
* @return the exception * @return the exception
*/ */
public static IllegalStateException newIllegalStateException( public static IllegalStateException newIllegalStateException(
String message, Object... arguments) { int errorCode, String message, Object... arguments) {
return initCause(new IllegalStateException( return initCause(new IllegalStateException(
MessageFormat.format(message, arguments) + " " + getVersion()), formatMessage(errorCode, message, arguments)),
arguments); arguments);
} }
...@@ -644,10 +703,35 @@ public class DataUtils { ...@@ -644,10 +703,35 @@ public class DataUtils {
} }
return e; return e;
} }
private static String formatMessage(int errorCode, String message, Object... arguments) {
return MessageFormat.format(message, arguments) + " " + getVersionAndCode(errorCode);
}
private static String getVersion() { private static String getVersionAndCode(int errorCode) {
return "[" + Constants.VERSION_MAJOR + "." + return "[" + Constants.VERSION_MAJOR + "." +
Constants.VERSION_MINOR + "." + Constants.BUILD_ID + "]"; Constants.VERSION_MINOR + "." + Constants.BUILD_ID + "/" + errorCode + "]";
}
/**
* Get the error code from an exception message.
*
* @param m the message
* @return the error code, or 0 if none
*/
public static int getErrorCode(String m) {
if (m.endsWith("]")) {
int dash = m.lastIndexOf('/');
if (dash >= 0) {
String s = m.substring(dash + 1, m.length() - 1);
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
// no error code
}
}
}
return 0;
} }
/** /**
......
...@@ -53,6 +53,7 @@ public class FreeSpaceList { ...@@ -53,6 +53,7 @@ public class FreeSpaceList {
} }
} }
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Could not find a free page to allocate"); "Could not find a free page to allocate");
} }
...@@ -75,10 +76,12 @@ public class FreeSpaceList { ...@@ -75,10 +76,12 @@ public class FreeSpaceList {
} }
if (found == null) { if (found == null) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Cannot find spot to mark chunk as used in free list: {0}", c); "Cannot find spot to mark chunk as used in free list: {0}", c);
} }
if (chunkStart + required > found.start + found.length) { if (chunkStart + required > found.start + found.length) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Chunk runs over edge of free space: {0}", c); "Chunk runs over edge of free space: {0}", c);
} }
if (found.start == chunkStart) { if (found.start == chunkStart) {
...@@ -127,6 +130,7 @@ public class FreeSpaceList { ...@@ -127,6 +130,7 @@ public class FreeSpaceList {
} }
if (found == null) { if (found == null) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Cannot find spot to mark chunk as unused in free list: {0}", c); "Cannot find spot to mark chunk as unused in free list: {0}", c);
} }
if (chunkStart + required + 1 == found.start) { if (chunkStart + required + 1 == found.start) {
......
...@@ -907,7 +907,8 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -907,7 +907,8 @@ public class MVMap<K, V> extends AbstractMap<K, V>
*/ */
protected void checkOpen() { protected void checkOpen() {
if (closed) { if (closed) {
throw DataUtils.newIllegalStateException("This map is closed"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_CLOSED, "This map is closed");
} }
} }
...@@ -937,7 +938,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -937,7 +938,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
if (writing) { if (writing) {
// try to detect concurrent modification // try to detect concurrent modification
// on a best-effort basis // on a best-effort basis
throw DataUtils.newConcurrentModificationException(); throw DataUtils.newConcurrentModificationException(getName());
} }
} }
...@@ -1004,7 +1005,7 @@ public class MVMap<K, V> extends AbstractMap<K, V> ...@@ -1004,7 +1005,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
public MVMap<K, V> openVersion(long version) { public MVMap<K, V> openVersion(long version) {
if (readOnly) { if (readOnly) {
throw DataUtils.newUnsupportedOperationException( throw DataUtils.newUnsupportedOperationException(
"This map is read-only - need to call the method on the writable map"); "This map is read-only; need to call the method on the writable map");
} }
DataUtils.checkArgument(version >= createVersion, DataUtils.checkArgument(version >= createVersion,
"Unknown version {0}; this map was created in version is {1}", "Unknown version {0}; this map was created in version is {1}",
......
...@@ -10,6 +10,7 @@ import java.io.IOException; ...@@ -10,6 +10,7 @@ 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.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;
...@@ -436,6 +437,10 @@ public class MVStore { ...@@ -436,6 +437,10 @@ public class MVStore {
/** /**
* Open the store. * 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() { void open() {
meta = new MVMapConcurrent<String, String>(StringDataType.INSTANCE, StringDataType.INSTANCE); meta = new MVMapConcurrent<String, String>(StringDataType.INSTANCE, StringDataType.INSTANCE);
...@@ -476,11 +481,13 @@ public class MVStore { ...@@ -476,11 +481,13 @@ public class MVStore {
* Try to open the file in read or write mode. * Try to open the file in read or write mode.
* *
* @return if opening the file was successful, and false if the file could * @return if opening the file was successful, and false if the file could
* not be opened in write mode because the write file format it too * not be opened in write mode because the write file format it too
* high (in which case the file can be opened in read-only mode) * high (in which case the file can be opened in read-only mode)
* @throw IllegalStateException if the file could not be opened at all * @throw IllegalStateException if the file could not be opened
* because of an IOException or file format error
*/ */
private boolean openFile() { private boolean openFile() {
IllegalStateException exception;
try { try {
log("file open"); log("file open");
FilePath f = FilePath.get(fileName); FilePath f = FilePath.get(fileName);
...@@ -493,16 +500,19 @@ public class MVStore { ...@@ -493,16 +500,19 @@ public class MVStore {
file = new FilePathCrypt.FileCrypt(fileName, password, file); file = new FilePathCrypt.FileCrypt(fileName, password, file);
} }
file = FilePathCache.wrap(file); file = FilePathCache.wrap(file);
if (readOnly) { try {
fileLock = file.tryLock(0, Long.MAX_VALUE, true); if (readOnly) {
if (fileLock == null) { fileLock = file.tryLock(0, Long.MAX_VALUE, true);
throw new IOException("The file is locked: " + fileName); } else {
} fileLock = file.tryLock();
} else {
fileLock = file.tryLock();
if (fileLock == null) {
throw new IOException("The file is locked: " + fileName);
} }
} 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(); fileSize = file.size();
if (fileSize == 0) { if (fileSize == 0) {
...@@ -521,8 +531,9 @@ public class MVStore { ...@@ -521,8 +531,9 @@ public class MVStore {
int formatRead = x == null ? formatWrite : Integer.parseInt(x); int formatRead = x == null ? formatWrite : Integer.parseInt(x);
if (formatRead > FORMAT_READ) { if (formatRead > FORMAT_READ) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
"The file format {0} is larger than the supported format {1}", DataUtils.ERROR_UNSUPPORTED_FORMAT,
formatRead, FORMAT_READ); "The file format {0} is larger than the supported format {1}",
formatRead, FORMAT_READ);
} }
if (formatWrite > FORMAT_WRITE) { if (formatWrite > FORMAT_WRITE) {
readOnly = true; readOnly = true;
...@@ -533,14 +544,21 @@ public class MVStore { ...@@ -533,14 +544,21 @@ public class MVStore {
readMeta(); readMeta();
} }
} }
} catch (Exception e) { exception = null;
} catch (IOException e) {
exception = DataUtils.newIllegalStateException(
DataUtils.ERROR_READING_FAILED,
"Could not open file {0}", fileName, e);
} catch (IllegalStateException e) {
exception = e;
}
if (exception != null) {
try { try {
closeFile(false); closeFile(false);
} catch (Exception e2) { } catch (Exception e2) {
// ignore // ignore
} }
throw DataUtils.newIllegalStateException( throw exception;
"Could not open file {0}", fileName, e);
} }
return true; return true;
} }
...@@ -603,7 +621,12 @@ public class MVStore { ...@@ -603,7 +621,12 @@ public class MVStore {
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, Constants.UTF8)
.trim(); .trim();
HashMap<String, String> m = DataUtils.parseMap(s); HashMap<String, String> m;
try {
m = DataUtils.parseMap(s);
} catch (IllegalArgumentException e) {
continue;
}
String f = m.remove("fletcher"); String f = m.remove("fletcher");
if (f == null) { if (f == null) {
continue; continue;
...@@ -630,7 +653,8 @@ public class MVStore { ...@@ -630,7 +653,8 @@ public class MVStore {
} }
} }
if (currentVersion < 0) { if (currentVersion < 0) {
throw DataUtils.newIllegalStateException("File header is corrupt"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT, "File header is corrupt: {0}", fileName);
} }
lastStoredVersion = -1; lastStoredVersion = -1;
} }
...@@ -645,8 +669,11 @@ public class MVStore { ...@@ -645,8 +669,11 @@ public class MVStore {
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(Constants.UTF8);
DataUtils.checkArgument(bytes.length <= BLOCK_SIZE, if (bytes.length > BLOCK_SIZE) {
"File header too large: {0}", buff); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_UNSUPPORTED_FORMAT,
"File header too large: {0}", buff);
}
return bytes; return bytes;
} }
...@@ -720,6 +747,7 @@ public class MVStore { ...@@ -720,6 +747,7 @@ public class MVStore {
maps.clear(); maps.clear();
} catch (Exception e) { } catch (Exception e) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Closing failed for file {0}", fileName, e); "Closing failed for file {0}", fileName, e);
} finally { } finally {
file = null; file = null;
...@@ -799,7 +827,8 @@ public class MVStore { ...@@ -799,7 +827,8 @@ public class MVStore {
return currentVersion; return currentVersion;
} }
if (readOnly) { if (readOnly) {
throw DataUtils.newIllegalStateException("This store is read-only"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED, "This store is read-only");
} }
int currentUnsavedPageCount = unsavedPageCount; int currentUnsavedPageCount = unsavedPageCount;
long storeVersion = currentStoreVersion = currentVersion; long storeVersion = currentStoreVersion = currentVersion;
...@@ -893,7 +922,8 @@ public class MVStore { ...@@ -893,7 +922,8 @@ public class MVStore {
if (ASSERT) { if (ASSERT) {
if (freedPages.size() > 0) { if (freedPages.size() > 0) {
throw DataUtils.newIllegalStateException("Temporary freed chunks"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Temporary freed chunks");
} }
} }
...@@ -1002,14 +1032,17 @@ public class MVStore { ...@@ -1002,14 +1032,17 @@ public class MVStore {
c.pageCountLive += f.pageCountLive; c.pageCountLive += f.pageCountLive;
if (c.pageCountLive < 0) { if (c.pageCountLive < 0) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Corrupt page count {0}", c.pageCountLive); "Corrupt page count {0}", c.pageCountLive);
} }
if (c.maxLengthLive < 0) { if (c.maxLengthLive < 0) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Corrupt max length {0}", c.maxLengthLive); "Corrupt max length {0}", c.maxLengthLive);
} }
if (c.pageCount == 0 && c.maxLengthLive > 0) { if (c.pageCount == 0 && c.maxLengthLive > 0) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Corrupt max length {0}", c.maxLengthLive); "Corrupt max length {0}", c.maxLengthLive);
} }
modified.add(c); modified.add(c);
...@@ -1060,6 +1093,7 @@ public class MVStore { ...@@ -1060,6 +1093,7 @@ public class MVStore {
file.truncate(used); file.truncate(used);
} catch (IOException e) { } catch (IOException e) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_WRITING_FAILED,
"Could not truncate file {0} to size {1}", "Could not truncate file {0} to size {1}",
fileName, used, e); fileName, used, e);
} }
...@@ -1259,6 +1293,7 @@ public class MVStore { ...@@ -1259,6 +1293,7 @@ public class MVStore {
Chunk c = getChunk(pos); Chunk c = getChunk(pos);
if (c == null) { if (c == null) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"Chunk {0} not found", "Chunk {0} not found",
DataUtils.getPageChunkId(pos)); DataUtils.getPageChunkId(pos));
} }
...@@ -1682,7 +1717,8 @@ public class MVStore { ...@@ -1682,7 +1717,8 @@ public class MVStore {
private void checkOpen() { private void checkOpen() {
if (closed) { if (closed) {
throw DataUtils.newIllegalStateException("This store is closed"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_CLOSED, "This store is closed");
} }
} }
...@@ -1741,6 +1777,15 @@ public class MVStore { ...@@ -1741,6 +1777,15 @@ public class MVStore {
store(true); store(true);
} }
/**
* Set the read cache size in MB.
*
* @param mb the cache size in MB.
*/
public void setCacheSize(long mb) {
cache.setMaxMemory(mb * 1024 * 1024);
}
public boolean isReadOnly() { public boolean isReadOnly() {
return readOnly; return readOnly;
} }
......
...@@ -429,6 +429,7 @@ public class Page { ...@@ -429,6 +429,7 @@ public class Page {
} }
if (check != totalCount) { if (check != totalCount) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL,
"Expected: {0} got: {1}", check, totalCount); "Expected: {0} got: {1}", check, totalCount);
} }
} }
...@@ -694,6 +695,7 @@ public class Page { ...@@ -694,6 +695,7 @@ public class Page {
int pageLength = buff.getInt(); int pageLength = buff.getInt();
if (pageLength > maxLength) { if (pageLength > maxLength) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"File corrupted, expected length =< {0}, got {1}", "File corrupted, expected length =< {0}, got {1}",
maxLength, pageLength); maxLength, pageLength);
} }
...@@ -701,6 +703,7 @@ public class Page { ...@@ -701,6 +703,7 @@ public class Page {
int mapId = DataUtils.readVarInt(buff); int mapId = DataUtils.readVarInt(buff);
if (mapId != map.getId()) { if (mapId != map.getId()) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"File corrupted, expected map id {0}, got {1}", "File corrupted, expected map id {0}, got {1}",
map.getId(), mapId); map.getId(), mapId);
} }
...@@ -709,6 +712,7 @@ public class Page { ...@@ -709,6 +712,7 @@ public class Page {
^ DataUtils.getCheckValue(pageLength); ^ DataUtils.getCheckValue(pageLength);
if (check != (short) checkTest) { if (check != (short) checkTest) {
throw DataUtils.newIllegalStateException( throw DataUtils.newIllegalStateException(
DataUtils.ERROR_FILE_CORRUPT,
"File corrupted, expected check value {0}, got {1}", "File corrupted, expected check value {0}, got {1}",
checkTest, check); checkTest, check);
} }
...@@ -818,7 +822,8 @@ public class Page { ...@@ -818,7 +822,8 @@ public class Page {
^ DataUtils.getCheckValue(pageLength); ^ DataUtils.getCheckValue(pageLength);
buff.putShort(start + 4, (short) check); buff.putShort(start + 4, (short) check);
if (pos != 0) { if (pos != 0) {
throw DataUtils.newIllegalStateException("Page already stored"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Page already stored");
} }
pos = DataUtils.getPagePos(chunkId, start, pageLength, type); pos = DataUtils.getPagePos(chunkId, start, pageLength, type);
long max = DataUtils.getPageMaxLength(pos); long max = DataUtils.getPageMaxLength(pos);
...@@ -901,7 +906,8 @@ public class Page { ...@@ -901,7 +906,8 @@ public class Page {
public int getMemory() { public int getMemory() {
if (MVStore.ASSERT) { if (MVStore.ASSERT) {
if (memory != calculateMemory()) { if (memory != calculateMemory()) {
throw DataUtils.newIllegalStateException("Memory calculation error"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_INTERNAL, "Memory calculation error");
} }
} }
return memory; return memory;
......
...@@ -11,13 +11,16 @@ import java.util.List; ...@@ -11,13 +11,16 @@ import java.util.List;
import org.h2.api.TableEngine; import org.h2.api.TableEngine;
import org.h2.command.ddl.CreateTableData; import org.h2.command.ddl.CreateTableData;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVStore; import org.h2.mvstore.MVStore;
import org.h2.mvstore.db.TransactionStore.Transaction; import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.store.InDoubtTransaction; import org.h2.store.InDoubtTransaction;
import org.h2.store.fs.FileUtils;
import org.h2.table.RegularTable; import org.h2.table.RegularTable;
import org.h2.table.TableBase; import org.h2.table.TableBase;
import org.h2.util.New; import org.h2.util.New;
...@@ -26,13 +29,14 @@ import org.h2.util.New; ...@@ -26,13 +29,14 @@ import org.h2.util.New;
* A table engine that internally uses the MVStore. * A table engine that internally uses the MVStore.
*/ */
public class MVTableEngine implements TableEngine { public class MVTableEngine implements TableEngine {
@Override /**
public TableBase createTable(CreateTableData data) { * Initialize the MVStore.
Database db = data.session.getDatabase(); *
if (!data.persistData || (data.temporary && !data.persistIndexes)) { * @param db the database
return new RegularTable(data); * @return the store
} */
public static Store init(Database db) {
Store store = db.getMvStore(); Store store = db.getMvStore();
if (store == null) { if (store == null) {
byte[] key = db.getFilePasswordHash(); byte[] key = db.getFilePasswordHash();
...@@ -41,9 +45,19 @@ public class MVTableEngine implements TableEngine { ...@@ -41,9 +45,19 @@ public class MVTableEngine implements TableEngine {
if (dbPath == null) { if (dbPath == null) {
store = new Store(db, builder.open()); store = new Store(db, builder.open());
} else { } else {
builder.fileName(dbPath + Constants.SUFFIX_MV_FILE); String fileName = dbPath + Constants.SUFFIX_MV_FILE;
builder.fileName(fileName);
if (db.isReadOnly()) { if (db.isReadOnly()) {
builder.readOnly(); builder.readOnly();
} else {
// possibly create the directory
boolean exists = FileUtils.exists(fileName);
if (exists && !FileUtils.canWrite(fileName)) {
// read only
} else {
String dir = FileUtils.getParent(fileName);
FileUtils.createDirectories(dir);
}
} }
if (key != null) { if (key != null) {
char[] password = new char[key.length]; char[] password = new char[key.length];
...@@ -52,10 +66,32 @@ public class MVTableEngine implements TableEngine { ...@@ -52,10 +66,32 @@ public class MVTableEngine implements TableEngine {
} }
builder.encryptionKey(password); builder.encryptionKey(password);
} }
store = new Store(db, builder.open()); try {
store = new Store(db, builder.open());
} catch (IllegalStateException e) {
int errorCode = DataUtils.getErrorCode(e.getMessage());
if (errorCode == DataUtils.ERROR_FILE_CORRUPT) {
if (key != null) {
throw DbException.get(ErrorCode.FILE_ENCRYPTION_ERROR_1, fileName);
}
} else if (errorCode == DataUtils.ERROR_FILE_LOCKED) {
throw DbException.get(ErrorCode.DATABASE_ALREADY_OPEN_1, fileName);
}
throw DbException.get(ErrorCode.FILE_CORRUPTED_1, fileName);
}
} }
db.setMvStore(store); db.setMvStore(store);
} }
return store;
}
@Override
public TableBase createTable(CreateTableData data) {
Database db = data.session.getDatabase();
if (!data.persistData || (data.temporary && !data.persistIndexes)) {
return new RegularTable(data);
}
Store store = init(db);
MVTable table = new MVTable(data, store); MVTable table = new MVTable(data, store);
store.openTables.add(table); store.openTables.add(table);
table.init(data.session); table.init(data.session);
...@@ -188,6 +224,10 @@ public class MVTableEngine implements TableEngine { ...@@ -188,6 +224,10 @@ public class MVTableEngine implements TableEngine {
return result; return result;
} }
public void setCacheSize(int kb) {
store.setCacheSize(kb * 1024);
}
} }
/** /**
......
...@@ -111,7 +111,9 @@ public class TransactionStore { ...@@ -111,7 +111,9 @@ public class TransactionStore {
} }
Long lastKey = preparedTransactions.lastKey(); Long lastKey = preparedTransactions.lastKey();
if (lastKey != null && lastKey.longValue() > lastTransactionId) { if (lastKey != null && lastKey.longValue() > lastTransactionId) {
throw DataUtils.newIllegalStateException("Last transaction not stored"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TRANSACTION_CORRUPT,
"Last transaction not stored");
} }
if (undoLog.size() > 0) { if (undoLog.size() > 0) {
long[] key = undoLog.firstKey(); long[] key = undoLog.firstKey();
...@@ -616,7 +618,8 @@ public class TransactionStore { ...@@ -616,7 +618,8 @@ public class TransactionStore {
*/ */
void checkNotClosed() { void checkNotClosed() {
if (status == STATUS_CLOSED) { if (status == STATUS_CLOSED) {
throw DataUtils.newIllegalStateException("Transaction is closed"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_CLOSED, "Transaction is closed");
} }
} }
...@@ -749,14 +752,16 @@ public class TransactionStore { ...@@ -749,14 +752,16 @@ public class TransactionStore {
// wait until it is committed, or until the lock timeout // wait until it is committed, or until the lock timeout
long timeout = transaction.store.lockTimeout; long timeout = transaction.store.lockTimeout;
if (timeout == 0) { if (timeout == 0) {
throw DataUtils.newIllegalStateException("Lock timeout"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TRANSACTION_LOCK_TIMEOUT, "Lock timeout");
} }
if (start == 0) { if (start == 0) {
start = System.currentTimeMillis(); start = System.currentTimeMillis();
} else { } else {
long t = System.currentTimeMillis() - start; long t = System.currentTimeMillis() - start;
if (t > timeout) { if (t > timeout) {
throw DataUtils.newIllegalStateException("Lock timeout"); throw DataUtils.newIllegalStateException(
DataUtils.ERROR_TRANSACTION_LOCK_TIMEOUT, "Lock timeout");
} }
// TODO use wait/notify instead, or remove the feature // TODO use wait/notify instead, or remove the feature
try { try {
......
...@@ -8,8 +8,8 @@ package org.h2.mvstore.rtree; ...@@ -8,8 +8,8 @@ package org.h2.mvstore.rtree;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import org.h2.mvstore.Cursor;
import org.h2.mvstore.CursorPos; import org.h2.mvstore.CursorPos;
import org.h2.mvstore.DataUtils;
import org.h2.mvstore.MVMap; import org.h2.mvstore.MVMap;
import org.h2.mvstore.Page; import org.h2.mvstore.Page;
import org.h2.mvstore.type.DataType; import org.h2.mvstore.type.DataType;
...@@ -63,7 +63,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> { ...@@ -63,7 +63,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
*/ */
public Iterator<SpatialKey> findIntersectingKeys(SpatialKey x) { public Iterator<SpatialKey> findIntersectingKeys(SpatialKey x) {
checkOpen(); checkOpen();
return new RTreeCursor(this, root, x) { return new RTreeCursor(root, x) {
@Override @Override
protected boolean check(boolean leaf, SpatialKey key, SpatialKey test) { protected boolean check(boolean leaf, SpatialKey key, SpatialKey test) {
return keyType.isOverlap(key, test); return keyType.isOverlap(key, test);
...@@ -79,7 +79,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> { ...@@ -79,7 +79,7 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
*/ */
public Iterator<SpatialKey> findContainedKeys(SpatialKey x) { public Iterator<SpatialKey> findContainedKeys(SpatialKey x) {
checkOpen(); checkOpen();
return new RTreeCursor(this, root, x) { return new RTreeCursor(root, x) {
@Override @Override
protected boolean check(boolean leaf, SpatialKey key, SpatialKey test) { protected boolean check(boolean leaf, SpatialKey key, SpatialKey test) {
if (leaf) { if (leaf) {
...@@ -479,78 +479,223 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> { ...@@ -479,78 +479,223 @@ public class MVRTreeMap<V> extends MVMap<SpatialKey, V> {
/** /**
* A cursor to iterate over a subset of the keys. * A cursor to iterate over a subset of the keys.
*/ */
static class RTreeCursor extends Cursor<SpatialKey> { static class RTreeCursor implements Iterator<SpatialKey> {
protected RTreeCursor(MVRTreeMap<?> map, Page root, SpatialKey from) { private final SpatialKey filter;
super(map, root, from); private CursorPos pos;
private SpatialKey current;
private final Page root;
private boolean initialized;
protected RTreeCursor(Page root, SpatialKey filter) {
this.root = root;
this.filter = filter;
} }
@Override @Override
public void skip(long n) { public boolean hasNext() {
if (!hasNext()) { if (!initialized) {
return; // init
} pos = new CursorPos(root, 0, null);
while (n-- > 0) {
fetchNext(); fetchNext();
initialized = true;
} }
return current != null;
} }
/** public void skip(long n) {
* Check a given key. while (hasNext() && n-- > 0) {
* fetchNext();
* @param leaf if the key is from a leaf page }
* @param key the stored key
* @param test the user-supplied test key
* @return true if there is a match
*/
protected boolean check(boolean leaf, SpatialKey key, SpatialKey test) {
return true;
} }
@Override @Override
protected void min(Page p, SpatialKey x) { public SpatialKey next() {
while (true) { if (!hasNext()) {
if (p.isLeaf()) { return null;
pos = new CursorPos(p, 0, pos);
return;
}
boolean found = false;
for (int i = 0; i < p.getKeyCount(); i++) {
if (check(false, (SpatialKey) p.getKey(i), x)) {
pos = new CursorPos(p, i + 1, pos);
p = p.getChildPage(i);
found = true;
break;
}
}
if (!found) {
break;
}
} }
SpatialKey c = current;
fetchNext(); fetchNext();
return c;
} }
@Override @Override
public void remove() {
throw DataUtils.newUnsupportedOperationException(
"Removing is not supported");
}
/**
* Fetch the next entry if there is one.
*/
protected void fetchNext() { protected void fetchNext() {
while (pos != null) { while (pos != null) {
while (pos.index < pos.page.getKeyCount()) { Page p = pos.page;
SpatialKey k = (SpatialKey) pos.page.getKey(pos.index++); if (p.isLeaf()) {
if (check(true, k, from)) { while (pos.index < p.getKeyCount()) {
current = k; SpatialKey c = (SpatialKey) p.getKey(pos.index++);
return; if (filter == null || check(true, c, filter)) {
current = c;
return;
}
}
} else {
boolean found = false;
while (pos.index < p.getKeyCount()) {
int index = pos.index++;
SpatialKey c = (SpatialKey) p.getKey(index);
if (filter == null || check(false, c, filter)) {
Page child = pos.page.getChildPage(index);
pos = new CursorPos(child, 0, pos);
found = true;
break;
}
}
if (found) {
continue;
} }
} }
// parent
pos = pos.parent; pos = pos.parent;
if (pos == null) {
break;
}
MVRTreeMap<?> m = (MVRTreeMap<?>) map;
if (pos.index < m.getChildPageCount(pos.page)) {
min(pos.page.getChildPage(pos.index++), from);
}
} }
current = null; current = null;
} }
// if(pos==null || pos.page != p) {
// pos = new CursorPos(p, i + 1, pos);
// } else {
// pos.index = i + 1;
// }
// p = p.getChildPage(i);
// found = true;
// break;
// }
// }
//
// if (pos.index < pos.page.getKeyCount()) {
// pos.index++;
// }
// current = (SpatialKey) pos.page.getKey(pos.index++);
// return;
// }
// pos = pos.parent;
// if (pos == null) {
// break;
// }
// if (pos.index < m.getChildPageCount(pos.page)) {
// min(pos.page.getChildPage(pos.index++), null);
// }
// }
// current = null;
// }
/**
* Check a given key.
*
* @param leaf if the key is from a leaf page
* @param key the stored key
* @param test the user-supplied test key
* @return true if there is a match
*/
protected boolean check(boolean leaf, SpatialKey key, SpatialKey test) {
return true;
}
//// @Override
// protected void min2(Page p, SpatialKey x) {
// while (true) {
// if (p.isLeaf()) {
// pos = new CursorPos(p, 0, pos);
// return;
// } else if(pos != null && pos.page != p ) {
// pos = new CursorPos(p, 0, pos);
// }
// boolean found = false;
// int firstChildIndex = 0;
// if(pos!=null && pos.page == p) {
// firstChildIndex = pos.index;
// }
// for (int i = firstChildIndex; i < p.getKeyCount(); i++) {
// if (check(false, (SpatialKey) p.getKey(i), x)) {
// if(pos==null || pos.page != p) {
// pos = new CursorPos(p, i + 1, pos);
// } else {
// pos.index = i + 1;
// }
// p = p.getChildPage(i);
// found = true;
// break;
// }
// }
// if (!found) {
// if(pos==null || pos.page.isLeaf()) {
// break;
// } else {
// pos = pos.parent;
// if(pos!=null) {
// p = pos.page;
// } else {
// //No more entries
// break;
// }
// }
// }
// }
// }
//
// @Override
// protected void min(Page p, SpatialKey x) {
// // x
// }
//
// protected void min3(Page p, SpatialKey x) {
// while (true) {
// if (p.isLeaf()) {
// pos = new CursorPos(p, 0, pos);
// return;
// }
// boolean found = false;
// for (int i = 0; i < p.getKeyCount(); i++) {
// if (check(false, (SpatialKey) p.getKey(i), x)) {
// pos = new CursorPos(p, i + 1, pos);
// p = p.getChildPage(i);
// found = true;
// break;
// }
// }
// if (!found) {
// if (pos == null) {
// return;
// }
// pos = pos.parent;
// break;
// }
// }
// }
//
// @Override
// protected void fetchNext() {
// while (pos != null) {
// while (pos.index < pos.page.getKeyCount()) {
// SpatialKey k = (SpatialKey) pos.page.getKey(pos.index++);
// if (check(true, k, from)) {
// current = k;
// return;
// }
// }
// pos = pos.parent;
// if (pos == null) {
// break;
// }
// MVRTreeMap<?> m = (MVRTreeMap<?>) map;
// if (pos.index < m.getChildPageCount(pos.page)) {
// min(pos.page.getChildPage(pos.index++), from);
// }
// }
// current = null;
// }
} }
@Override @Override
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论