提交 32bae90e authored 作者: Thomas Mueller's avatar Thomas Mueller

A persistent multi-version map (work in progress).

上级 6ab864dc
......@@ -8,7 +8,7 @@ package org.h2.test.store;
import java.nio.ByteBuffer;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.DataTypeFactory;
import org.h2.dev.store.btree.MapFactory;
import org.h2.dev.store.btree.DataUtils;
import org.h2.util.StringUtils;
......@@ -115,7 +115,7 @@ public class RowType implements DataType {
* @param factory the data type factory
* @return the row type
*/
static RowType fromString(String t, DataTypeFactory factory) {
static RowType fromString(String t, MapFactory factory) {
if (!t.startsWith("r(") || !t.endsWith(")")) {
throw new RuntimeException("Unknown type: " + t);
}
......@@ -123,7 +123,7 @@ public class RowType implements DataType {
String[] array = StringUtils.arraySplit(t, ',', false);
DataType[] types = new DataType[array.length];
for (int i = 0; i < array.length; i++) {
types[i] = factory.fromString(array[i]);
types[i] = factory.buildDataType(array[i]);
}
return new RowType(types);
}
......
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.store;
import org.h2.dev.store.btree.BtreeMap;
import org.h2.dev.store.btree.BtreeMapStore;
import org.h2.dev.store.btree.DataType;
/**
* A stored r-tree.
*
* @param <K> the key class
* @param <V> the value class
*/
public class RtreeMap<K, V> extends BtreeMap<K, V> {
RtreeMap(BtreeMapStore store, int id, String name, DataType keyType,
DataType valueType, long createVersion) {
super(store, id, name, keyType, valueType, createVersion);
}
}
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.store;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Set;
import org.h2.dev.store.btree.BtreeMap;
import org.h2.dev.store.btree.BtreeMapStore;
import org.h2.dev.store.btree.DataType;
/**
* A custom map returning the values 1 .. 10.
*
* @param <K> the key type
* @param <V> the key type
*/
public class SequenceMap<K, V> extends BtreeMap<K, V> {
int min = 1, max = 10;
SequenceMap(BtreeMapStore store, int id, String name, DataType keyType,
DataType valueType, long createVersion) {
super(store, id, name, keyType, valueType, createVersion);
setReadOnly(true);
}
public Set<K> keySet() {
return new AbstractSet<K>() {
@Override
public Iterator<K> iterator() {
return new Iterator<K>() {
int x = min;
@Override
public boolean hasNext() {
return x <= max;
}
@SuppressWarnings("unchecked")
@Override
public K next() {
return (K) Integer.valueOf(x++);
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public int size() {
return max - min + 1;
}
};
}
}
......@@ -29,6 +29,7 @@ public class TestBtreeMapStore extends TestBase {
}
public void test() {
testCustomMapType();
testTruncateFile();
testFastDelete();
testRollbackInMemory();
......@@ -44,6 +45,20 @@ public class TestBtreeMapStore extends TestBase {
testSimple();
}
private void testCustomMapType() {
String fileName = getBaseDir() + "/testMeta.h3";
FileUtils.delete(fileName);
BtreeMapStore s;
s = openStore(fileName);
SequenceMap<Integer, String> seq = s.openMap("data", "s", "i", "").cast();
StringBuilder buff = new StringBuilder();
for (int x : seq.keySet()) {
buff.append(x).append(';');
}
assertEquals("1;2;3;4;5;6;7;8;9;10;", buff.toString());
s.close();
}
private void testTruncateFile() {
String fileName = getBaseDir() + "/testMeta.h3";
FileUtils.delete(fileName);
......@@ -233,11 +248,11 @@ public class TestBtreeMapStore extends TestBase {
data.put("1", "Hello");
data.put("2", "World");
s.store();
assertEquals("1/1//", m.get("map.data"));
assertEquals("1/1///", m.get("map.data"));
assertTrue(m.containsKey("chunk.1"));
data.put("1", "Hallo");
s.store();
assertEquals("1/1//", m.get("map.data"));
assertEquals("1/1///", m.get("map.data"));
assertTrue(m.get("root.1").length() > 0);
assertTrue(m.containsKey("chunk.1"));
assertTrue(m.containsKey("chunk.2"));
......@@ -255,8 +270,7 @@ public class TestBtreeMapStore extends TestBase {
BtreeMapStore s = openStore(fileName);
// s.setCompressor(null);
s.setMaxPageSize(40);
RowType rowType = RowType.fromString("r(i,,)", new TestTypeFactory());
BtreeMap<Integer, Object[]> m = s.openMap("data", new IntegerType(), rowType);
BtreeMap<Integer, Object[]> m = s.openMap("data", "", "i", "r(i,,)");
int i = 0;
// long t = System.currentTimeMillis();
for (; i < len;) {
......@@ -540,7 +554,7 @@ public class TestBtreeMapStore extends TestBase {
}
private static BtreeMapStore openStore(String fileName) {
BtreeMapStore store = BtreeMapStore.open(fileName, new TestTypeFactory());
BtreeMapStore store = BtreeMapStore.open(fileName, new TestMapFactory());
store.setMaxPageSize(10);
return store;
}
......
......@@ -5,16 +5,29 @@
*/
package org.h2.test.store;
import org.h2.dev.store.btree.BtreeMap;
import org.h2.dev.store.btree.BtreeMapStore;
import org.h2.dev.store.btree.DataType;
import org.h2.dev.store.btree.DataTypeFactory;
import org.h2.dev.store.btree.MapFactory;
import org.h2.dev.store.btree.StringType;
/**
* A data type factory.
*/
public class TestTypeFactory implements DataTypeFactory {
public class TestMapFactory implements MapFactory {
public DataType fromString(String s) {
@Override
public <K, V> BtreeMap<K, V> buildMap(String mapType, BtreeMapStore store,
int id, String name, DataType keyType, DataType valueType,
long createVersion) {
if (mapType.equals("s")) {
return new SequenceMap<K, V>(store, id, name, keyType, valueType, createVersion);
}
throw new RuntimeException("Unsupported map type " + mapType);
}
@Override
public DataType buildDataType(String s) {
if (s.length() == 0) {
return new StringType();
}
......@@ -27,9 +40,10 @@ public class TestTypeFactory implements DataTypeFactory {
throw new RuntimeException("Unknown data type " + s);
}
public DataType getDataType(Class<?> objectClass) {
@Override
public String getDataType(Class<?> objectClass) {
if (objectClass == Integer.class) {
return new IntegerType();
return "i";
}
throw new RuntimeException("Unsupported object class " + objectClass.toString());
}
......
......@@ -7,6 +7,7 @@
package org.h2.dev.store.btree;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
......@@ -19,6 +20,11 @@ import java.util.TreeMap;
*/
public class BtreeMap<K, V> {
static final IllegalArgumentException KEY_NOT_FOUND = new IllegalArgumentException(
"Key not found");
static final IllegalArgumentException KEY_ALREADY_EXISTS = new IllegalArgumentException(
"Key already exists");
private final int id;
private final String name;
private final DataType keyType;
......@@ -34,7 +40,8 @@ public class BtreeMap<K, V> {
private Page root;
private boolean readOnly;
BtreeMap(BtreeMapStore store, int id, String name, DataType keyType, DataType valueType, long createVersion) {
protected BtreeMap(BtreeMapStore store, int id, String name,
DataType keyType, DataType valueType, long createVersion) {
this.store = store;
this.id = id;
this.name = name;
......@@ -53,13 +60,125 @@ public class BtreeMap<K, V> {
checkWrite();
Page oldRoot = root;
if (containsKey(key)) {
root = Page.set(this, root, store.getCurrentVersion(), key, data);
root = set(root, store.getCurrentVersion(), key, data);
} else {
root = Page.add(this, root, store.getCurrentVersion(), key, data);
root = add(root, store.getCurrentVersion(), key, data);
}
markChanged(oldRoot);
}
/**
* Update a value for an existing key.
*
* @param map the map
* @param p the page (may not be null)
* @param writeVersion the write version
* @param key the key
* @param value the value
* @return the root page
* @throws InvalidArgumentException if this key does not exist (without
* stack trace)
*/
private Page set(Page p, long writeVersion, Object key,
Object value) {
if (p == null) {
throw KEY_NOT_FOUND;
}
int index = p.binarySearch(key);
if (p.isLeaf()) {
if (index < 0) {
throw KEY_NOT_FOUND;
}
p = p.copyOnWrite(writeVersion);
p.setValue(index, value);
return p;
}
// it is a node
if (index < 0) {
index = -index - 1;
} else {
index++;
}
Page c = p.getChildPage(index);
Page c2 = set(c, writeVersion, key, value);
if (c != c2) {
p = p.copyOnWrite(writeVersion);
p.setChild(index, c2.getPos(), c2.getPos());
}
return p;
}
/**
* Add a new key-value pair.
*
* @param map the map
* @param p the page (may be null)
* @param writeVersion the write version
* @param key the key
* @param value the value
* @return the root page
* @throws InvalidArgumentException if this key already exists (without
* stack trace)
*/
private Page add(Page p, long writeVersion, Object key,
Object value) {
if (p == null) {
Object[] keys = { key };
Object[] values = { value };
p = Page.create(this, writeVersion, keys, values, null, null, 1);
return p;
}
if (p.getKeyCount() >= store.getMaxPageSize()) {
// only possible if this is the root,
// otherwise we would have split earlier
p = p.copyOnWrite(writeVersion);
int at = p.getKeyCount() / 2;
long totalSize = p.getTotalSize();
Object k = p.getKey(at);
Page split = p.split(at);
Object[] keys = { k };
long[] children = { p.getPos(), split.getPos() };
long[] childrenSize = { p.getTotalSize(), split.getTotalSize() };
p = Page.create(this, writeVersion, keys, null, children, childrenSize,
totalSize);
// now p is a node; insert continues
} else if (p.isLeaf()) {
int index = p.binarySearch(key);
if (index >= 0) {
throw KEY_ALREADY_EXISTS;
}
index = -index - 1;
p = p.copyOnWrite(writeVersion);
p.insert(index, key, value, 0, 0);
return p;
}
// p is a node
int index = p.binarySearch(key);
if (index < 0) {
index = -index - 1;
} else {
index++;
}
Page c = p.getChildPage(index);
if (c.getKeyCount() >= store.getMaxPageSize()) {
// split on the way down
c = c.copyOnWrite(writeVersion);
int at = c.getKeyCount() / 2;
Object k = c.getKey(at);
Page split = c.split(at);
p = p.copyOnWrite(writeVersion);
p.setChild(index, c.getPos(), c.getTotalSize());
p.insert(index, k, null, split.getPos(), split.getTotalSize());
// now we are not sure where to add
return add(p, writeVersion, key, value);
}
Page c2 = add(c, writeVersion, key, value);
p = p.copyOnWrite(writeVersion);
// the child might be the same, but not the size
p.setChild(index, c2.getPos(), c2.getTotalSize());
return p;
}
/**
* Get a value.
*
......@@ -72,8 +191,102 @@ public class BtreeMap<K, V> {
if (root == null) {
return null;
}
return (V) root.find(key);
return (V) binarySearch(root, key);
}
/**
* Go to the first element for the given key.
*
* @param p the current page
* @param parents the stack of parent page positions
* @param key the key
*/
void min(Page p, ArrayList<CursorPos> parents, Object key) {
while (p != null) {
if (!p.isLeaf()) {
int x = key == null ? -1 : p.binarySearch(key);
if (x < 0) {
x = -x - 1;
} else {
x++;
}
CursorPos c = new CursorPos();
c.page = p;
c.index = x;
parents.add(c);
p = p.getChildPage(x);
} else {
int x = key == null ? 0 : p.binarySearch(key);
if (x < 0) {
x = -x - 1;
}
CursorPos c = new CursorPos();
c.page = p;
c.index = x;
parents.add(c);
return;
}
}
}
/**
* Get the next key.
*
* @param parents the stack of parent page positions
* @return the next key
*/
Object nextKey(ArrayList<CursorPos> parents) {
if (parents.size() == 0) {
return null;
}
while (true) {
// TODO performance: avoid remove/add pairs if possible
CursorPos p = parents.remove(parents.size() - 1);
int index = p.index++;
if (index < p.page.getKeyCount()) {
parents.add(p);
return p.page.getKey(index);
}
while (true) {
if (parents.size() == 0) {
return null;
}
p = parents.remove(parents.size() - 1);
index = ++p.index;
if (index <= p.page.getKeyCount()) {
parents.add(p);
Page x = p.page;
x = x.getChildPage(index);
min(x, parents, null);
break;
}
}
}
}
/**
* Get the value for the given key, or null if not found.
*
* @param key the key
* @return the value or null
*/
private Object binarySearch(Page p, Object key) {
int x = p.binarySearch(key);
if (!p.isLeaf()) {
if (x < 0) {
x = -x - 1;
} else {
x++;
}
p = p.getChildPage(x);
return binarySearch(p, key);
}
if (x >= 0) {
return p.getValue(x);
}
return null;
}
public boolean containsKey(Object key) {
return get(key) != null;
......@@ -89,7 +302,31 @@ public class BtreeMap<K, V> {
if (root == null) {
return null;
}
return root.findPage(key);
return binarySearchPage(root, key);
}
/**
* Get the value for the given key, or null if not found.
*
* @param p the parent page
* @param key the key
* @return the page or null
*/
private Page binarySearchPage(Page p, Object key) {
int x = p.binarySearch(key);
if (!p.isLeaf()) {
if (x < 0) {
x = -x - 1;
} else {
x++;
}
p = p.getChildPage(x);
return binarySearchPage(p, key);
}
if (x >= 0) {
return p;
}
return null;
}
/**
......@@ -137,11 +374,61 @@ public class BtreeMap<K, V> {
checkWrite();
if (containsKey(key)) {
Page oldRoot = root;
root = Page.removeExisting(root, store.getCurrentVersion(), key);
root = removeExisting(root, store.getCurrentVersion(), key);
markChanged(oldRoot);
}
}
/**
* Remove an existing key-value pair.
*
* @param p the page (may not be null)
* @param writeVersion the write version
* @param key the key
* @return the new root page (null if empty)
* @throws InvalidArgumentException if not found (without stack trace)
*/
private Page removeExisting(Page p, long writeVersion, Object key) {
if (p == null) {
throw KEY_NOT_FOUND;
}
int index = p.binarySearch(key);
if (p.isLeaf()) {
if (index >= 0) {
if (p.getKeyCount() == 1) {
store.removePage(p.getPos());
return null;
}
p = p.copyOnWrite(writeVersion);
p.remove(index);
} else {
throw KEY_NOT_FOUND;
}
return p;
}
// node
if (index < 0) {
index = -index - 1;
} else {
index++;
}
Page c = p.getChildPage(index);
Page c2 = removeExisting(c, writeVersion, key);
p = p.copyOnWrite(writeVersion);
if (c2 == null) {
// this child was deleted
p.remove(index);
if (p.getKeyCount() == 0) {
store.removePage(p.getPos());
p = p.getChildPage(0);
}
} else {
p.setChild(index, c2.getPos(), c2.getTotalSize());
}
return p;
}
private void markChanged(Page oldRoot) {
if (oldRoot != root) {
long v = store.getCurrentVersion();
......@@ -212,16 +499,17 @@ public class BtreeMap<K, V> {
*/
public Iterator<K> keyIterator(K from) {
checkOpen();
return new Cursor<K>(root, from);
return new Cursor<K, V>(this, root, from);
}
public Set<K> keySet() {
checkOpen();
final Page root = this.root;
return new AbstractSet<K>() {
@Override
public Iterator<K> iterator() {
return new Cursor<K>(getRoot(), null);
return new Cursor<K, V>(BtreeMap.this, root, null);
}
@Override
......@@ -285,7 +573,7 @@ public class BtreeMap<K, V> {
}
}
void stored() {
void revertTemp() {
oldRoots.clear();
}
......@@ -339,8 +627,13 @@ public class BtreeMap<K, V> {
return this == o;
}
long getCreatedVersion() {
long getCreateVersion() {
return createVersion;
}
@SuppressWarnings("unchecked")
public <M> M cast() {
return (M) this;
}
}
......@@ -38,7 +38,7 @@ header:
blockSize=4096
TODO:
- support custom map types (page types); pager for r-tree, kd-tree
- support custom map types: b-tree, r-tree
- ability to diff / merge versions
- map.getVersion and opening old maps read-only
- limited support for writing to old versions (branches)
......@@ -55,6 +55,7 @@ TODO:
- check what happens on concurrent reads and 1 write; multiple writes
- support large binaries
- support stores that span multiple files (chunks stored in other files)
- triggers
*/
......@@ -68,7 +69,7 @@ public class BtreeMapStore {
private static final StringType STRING_TYPE = new StringType();
private final String fileName;
private final DataTypeFactory typeFactory;
private final MapFactory mapFactory;
private int readCacheSize = 2 * 1024 * 1024;
......@@ -110,9 +111,9 @@ public class BtreeMapStore {
private int readCount;
private int writeCount;
private BtreeMapStore(String fileName, DataTypeFactory typeFactory) {
private BtreeMapStore(String fileName, MapFactory mapFactory) {
this.fileName = fileName;
this.typeFactory = typeFactory;
this.mapFactory = mapFactory;
}
/**
......@@ -129,11 +130,11 @@ public class BtreeMapStore {
* Open a tree store.
*
* @param fileName the file name
* @param typeFactory the type factory
* @param mapFactory the map factory
* @return the store
*/
public static BtreeMapStore open(String fileName, DataTypeFactory typeFactory) {
BtreeMapStore s = new BtreeMapStore(fileName, typeFactory);
public static BtreeMapStore open(String fileName, MapFactory mapFactory) {
BtreeMapStore s = new BtreeMapStore(fileName, mapFactory);
s.open();
return s;
}
......@@ -144,12 +145,13 @@ public class BtreeMapStore {
* @param <K> the key type
* @param <V> the value type
* @param name the name of the map
* @param mapType the map type
* @param keyType the key type
* @param valueType the value type
* @return the map
*/
public <K, V> BtreeMap<K, V> openMap(String name, DataType keyType, DataType valueType) {
@SuppressWarnings("unchecked")
public <K, V> BtreeMap<K, V> openMap(String name, String mapType, String keyType, String valueType) {
BtreeMap<K, V> m = (BtreeMap<K, V>) maps.get(name);
if (m == null) {
String identifier = meta.get("map." + name);
......@@ -158,21 +160,28 @@ public class BtreeMapStore {
long createVersion;
if (identifier == null) {
id = ++lastMapId;
String types = id + "/" + currentVersion + "/" + keyType.asString() + "/" + valueType.asString();
createVersion = currentVersion;
String types = id + "/" + createVersion + "/" + mapType + "/" + keyType + "/" + valueType;
meta.put("map." + name, types);
root = 0;
createVersion = currentVersion;
} else {
String types = meta.get("map." + name);
String[] idTypeList = StringUtils.arraySplit(types, '/', false);
id = Integer.parseInt(idTypeList[0]);
createVersion = Long.parseLong(idTypeList[1]);
keyType = getDataType(idTypeList[2]);
valueType = getDataType(idTypeList[3]);
mapType = idTypeList[2];
keyType = idTypeList[3];
valueType = idTypeList[4];
String r = meta.get("root." + id);
root = r == null ? 0 : Long.parseLong(r);
}
m = new BtreeMap<K, V>(this, id, name, keyType, valueType, createVersion);
DataType k = buildDataType(keyType);
DataType v = buildDataType(valueType);
if (mapType.equals("")) {
m = new BtreeMap<K, V>(this, id, name, k, v, createVersion);
} else {
m = getMapFactory().buildMap(mapType, this, id, name, k, v, createVersion);
}
maps.put(name, m);
m.setRootPos(root);
}
......@@ -223,9 +232,11 @@ public class BtreeMapStore {
* @return the map
*/
public <K, V> BtreeMap<K, V> openMap(String name, Class<K> keyClass, Class<V> valueClass) {
DataType keyType = getDataType(keyClass);
DataType valueType = getDataType(valueClass);
return openMap(name, keyType, valueType);
String keyType = getDataType(keyClass);
String valueType = getDataType(valueClass);
@SuppressWarnings("unchecked")
BtreeMap<K, V> m = (BtreeMap<K, V>) openMap(name, "", keyType, valueType);
return m;
}
void removeMap(String name) {
......@@ -233,25 +244,25 @@ public class BtreeMapStore {
mapsChanged.remove(m);
}
private DataType getDataType(Class<?> clazz) {
private String getDataType(Class<?> clazz) {
if (clazz == String.class) {
return STRING_TYPE;
return "";
}
return getTypeFactory().getDataType(clazz);
return getMapFactory().getDataType(clazz);
}
private DataType getDataType(String s) {
if (s.equals("")) {
private DataType buildDataType(String dataType) {
if (dataType.equals("")) {
return STRING_TYPE;
}
return getTypeFactory().fromString(s);
return getMapFactory().buildDataType(dataType);
}
private DataTypeFactory getTypeFactory() {
if (typeFactory == null) {
throw new RuntimeException("No data type factory set");
private MapFactory getMapFactory() {
if (mapFactory == null) {
throw new RuntimeException("No factory set");
}
return typeFactory;
return mapFactory;
}
/**
......@@ -657,6 +668,7 @@ public class BtreeMapStore {
if (percentTotal > fillRate) {
return false;
}
// calculate how many entries a chunk has on average
// TODO use the max size instead of the count
int averageEntryCount = (int) (entryCountTotal / chunks.size());
......@@ -664,20 +676,27 @@ public class BtreeMapStore {
// the 'old' list contains the chunks we want to free up
ArrayList<Chunk> old = New.arrayList();
for (Chunk c : chunks.values()) {
if (retainChunk == -1 || c.id < retainChunk) {
int age = lastChunkId - c.id + 1;
c.collectPriority = c.getFillRate() / age;
old.add(c);
}
}
if (old.size() == 0) {
return false;
}
// sort the list, so the first entry should be collected first
Collections.sort(old, new Comparator<Chunk>() {
public int compare(Chunk o1, Chunk o2) {
return new Integer(o1.collectPriority).compareTo(o2.collectPriority);
}
});
int moveCount = 0;
Chunk move = null;
// find out up to were we need to move
// try to move one (average sized) chunk
int moveCount = 0;
Chunk move = null;
for (Chunk c : old) {
if (moveCount + c.liveCount > averageEntryCount) {
break;
......@@ -711,39 +730,39 @@ public class BtreeMapStore {
}
}
}
// the metaRootPos might not be set
move = readChunkHeader(move.start);
log(" meta:" + move.id + "/" + move.metaRootPos + " start: " + move.start);
// change at least one entry in the map
// to ensure a chunk will be written
// (even if there is nothing to move)
meta.put("chunk." + move.id, move.toString());
BtreeMap<String, String> oldMeta = new BtreeMap<String, String>(this, 0, "old-meta", STRING_TYPE, STRING_TYPE, 0);
oldMeta.setRootPos(move.metaRootPos);
Iterator<String> it = oldMeta.keyIterator(null);
Iterator<String> it = oldMeta.keyIterator("map.");
while (it.hasNext()) {
String k = it.next();
String s = oldMeta.get(k);
log(" " + k + " " + s.replace('\n', ' '));
if (!k.startsWith("map.")) {
continue;
break;
}
String s = oldMeta.get(k);
k = k.substring("map.".length());
if (!maps.containsKey(k)) {
@SuppressWarnings("unchecked")
BtreeMap<Object, Object> data = (BtreeMap<Object, Object>) maps.get(k);
if (data == null) {
continue;
}
String[] idTypesList = StringUtils.arraySplit(s, '/', false);
int id = Integer.parseInt(idTypesList[0]);
DataType kt = getDataType(idTypesList[2]);
DataType vt = getDataType(idTypesList[3]);
log(" " + k + " " + s.replace('\n', ' '));
String[] idTypeList = StringUtils.arraySplit(s, '/', false);
int id = Integer.parseInt(idTypeList[0]);
DataType kt = buildDataType(idTypeList[3]);
DataType vt = buildDataType(idTypeList[4]);
long oldDataRoot = Long.parseLong(oldMeta.get("root." + id));
if (oldDataRoot != 0) {
BtreeMap<?, ?> oldData = new BtreeMap<Object, Object>(this, id, "old-" + k, kt, vt, 0);
if (oldDataRoot == 0) {
// no rows
} else {
oldData.setRootPos(oldDataRoot);
@SuppressWarnings("unchecked")
BtreeMap<Object, Object> data = (BtreeMap<Object, Object>) maps.get(k);
Iterator<?> dataIt = oldData.keyIterator(null);
while (dataIt.hasNext()) {
Object o = dataIt.next();
......@@ -952,7 +971,7 @@ public class BtreeMapStore {
}
}
for (BtreeMap<?, ?> m : maps.values()) {
if (m.getCreatedVersion() > version) {
if (m.getCreateVersion() > version) {
m.close();
removeMap(m.getName());
} else {
......@@ -969,7 +988,7 @@ public class BtreeMapStore {
private void revertTemp() {
freedChunks.clear();
for (BtreeMap<?, ?> m : mapsChanged.values()) {
m.stored();
m.revertTemp();
}
mapsChanged.clear();
temp.clear();
......
......@@ -13,14 +13,17 @@ import java.util.Iterator;
* A cursor to iterate over elements in ascending order.
*
* @param <K> the key type
* @param <V> the value type
*/
class Cursor<K> implements Iterator<K> {
class Cursor<K, V> implements Iterator<K> {
private ArrayList<CursorPos> parents = new ArrayList<CursorPos>();
private final BtreeMap<K, V> map;
private final ArrayList<CursorPos> parents = new ArrayList<CursorPos>();
private K current;
Cursor(Page root, K from) {
Page.min(root, parents, from);
Cursor(BtreeMap<K, V> map, Page root, K from) {
this.map = map;
map.min(root, parents, from);
fetchNext();
}
......@@ -34,7 +37,7 @@ class Cursor<K> implements Iterator<K> {
@SuppressWarnings("unchecked")
private void fetchNext() {
current = (K) Page.nextKey(parents);
current = (K) map.nextKey(parents);
}
public boolean hasNext() {
......
......@@ -64,9 +64,10 @@ public interface DataType {
Object read(ByteBuffer buff);
/**
* Get the string representation of this type.
* Get the stable string representation that is used to build this data
* type.
*
* @return the string
* @return the string representation
*/
String asString();
......
......@@ -9,15 +9,31 @@ package org.h2.dev.store.btree;
/**
* A factory for data types.
*/
public interface DataTypeFactory {
public interface MapFactory {
/**
* Read the data type.
* Build a map.
*
* @param s the string
* @param mapType the map type and type specific meta data
* @param store the store
* @param id the unique map id
* @param name the map name
* @param keyType the key type
* @param valueType the value type
* @param createVersion when the map was created
* @return the map
*/
<K, V> BtreeMap<K, V> buildMap(
String mapType, BtreeMapStore store, int id, String name,
DataType keyType, DataType valueType, long createVersion);
/**
* Parse the data type.
*
* @param dataType the string and type specific meta data
* @return the type
*/
DataType fromString(String s);
DataType buildDataType(String dataType);
/**
* Get the data type object for the given class.
......@@ -25,6 +41,6 @@ public interface DataTypeFactory {
* @param objectClass the class
* @return the data type object
*/
DataType getDataType(Class<?> objectClass);
String getDataType(Class<?> objectClass);
}
......@@ -9,13 +9,12 @@ package org.h2.dev.store.btree;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import org.h2.compress.Compressor;
/**
* A btree page (a node or a leaf).
* A page (a node or a leaf).
* <p>
* For nodes, the key at a given index is larger than the largest key of the
* For b-tree nodes, the key at a given index is larger than the largest key of the
* child at the same index.
* <p>
* File format: page length (including length): int check value: short map id:
......@@ -25,11 +24,6 @@ import org.h2.compress.Compressor;
*/
public class Page {
private static final IllegalArgumentException KEY_NOT_FOUND = new IllegalArgumentException(
"Key not found");
private static final IllegalArgumentException KEY_ALREADY_EXISTS = new IllegalArgumentException(
"Key already exists");
private final BtreeMap<?, ?> map;
private final long version;
private long pos;
......@@ -101,15 +95,33 @@ public class Page {
return p;
}
private Page copyOnWrite(long writeVersion) {
if (version == writeVersion) {
return this;
Object getKey(int index) {
return keys[index];
}
getStore().removePage(pos);
Page newPage = create(map, writeVersion, keys, values, children,
childrenSize, totalSize);
newPage.cachedCompare = cachedCompare;
return newPage;
Page getChildPage(int index) {
return map.readPage(children[index]);
}
Object getValue(int x) {
return values[x];
}
int getKeyCount() {
return keys.length;
}
boolean isLeaf() {
return children == null;
}
/**
* Get the position of the page
*
* @return the position
*/
long getPos() {
return pos;
}
public String toString() {
......@@ -133,62 +145,28 @@ public class Page {
return buff.toString();
}
/**
* Get the position of the page
*
* @return the position
*/
long getPos() {
return pos;
}
/**
* Get the value for the given key, or null if not found.
*
* @param key the key
* @return the value or null
*/
Object find(Object key) {
int x = findKey(key);
if (children != null) {
if (x < 0) {
x = -x - 1;
} else {
x++;
}
Page p = map.readPage(children[x]);
return p.find(key);
}
if (x >= 0) {
return values[x];
Page copyOnWrite(long writeVersion) {
if (version == writeVersion) {
return this;
}
return null;
map.getStore().removePage(pos);
Page newPage = create(map, writeVersion, keys, values, children,
childrenSize, totalSize);
newPage.cachedCompare = cachedCompare;
return newPage;
}
/**
* Get the value for the given key, or null if not found.
* Search the key in this page using a binary search. Instead of always
* starting the search in the middle, the last found index is cached. If the
* key was found, the returned value is the index in the key array. If not
* found, the returned value is negative, where -1 means the provided key is
* smaller than any keys in this page. See also Arrays.binarySearch.
*
* @param key the key
* @return the page or null
* @return the value or null
*/
Page findPage(Object key) {
int x = findKey(key);
if (children != null) {
if (x < 0) {
x = -x - 1;
} else {
x++;
}
Page p = map.readPage(children[x]);
return p.findPage(key);
}
if (x >= 0) {
return this;
}
return null;
}
private int findKey(Object key) {
int binarySearch(Object key) {
int low = 0, high = keys.length - 1;
int x = cachedCompare - 1;
if (x < 0 || x > high) {
......@@ -225,85 +203,7 @@ public class Page {
// return -(low + 1);
}
/**
* Go to the first element for the given key.
*
* @param p the current page
* @param parents the stack of parent page positions
* @param key the key
*/
static void min(Page p, ArrayList<CursorPos> parents, Object key) {
while (p != null) {
if (p.children != null) {
int x = key == null ? -1 : p.findKey(key);
if (x < 0) {
x = -x - 1;
} else {
x++;
}
CursorPos c = new CursorPos();
c.page = p;
c.index = x;
parents.add(c);
p = p.map.readPage(p.children[x]);
} else {
int x = key == null ? 0 : p.findKey(key);
if (x < 0) {
x = -x - 1;
}
CursorPos c = new CursorPos();
c.page = p;
c.index = x;
parents.add(c);
return;
}
}
}
/**
* Get the next key.
*
* @param parents the stack of parent page positions
* @return the next key
*/
static Object nextKey(ArrayList<CursorPos> parents) {
if (parents.size() == 0) {
return null;
}
while (true) {
// TODO performance: avoid remove/add pairs if possible
CursorPos p = parents.remove(parents.size() - 1);
int index = p.index++;
if (index < p.page.keys.length) {
parents.add(p);
return p.page.keys[index];
}
while (true) {
if (parents.size() == 0) {
return null;
}
p = parents.remove(parents.size() - 1);
index = ++p.index;
if (index < p.page.children.length) {
parents.add(p);
Page x = p.page;
x = x.map.readPage(x.children[index]);
min(x, parents, null);
break;
}
}
}
}
private int keyCount() {
return keys.length;
}
private boolean isLeaf() {
return children == null;
}
private Page split(int at) {
Page split(int at) {
return isLeaf() ? splitLeaf(at) : splitNode(at);
}
......@@ -357,118 +257,6 @@ public class Page {
return newPage;
}
/**
* Update a value for an existing key.
*
* @param map the map
* @param p the page (may not be null)
* @param writeVersion the write version
* @param key the key
* @param value the value
* @return the root page
* @throws InvalidArgumentException if this key does not exist (without
* stack trace)
*/
static Page set(BtreeMap<?, ?> map, Page p, long writeVersion, Object key,
Object value) {
if (p == null) {
throw KEY_NOT_FOUND;
}
int index = p.findKey(key);
if (p.isLeaf()) {
if (index < 0) {
throw KEY_NOT_FOUND;
}
p = p.copyOnWrite(writeVersion);
p.setValue(index, value);
return p;
}
// it is a node
if (index < 0) {
index = -index - 1;
} else {
index++;
}
Page c = map.readPage(p.children[index]);
Page c2 = set(map, c, writeVersion, key, value);
if (c != c2) {
p = p.copyOnWrite(writeVersion);
p.setChild(index, c2.getPos(), c2.getPos());
}
return p;
}
/**
* Add a new key-value pair.
*
* @param map the map
* @param p the page (may be null)
* @param writeVersion the write version
* @param key the key
* @param value the value
* @return the root page
* @throws InvalidArgumentException if this key already exists (without
* stack trace)
*/
static Page add(BtreeMap<?, ?> map, Page p, long writeVersion, Object key,
Object value) {
if (p == null) {
Object[] keys = { key };
Object[] values = { value };
p = create(map, writeVersion, keys, values, null, null, 1);
return p;
}
if (p.keyCount() >= map.getStore().getMaxPageSize()) {
// only possible if this is the root,
// otherwise we would have split earlier
p = p.copyOnWrite(writeVersion);
int at = p.keyCount() / 2;
long totalSize = p.getTotalSize();
Object k = p.keys[at];
Page split = p.split(at);
Object[] keys = { k };
long[] children = { p.getPos(), split.getPos() };
long[] childrenSize = { p.getTotalSize(), split.getTotalSize() };
p = create(map, writeVersion, keys, null, children, childrenSize,
totalSize);
// now p is a node; insert continues
} else if (p.isLeaf()) {
int index = p.findKey(key);
if (index >= 0) {
throw KEY_ALREADY_EXISTS;
}
index = -index - 1;
p = p.copyOnWrite(writeVersion);
p.insert(index, key, value, 0, 0);
return p;
}
// p is a node
int index = p.findKey(key);
if (index < 0) {
index = -index - 1;
} else {
index++;
}
Page c = map.readPage(p.children[index]);
if (c.keyCount() >= map.getStore().getMaxPageSize()) {
// split on the way down
c = c.copyOnWrite(writeVersion);
int at = c.keyCount() / 2;
Object k = c.keys[at];
Page split = c.split(at);
p = p.copyOnWrite(writeVersion);
p.setChild(index, c.getPos(), c.getTotalSize());
p.insert(index, k, null, split.getPos(), split.getTotalSize());
// now we are not sure where to add
return add(map, p, writeVersion, key, value);
}
Page c2 = add(map, c, writeVersion, key, value);
p = p.copyOnWrite(writeVersion);
// the child might be the same, but not the size
p.setChild(index, c2.getPos(), c2.getTotalSize());
return p;
}
long getTotalSize() {
if (BtreeMapStore.ASSERT) {
long check = 0;
......@@ -487,7 +275,7 @@ public class Page {
return totalSize;
}
private void setChild(int index, long pos, long childSize) {
void setChild(int index, long pos, long childSize) {
if (pos != children[index]) {
long[] newChildren = new long[children.length];
System.arraycopy(children, 0, newChildren, 0, newChildren.length);
......@@ -504,7 +292,7 @@ public class Page {
}
}
private void setValue(int index, Object value) {
void setValue(int index, Object value) {
// create a copy - not required if already cloned once in this version,
// but avoid unnecessary cloning would require a "modified" flag
Object[] newValues = new Object[values.length];
......@@ -521,65 +309,16 @@ public class Page {
for (long c : children) {
int type = DataUtils.getPageType(c);
if (type == DataUtils.PAGE_TYPE_LEAF) {
getStore().removePage(c);
map.getStore().removePage(c);
} else {
map.readPage(c).removeAllRecursive();
}
}
}
getStore().removePage(pos);
}
/**
* Remove an existing key-value pair.
*
* @param p the page (may not be null)
* @param writeVersion the write version
* @param key the key
* @return the new root page (null if empty)
* @throws InvalidArgumentException if not found (without stack trace)
*/
static Page removeExisting(Page p, long writeVersion, Object key) {
if (p == null) {
throw KEY_NOT_FOUND;
}
int index = p.findKey(key);
if (p.isLeaf()) {
if (index >= 0) {
if (p.keyCount() == 1) {
p.getStore().removePage(p.pos);
return null;
}
p = p.copyOnWrite(writeVersion);
p.remove(index);
} else {
throw KEY_NOT_FOUND;
}
return p;
}
// node
if (index < 0) {
index = -index - 1;
} else {
index++;
}
Page c = p.map.readPage(p.children[index]);
Page c2 = removeExisting(c, writeVersion, key);
p = p.copyOnWrite(writeVersion);
if (c2 == null) {
// this child was deleted
p.remove(index);
if (p.keyCount() == 0) {
p.getStore().removePage(p.pos);
p = p.map.readPage(p.children[0]);
}
} else {
p.setChild(index, c2.getPos(), c2.getTotalSize());
}
return p;
map.getStore().removePage(pos);
}
private void insert(int index, Object key, Object value, long child,
void insert(int index, Object key, Object value, long child,
long childSize) {
Object[] newKeys = new Object[keys.length + 1];
DataUtils.copyWithGap(keys, newKeys, keys.length, index);
......@@ -607,7 +346,7 @@ public class Page {
}
}
private void remove(int index) {
void remove(int index) {
Object[] newKeys = new Object[keys.length - 1];
int keyIndex = index >= keys.length ? index - 1 : index;
DataUtils.copyExcept(keys, newKeys, keys.length, keyIndex);
......@@ -644,16 +383,14 @@ public class Page {
throw new RuntimeException("Error reading page, expected map "
+ map.getId() + " got " + mapId);
}
int len = DataUtils.readVarInt(buff);
int checkTest = DataUtils.getCheckValue(chunkId)
^ DataUtils.getCheckValue(map.getId())
^ DataUtils.getCheckValue(offset)
^ DataUtils.getCheckValue(pageLength)
^ DataUtils.getCheckValue(len);
^ DataUtils.getCheckValue(pageLength);
if (check != (short) checkTest) {
throw new RuntimeException("Error in check value, expected "
+ checkTest + " got " + check);
}
int len = DataUtils.readVarInt(buff);
keys = new Object[len];
int type = buff.get();
boolean node = (type & 1) == DataUtils.PAGE_TYPE_NODE;
......@@ -743,10 +480,8 @@ public class Page {
int pageLength = buff.position() - start;
buff.putInt(start, pageLength);
int check = DataUtils.getCheckValue(chunkId)
^ DataUtils.getCheckValue(map.getId())
^ DataUtils.getCheckValue(start)
^ DataUtils.getCheckValue(pageLength)
^ DataUtils.getCheckValue(len);
^ DataUtils.getCheckValue(pageLength);
buff.putShort(start + 4, (short) check);
this.pos = DataUtils.getPagePos(chunkId, start, pageLength, type);
}
......@@ -823,12 +558,4 @@ public class Page {
return count;
}
BtreeMapStore getStore() {
return map.getStore();
}
long getVersion() {
return version;
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论